行业知识
C++:Vector的模拟实现
�� 创作不易,感谢三连 !!
一,前言
在学习string类的时候,我们可能会发现遍历的话下标访问特别香,比迭代器用的舒服,但是下标其实只能是支持连续的空间,他的使用是非常具有局限性的,随着STL学习的深入我们会发现其实迭代器才是大佬!!Vector虽然也支持下标访问,但是很多成员函数都是用的迭代器,所以我们要模拟实现的话迭代器十分重要,vs使用的是PJ版的STL版本,比较难懂,所以我们模拟实现统一用SGI版本去实现,所以在模拟实现之前,我们要去看看他的源码到底有哪些成员变量
SGI下的vector有三个成员变量,通过观察其他源码可以大致推断 _start是指向起始位置,_finish是指向有效数据的下一个位置(迭代器都遵循左闭右开),end_of_storage是指向有效容量的最后一个位置。
通过这个我们可以观察到SGI版本下的迭代器其实就是一个原生指针,value_type*类型相当于是模板T对应的指针类型,有了这些大致了解,我们就可以去模拟实现啦!!
二,vector的模拟实现
大致框架需要有模板(类外定义)/迭代器以及迭代器的获取(public定义,要有可读可写的也要有可读不可写的)/成员变量(private定义) 并且为了不和库的vector冲突,我们需要自己搞一个命名空间
namespace cyx{//模板template//迭代器(可读可写)class vector{public:typedef T* iterator;iterator begin(){return _start;}iterator end(){return _finish;}//迭代器(可读不可写)typedef const T* const_iterator;const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}private://成员变量iterator _start;iterator _finish;iterator _end_of_storage;}} 然后我们开始实现!!
2.1 构造函数和析构函数
2.1.1 无参构造函数
//无参构造函数vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){} 2.1.2 迭代器区间构造
//传别人的迭代器进行构造templatevector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){ //这里传的是别人的迭代器,不知道会传多少数据,不能提前扩容,只能让pushback的时候去判断while (first != last){push_back(*first);++first;}} push_back是尾插数据,具体实现后面会写。
思考:为什么迭代器也要搞个类模板呢?
答:本质上是为了让这个函数更加灵活,可以传不同类型的迭代器来帮助我们初始化!!
比如这个地方我们传string类的迭代器
传vector类的迭代器
2.1.3 有参构造函数(对n个存储的类型去调用他们的构造)
//有参构造函数(对n个存储的类型去调用他们的构造)vector(size_t n,const T&val=T() ):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);//因为我们知道会进多少数据,所以可以提前开空间for (int i = 0; i reserve是扩容到n,具体实现后面会写。
思考:
1.缺省值T( )是什么意思
答:这个地方的缺省值不能给0!!因为vector可能会存储内置类型,也可能会存储自定义类型,比如vector,所以如果我们没给值,缺省值就要给他的默认无参构造函数,这个默认构造函数可以使用匿名对象。
2.const T&val=T() T()不是用一次就析构吗,为什么可以用引用
答:T()是一个用一次就析构的匿名对象,其实本质上是因为他没有名字,用T引用val可以充当他的名字,此时用val就相当于用这个匿名对象,所以匿名对象的生命周期被延长到和val一样,但是由于匿名对象是一个临时变量,所以具有常性,所以必须用const修饰的val才可以当他的别名,否则会出现权限放大!!
3.非法的间接寻址是为什么?
如下图我传(10,5),会出非法间接寻址
但是我传(10u,5)就可以正常使用了,为什么会这样??
答:
根据上图写出一个重载的有参构造
//重载一个防止间接寻址vector(int n, const T val = T()):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);//因为我们知道会进多少数据,所以可以提前开空间for (int i = 0; i 2.1.4 拷贝构造+memcpy拷贝问题+赋值重载
但是真的有这么顺利吗??
思考:
为什么存string类就会崩了?? 这就涉及到memcpy的拷贝问题
我们以上述问题来画图解释一下
总结:
1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是
浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。
所以在这个地方我们的拷贝构造不能用memcpy
//拷贝构造(传统)vector(const vector& v):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){_start = new T[v.capacity()];//memcpy(_start, v._start, sizeof(T)*v.size()); 不能用memcpy 是浅拷贝for (int i = 0; i 但是真的没有问题了吗??看看这个
但道理来说得打印出9个1 结果呢??
原因是什么呢,我们先看看resize函数
//重载赋值=(传统)vector& operator=(const vector &v){T* temp = new T [v.capacity()];for(int i=0;i