> 文章列表 > C++初阶—vector深度剖析及模拟实现

C++初阶—vector深度剖析及模拟实现

C++初阶—vector深度剖析及模拟实现

目录

➡️0. 前言

😊1.简易框架实现

🐔1. 无参构造

🐔2. 容量capacity — 长度size()

🐔3. 动态增长 — push_back—pop_back — reserve

🐔4. 迭代器的实现

🐔4.front和back的实现

😊2.迭代器失效问题

🐔1. insert和erase初步实现

🐔2. g++和msvc差异分析及测试:

🐔3. erase和insert更新

😊3.深浅拷贝问题

🐔1.实现迭代器区间构造

🐔2.实现拷贝构造—传统写法

🐔3.实现拷贝构造—现代写法

🐔4.更新reserve函数

😊4.其他函数的实现

🐔1.resize函数

😊5.完整代码


➡️0. 前言

vector 容器是 STL 中最常用的容器之一,它和 array 容器非常类似,都可以看做是对C++普通数组的“升级版”。不同之处在于,array 实现的是静态数组(容量固定的数组),而 vector 实现的是一个动态数组,即可以进行元素的插入和删除,在此过程中,vector 会动态调整所占用的内存空间,整个过程无需人工干预。

vector 常被称为向量容器,因为该容器擅长在尾部插入或删除元素,在常量时间内就可以完成,时间复杂度为O(1);而对于在容器头部或者中部插入或删除元素,则花费时间要长一些(移动元素需要耗费时间),时间复杂度为线性阶O(n)

下面将对vector最主要部分以及最常使用部分进行模拟实现,以便更好的掌握STL容器vector

深度剖析vector迭代器在Linux—g++失效问题以及在Windows—MSVC失效问题,涉及深浅拷贝问题

参考文档:vector - C++ Reference (cplusplus.com) 以及 STL源码

😊1.简易框架实现

 此图参考书籍《STL源码剖析》侯杰老师

由上图便可得出vector成员变量,分别实现其构造函数,析构函数

	template<class T>class vector{private:iterator _start;iterator _finish;iterator _end_of_storage;};

🐔1. 无参构造

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

🐔2. 容量capacity — 长度size()

		size_t capacity() const{return _end_of_storage - _start;}size_t size() const{return _finish - _start;}

🐔3. 动态增长 — push_back—pop_back — reserve

如果reserve空间大于capacity,便需要进行扩容,而push_back也需要检查容量大小,判断是否需要扩容,此时扩容一块新的空间,旧空间将会进行释放,同时将原空间数据拷贝至新空间,此时使用memcpy是否有问题?

		void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){std::memcpy(tmp, _start, sizeof(T) * sz);delete[] _start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}}void push_back(const T& x){if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}void pop_back(){assert(_finish > _start);--_finish;}

🐔4. 迭代器的实现

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

测试案例1:

	void func(const vector<int>& v){vector<int>::const_iterator it = v.begin();while (it != v.end()){std::cout << *it << " ";it++;}}void test_vector1(){/*  vector<std::string> v;v.push_back("xxxx");//发生隐式类型转换,产生临时变量,临时变量具有常性//因此push_back参数需要用const修饰,同时大对象传参用& */vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (size_t i = 0; i < v.size(); ++i){std::cout << v[i] << " ";}std::cout << std::endl;v.pop_back();v.pop_back();vector<int>::iterator it = v.begin();while (it != v.end()){std::cout << *it << " ";it++;}std::cout << std::endl;for (auto& e : v){std::cout << e << " ";}std::cout << std::endl;func(v);}

🐔4.front和back的实现

		T& front(){assert(size() > 0);return _start[0];}T& back(){assert(size() > 0);return *(_finish - 1);}

😊2.迭代器失效问题

🐔1. insert和erase初步实现

