当前位置: 首页 > news >正文

【C++】:深入理解vector(2):vector深度剖析及模拟实现

目录

 一 怎么看源代码

二 vector的部分源代码

1 迭代器类型的成员变量

2 迭代器

3 指定位置插入

4 其他

三 自己实现vector

1 头文件

2 类

3 构造函数和析构函数

4 尾插

5 capacity()

6 reverse

7 insert

8 erase

9 iterator补充

10 resize

11 深拷贝和浅拷贝

1 传统写法

2 现代写法

四      memcpy拷贝时出现的问题 

​编辑

五 理解动态二维数组


深入理解vector(1)链接:【【C++】深入理解vector(1):vector的使用和OJ题

 一 怎么看源代码

在我们尝试去看源代码的时候,因为源代码的复用性较高,比较冗杂,对于我们这种没有足够实战经验的uu来说不是特别友好,所以我们可以借助工具去阅读源代码。

链接:Source insight

在看源代码的时候,要学会抓核心:了解内容,抓框架,注释,画图。

画图是一项很有用的功能,在面对很多比较复杂,容易搞混的代码和情况的时候,能很直观的反映问题。


二 vector的部分源代码

1 迭代器类型的成员变量

start:指向 vector 内部数组的起始位置,即第一个元素的存储地址

start:指向 vector 内部数组的起始位置,即第一个元素的存储地址

end_of_storage:指向 vector 当前已分配内存空间的末尾位置

2 迭代器

3 指定位置插入

4 其他

在学习STL源代码的时候,可以让AI帮忙写相关的注释帮助理解,也可以看书:《STL源码剖析》


三 自己实现vector

1 头文件

#pragma once#include<assert.h>

2 类

namespace zz
{template<class T>class vector{public://typedef T* iterator;using iterator = T*;using const_iterator = const T*;//.........private:iterator _start;iterator _finish;iterator _end_of_storage'
};

其中,使用using定义了T*重命名为iterator

在此处,using的作用和typedef是一样的,但是using比typedef的作用更广泛,这个我们后面再讲

注意:后面写的函数实现等内容都是再类中的public中实现的,后面不再赘述。

模板不能声明和定义分离

3 构造函数和析构函数

(1)构造函数

vector():: _start(nullptr);, _finish(nullptr);, _end_of_storage(nullage);{}

在 C++ 中,这个构造函数定义末尾的 {} 是函数体的标志,用于表示构造函数的实现部分。

具体来说:

  • 前面的 vector() 是构造函数的声明(因为是无参构造,所以括号内为空)。
  • 中间的 :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) 是成员初始化列表,用于在进入函数体之前初始化类的成员变量(这里将三个指针都初始化为 nullptr)。
  • 最后的 {} 是构造函数的函数体,由于成员变量已经通过初始化列表完成了初始化,且这个构造函数不需要额外执行其他逻辑,所以函数体为空。

简单理解:{} 在这里表示 “构造函数的具体执行代码”,只是当前这个构造函数没有需要执行的额外代码,所以用空的函数体即可。如果后续需要在构造时添加其他操作(比如打印日志),就可以在 {} 内部编写代码。

(2)析构函数

~vector():
{if(_start){delete[] _start;_start = _finish = _endd_of_storage;}
}

4 尾插

void push_back(const T& x)
{if(_finsh == _end_ of_storage){reverse(capacity()==0 ? 4 : capacity()*2);}*_finish = x;++_finish;
}

因为finish指向最后一个有效数据的下一个位置,所以先赋值,再finish++

注意:因为我们的成员函数不含capacity,所以我们需要自己实现一个capacity ,还有扩容reverse函数

5 capacity()

size_t capacity() const
{return end_of_storage - _start;
}

6 reverse