首先根据以往方式实现简易指定位置插入删除操作

		void insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_end_of_storage == _finish){size_t len = pos - _start;//开辟新的空间pos指向位置被释放,此时pos成为野指针reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;//规避野指针,根据偏移量,计算新的pos,进行插入}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;}void erase(iterator pos){assert(pos >= _start && pos < _finish);iterator begin = pos + 1;while (begin <_finish){*(begin-1) = *begin;++begin;}--_finish;}

测试案例1:

	void test_vector2(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//v.push_back(5);for (size_t i = 0; i < v.size(); ++i){std::cout << v[i] << " ";}std::cout << std::endl;vector<int>::iterator pv = std::find(v.begin(),v.end(),4);if (pv != v.end()){//在p位置插入数据以后,不要访问p,因为p可能由于扩容而失效//迭代器失效——会造成野指针  v.insert(pv, 100);//v.insert(pv, 99);//err}v.insert(v.begin(), 1);for (auto& e : v){std::cout << e << " ";}std::cout << std::endl;}

通过上测试案例,如果在pv位置进行插入,此时再次使用pv位置,便可能会导致野指针问题,野指针问题的来源正是由于扩容释放原空间而产生的原指针指向已失效空间。

测试案例2:

	void test_vector3(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (size_t i = 0; i < v.size(); ++i){std::cout << v[i] << " ";}std::cout << std::endl;vector<int>::iterator pv = std::find(v.begin(), v.end(), 3);v.erase(pv);for (auto& e : v){std::cout << e << " ";}std::cout << std::endl;v.erase(pv);for (auto& e : v){std::cout << e << " ";}std::cout << std::endl;}

在不同编译器下,STL版本不同,容器删除操作的底层实现也不同,而删除操作如果进行了缩容(以时间换空间),便会产生野指针问题。

🐔2. g++和msvc差异分析及测试:

void test_std_vector3()
{std::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(3);v.push_back(4);v.push_back(5);for (size_t i = 0; i < v.size(); ++i){std::cout << v[i] << " ";}std::cout << std::endl;std::vector<int>::iterator pv = std::find(v.begin(), v.end(), 3);v.erase(pv);for (auto& e : v){std::cout << e << " ";}std::cout << std::endl;v.erase(pv);for (auto& e : v){std::cout << e << " ";}std::cout << std::endl;
}

结论:insert/erase pos位置,不要直接访问pos(pos失效)一定要更新,在直接访问,否则可能会出各种出乎意料的结果,这就是所谓的迭代器失效。
STL只是一个规范,在不同编译器下,STL底层实现不同

vs——PJ版STL 
insert和erase在pos位置操作后,不允许在此位置再次操作,强制报错

g++——SGI版本STL
insert和erase在pos位置操作后,允许在此位置再次操作,但是insert扩容或者erase缩容后,会造成野指针问题,会强制报错

🐔3. erase和insert更新

		iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_end_of_storage == _finish){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;//STL规定:返回一个迭代器,指向新插入元素的位置//避免因扩容造成的野指针问题,同时返回新插入元素的位置//只要更新pos,便可以解决在当前位置之前继续插入}iterator erase(iterator pos){assert(pos >= _start && pos < _finish);iterator begin = pos + 1;while (begin <_finish){*(begin-1) = *begin;++begin;}--_finish;//if (size() < capacity / 2)//{//	//缩容---以时间换空间//	//缩容就会导致迭代器的失效//}return pos;//STL规定了返回删除位置的下一个位置的迭代器//避免因缩容导致的野指针问题,同时返回删除位置的下一个位置的迭代器//只要更新pos,便可以进行连续的删除}

测试案例:

	void test_vector5(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(5);v.push_back(4);v.push_back(4);v.push_back(5);v.push_back(5);auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){it = v.insert(it, *it * 2);it++;}it++;}for (auto& e : v){std::cout << e << " ";}std::cout << std::endl;}

😊3.深浅拷贝问题

🐔1.实现迭代器区间构造

		//使用模板便可以使用其他容器的迭代器区间构造_双重模板template<class InputIterator>vector(InputIterator first, InputIterator last): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr){while (first != last){push_back(*first);++first;}}

🐔2.实现拷贝构造—传统写法

		//传统写法vector(const vector<T>& v){_start = new T[v.size()];//这里给size capacity都可以,各有各的优势std::memcpy(_start, v._start, sizeof(T) * v.size());_finish = _start + v.size();_end_of_storage = _start + v.size();//如果使用开辟空间为capacity,这_end_of_storage不同//_end_of_storage = _start + v.capacity;}

🐔3.实现拷贝构造—现代写法

		/*vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(v.size());//vector 数据是T 有可能是容器,使用引用//不给引用还会造成深拷贝,不修改,给constfor (const auto& e : v){push_back(e);}}*/void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage,v._end_of_storage);}//现代写法vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){vector<T> tmp(v.begin(), v.end());swap(tmp);}

测试案列1:

	void test_vector6(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(2);v.push_back(3);vector<int> vv(v);//涉及深浅拷贝//指向同一块空间,会析构两次,一个对象修改会影响另一个对象for (auto& e : v){std::cout << e << " ";}std::cout << std::endl;for (auto& e : vv){std::cout << e << " ";}std::cout << std::endl;//迭代器区间构造vectorstd::string str("abcdef");vector<char> v1(str.begin(),str.end());for (auto& e : v1){std::cout << e << " ";}std::cout << std::endl;}

测试案例2:

	class Solution {public:vector<vector<int>> generate(int numRows){vector<vector<int>> vv;vv.resize(numRows);for (size_t i = 0; i < vv.size(); ++i){vv[i].resize(i + 1, 0);vv[i].front() = vv[i].back() = 1;}for (size_t i = 0; i < vv.size(); ++i){for (size_t j = 0; j < vv[i].size(); ++j){if (vv[i][j] == 0){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}}return vv;//传值返回涉及深拷贝问题//vector<vector<int>> 里面浅拷贝,外面深拷贝,err}};void test_vector9(){vector<vector<int>> vv = Solution().generate(10);for (size_t i = 0; i < vv.size(); ++i){for (size_t j = 0; j < vv[i].size(); ++j){if (j == 0){for (size_t m = vv.size() - vv[i].size(); m > 0; m--){std::cout << " ";}}std::cout << vv[i][j] << " ";}std::cout << std::endl;}}

允许以上代码便会报错:

问题分析:

1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中

2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。

 

因此:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

🐔4.更新reserve函数

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

😊4.其他函数的实现

🐔1.resize函数

		void resize(size_t n,const T& val = T()){if (n > capacity()){reserve(n);}if (n > size()){while (_finish < _start+n){*_finish = val;++_finish;}}else{_finish = _start + n;}}

测试案例:

	void test_vector8(){vector<int> v1;v1.resize(10, 0);for (auto& e : v1){std::cout << e << " ";}std::cout << std::endl;std::cout << v1.capacity() << std::endl;vector<int> v2;v2.reserve(10);v2.push_back(1);v2.push_back(2);v2.push_back(3);v2.push_back(4);v2.push_back(5);v2.resize(8,8);for (auto& e : v2){std::cout << e << " ";}std::cout << std::endl;std::cout <<v2.capacity()<< std::endl;vector<int> v3;v3.reserve(10);v3.push_back(1);v3.push_back(2);v3.push_back(3);v3.push_back(4);v3.push_back(5);v3.resize(3);for (auto& e : v3){std::cout << e << " ";}std::cout << std::endl;std::cout << v3.capacity() << std::endl;}

😊5.完整代码

	template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){}template<class InputIterator>vector(InputIterator first, InputIterator last): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr){while (first != last){push_back(*first);++first;}}vector(int n, const T& val = T()): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr){while (n){push_back(val);n--;}}vector(size_t n, const T& val = T()): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr){while (n){push_back(val);n--;}}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage,v._end_of_storage);}vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){vector<T> tmp(v.begin(), v.end());swap(tmp);}~vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}vector<T>& operator=(vector<T> v){swap(v);return *this;}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}T& operator[](size_t pos){assert(pos < size());return _start[pos];}size_t capacity() const{return _end_of_storage - _start;}size_t size() const{return _finish - _start;}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];}delete[] _start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}}void resize(size_t n,const T& val = T()){if (n > capacity()){reserve(n);}if (n > size()){while (_finish < _start+n){*_finish = val;++_finish;}}else{_finish = _start + n;}}void push_back(const T& x){insert(end(), x);}void pop_back(){assert(_finish > _start);--_finish;}iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_end_of_storage == _finish){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;}iterator erase(iterator pos){assert(pos >= _start && pos < _finish);iterator begin = pos + 1;while (begin <_finish){*(begin-1) = *begin;++begin;}--_finish;return pos;}T& front(){assert(size() > 0);return _start[0];}T& back(){assert(size() > 0);return *(_finish - 1);}private:iterator _start;iterator _finish;iterator _end_of_storage;};