void reserve(size_t n)
{if(n > capacity() ){size_t sz = size();T* tmp = new T[n];if(_start){memcpy(tmp, _start, sizeof(T)*sz);delete[] _start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;
}

7 insert

但是这样写其实是有问题的,造成了迭代器失效

在开辟了新空间里之后,_start和_finish都指向了新的空间,但是此时pos还指向的是原来的旧         空间,但是此时旧空间已经释放,所以会造成pos为野指针

修改:

iterator insert(iterator pos, const T& x){assert(pos >= _start);assert(pos <= _finish);// if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}// Ųiterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}

修改点:用len记录pos到_start的距离,扩容完后,重新确定pos的位置

那为啥要返回pos呢:

当it传递给pos后,形参的改变不会影响实参

8 erase

iterator erase(iterator pos)
{assert(pos >= _start);assert(pos <= _finih);iterator it = pos + 1;while(it != _finissh){*(it-1) = *it;++it;}--finish;return pos;
}

erase和insert都会造成迭代器失效

vector 的 erase 函数删除元素时,会执行 “元素挪动” 操作

iterator it = pos + 1;
while (it != _finish) {*(it - 1) = *it;  // 后续元素向前挪动,覆盖被删除元素的位置++it;
}
--_finish;  // 有效元素数量减少

这个过程会导致两类迭代器失效:

  • 被删除的 pos 迭代器本身失效pos 原本指向待删除的元素,但删除后,该位置被后续元素覆盖(或变成无效元素)。此时 pos 指向的内存虽然可能存在(未释放),但已不再是原来的元素,继续使用 pos 会访问错误的数据。

  • pos 之后的所有迭代器失效由于 pos 之后的元素都向前挪动了一个位置(例如,原 pos+1 位置的元素移动到 pos 位置,原 pos+2 移动到 pos+1 位置……),原本指向这些元素的迭代器(如 pos+1pos+2 等)现在指向的是 “原位置的下一个元素”,与预期不符,因此失效。

9 iterator补充

iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}

10 resize

调整容器有效数据个数

viod resize(size_t n, T val = T() )
{if(n < size() )//n小于当前元素个数{_finish = _start + n;//finish截断多于数据}else{reserve(n);while (_finish < _strt + n){*_finish = val;++_finish;}
}

这个时候就有uu有疑问了,这个参数中的 T val = T()是什么

这也是C++中对内置类型初始化的一种方式

例如:

//C98int j = int();int k = int(1);
// C++11int y = {};int t = {1};int z{ 2 };int m{};

不带括号的类型是将变量初始化为接近0的值(可以看为是0),具体内容我们到后面再讲解,现在先让大家认识一下

11 深拷贝和浅拷贝

拷贝构造只能用引用传参,不能用传值传参

深拷贝不止要拷贝对象,也要拷贝对象所指向的内容

1 传统写法

初始化列表,显示写了,用显示的值,没有显示写,看缺省值(在private中的定义直接给值);如果都没有,也要走初始化列表(自定义类型调默认构造,内置类型可能是随机值),初始化列表是一定要写的

没有显示的写,给缺省值的情况是这样写的;

public:vector(){}
//.......private:_start = nullptr;_finish = nullptr;_end_of_storage = nullptr;

我们来看拷贝构造函数

	// 传统写法v2(v1)vector(const vector<T>& v){reserve(v.capacity());for (const auto& e : v){push_back(e);}}

这里的v是v1,this是v2

2 现代写法
	vector(const vector<T>& v){vector<T> tmp(v.begin(), v.end());swap(tmp);}

不自己去开辟空间,通过他人开辟空间


四      memcpy拷贝时出现的问题 

在我们前面写的reserve接口中,如果函数模板T表示的时内置类型:Int,double之类的,不会出现问题了,但如果是自定义类型呢?例如string

这个时候就会出错,因为memcpy是浅拷贝,如果有像string中含有指向其他地方的成员变量时,就会出错。                                                                                                                                                                                                                                                                 

我们来看一下这个部分的详细解释:

那么我们如何修改呢?

(1)使用for循环

	void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){for (size_t i = 0; i < sz; i++){tmp[i] = _start[i]; // 如果是string,调用string的赋值深拷贝}delete[] _start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}}

如果此时时自定义对象,那么在for循环中,就会自动调用对应的赋值运算符重载

(2)swap

void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){for (size_t i = 0; i < sz; i++){std::swap(tmp[i], _start[i]);  // 如果是string,调用string的交换,交换资源指向}delete[] _start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}}

// 如果是string,调用string的交换,交换资源指向


五 理解动态二维数组

以杨辉三角为例:

// 以杨慧三角的前n行为例:假设n为5
void test2vector(size_t n)
{// 使用vector定义二维数组vv,vv中的每个元素都是vector<int>bit::vector<bit::vector<int>> vv(n);// 将二维数组每一行中的vecotr<int>中的元素全部设置为1for (size_t i = 0; i < n; ++i)vv[i].resize(i + 1, 1);// 给杨慧三角出第一列和对角线的所有元素赋值for (int i = 2; i < n; ++i){for (int j = 1; j < i; ++j){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}
}

bit::vector> vv(n); 构造一个vv动态二维数组,vv中总共有n个元素,每个元素 都是vector类型的,每行没有包含任何元素,如果n为5时如下所示:

vv中元素填充完成之后,如下图所示:

使用标准库中vector构建动态二维数组时与上图实际是一致的。

http://www.dtcms.com/a/500438.html

相关文章:

  • 网站的友情连接怎么做广西建设执业资格注册中心官网
  • 力扣94.二叉树的中序遍历(递归and迭代法)(java)
  • 做网站ps建立多大的画布开发外贸客户的免费平台
  • 郑州企业网站排行兰州东方商易文化传播有限责任公司
  • 襄阳建设路21号创意园网站西安高端模板建站
  • HTTP 请求:GET 与 POST 的核心区别
  • 饰品类网站建设定位红酒网站模板
  • 稀疏值(sparse)的switch-case语句,编译器生成了条件跳转链(if-else链)的实现方式
  • 广州市住房和城乡建设局网站唐山网站建设开发设计公司
  • 做博客的网站有哪些网站推广技巧
  • 烟台做外贸网站网站开发商品排序逻辑
  • 想学网站开发北京网智易通科技有限公司
  • 网站从建设到运营管理的理解php网站运行很慢
  • NetBeans下载和安装教程(附安装包,适合新手)
  • php做的网站怎么发布哪里有广告设计制作的培训
  • 现在什么网站做外贸的最好只有asp网站代码可以重新编译吗
  • 【UCIe】协议演进
  • 重庆装修公司前十强优化网站技术
  • 从遍历序列到原树:二叉树重建的逻辑与实现
  • 第3章,[标签 Win32] :窗口类03,窗口过程函数与消息机制
  • 网站空间 数据库网站源码防盗原理
  • 网站开发 提成项目分享网
  • 有模板了怎么建设网站网站推广活动策划
  • 手机用什么软件做网站台州企业免费建站
  • 网约车平台app网站建设活动策划案格式模板和范文
  • 【C语言初阶】算术操纵符,移位操作符,位操作符,赋值操作符,单目操作符,关系操作符,逻辑操作符,条件操作符,
  • 郑州各区房价一览表seo页面优化技术
  • 虚拟主机怎么弄网站网页设计尺寸参考表
  • 创建用户组、用户、权限
  • LLMs:nanochat(仿照GPT-3 Small)的简介、安装和使用方法、案例应用之详细攻略