✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:C++之STL
🔥<3>创作者:我的代码爱吃辣
☂️<4>开发环境:Visual Studio 2022
💬<5>前言:上次我们已经数字会用了vector,这次我们对其底层更深一步挖掘,其中重点是,Vector中一些深浅拷贝问题。
目录
一.Vector模拟实现的整体框架
我们先认识一下Vector的整体模拟实现框架,Vector在功能上就是我们数据结构阶段实现的顺序表基本一致,但是Vector在成员框架上与顺序表有所不同,且Vector使用类和对象封装支持模板泛型。
template<class T> class Vector { public: //迭代器类型 typedef T* iterator; typedef const T* const_iterator; //... private: iterator _start;//数据存储首地址 iterator _finish;//有效数据尾部地址下一个地址。 iterator _end_of_storage;//容量尾地址下一个地址 };
Vector的迭代器是对顺序表原生类型指针的封装。
二. Vector的构造与析构
Vector主要是对成员变量初始化:
Vector() :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { }
析构主要释放我们申请的空间:
~Vector() { delete[] _start; _start = _finish = _end_of_storage = nullptr; }
三.size(),capacity()
size()返回当前顺序表存储的数据个数,capacity()返回当前顺序表的容量。
size_t size() const { return _finish - _start; } size_t capacity() const { return _end_of_storage - _start; }
编辑
四.reserve(),resize()
1.reserve()
设置Vector的容量,注意容量支持增加,但是不支持减小。
void reserve(size_t capa) { //仅支持容量扩大,不支持容量减小 if (capacity() < capa) { size_t sz = size(); iterator tmp = new T[capa]; //分清当前的是否已经有了容量,如果已经有了容量需要释放之前的容量, //如果之前没有容量仅需,将新开的空间指向我们的_start. if (_start) { memcpy(tmp, _start, sizeof(T) * capacity()); /*error !!*/ delete[] _start; } //注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start, //然后计算的size也并非是,准确的size。 _start = tmp; _finish = tmp + sz; _end_of_storage = _start + capa; } }
注意:
此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,然后计算的size也并非是,准确的size。除此之外这份代码依旧是有问题的。我们后面解释。
错误代码如下:
void reserve(size_t capa) { if (capacity() < capa) { iterator tmp = new T[capa]; if (_start) { memcpy(tmp, _start, sizeof(T) * capacity()); delete[] _start; } //注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start, //然后计算的size也并非是,准确的size。 _start = tmp; _finish = tmp + size(); _end_of_storage = _start + capa; } }
编辑
2.resize
提供改变存储数据的个数的能力。如果 n < size 时就是删除数据,n > size且空间不够时需要扩容+初始化,空间足够,仅需要初始化剩下的空间。
void resize(size_t n, T val = T()) { //1.n < size;-->删除数据 if (n < size()) { _finish = _start + n; } //2.n > size else { //(1)如果空间不足,需要扩容+初始化 if (n >= capacity()) { reserve(n); } //(2)空间足够,仅需要初始化剩下的空间 while (_finish != _start + n) { *(_finish) = val; _finish++; } } }
五.push_back(),pop_back()
1.push_back()
从尾部插入一个数据。
void push_back(const T& val) { //检查是否需要扩容 if (_finish == _end_of_storage) { capacity() == 0 ? reserve(5) : reserve(capacity() * 2); } //插入数据 *(_finish) = val; _finish++; }
2. pop_back()
bool empty() const { return size() == 0; } void pop_back() { //判空 assert(!empty()); //我们仅需将维护尾部数据的指针向前挪一位。 _finish--; }
六.Vector的迭代器
typedef T* iterator; typedef const T* const_iterator;
Vector底层就是顺序存储的结构,所以可以使用原生指针作为迭代器。
//普通迭代器 iterator begin() { return _start; } iterator end() { return _finish; } //const 迭代器 const_iterator begin()const { return _start; } const_iterator end()const { return _finish; }
有了迭代器就可以支持迭代器访问,和范围for。
int main() { Vector<int> v1; v1.push_back(100); v1.push_back(200); v1.push_back(300); v1.push_back(400); v1.push_back(500); v1.push_back(600); v1.push_back(700); Vector<int>::iterator it = v1.begin(); while (it != v1.end()) { cout << *it << " "; it++; } cout << endl; for (auto e : v1) { cout << e << " "; } return 0; }
编辑
七.operator [ ]
//穿引用返回 T& operator[](size_t pos) { //判断位置的合法性 assert(pos < size()); return _start[pos]; } const T& operator[](size_t pos) const { assert(pos < size()); return _start[pos]; }
编辑
八.insert(),erase()
1.迭代器失效
在模拟实现之前我们先看一下什么是迭代器失效问题:
迭代器失效问题通常发生在容器类的成员函数中,例如erase和insert
。在这些函数中,迭代器被重置或修改,导致原始迭代器不再指向容器的正确位置,从而导致迭代器失效。
int main() { vector<int> v1; v1.push_back(100); v1.push_back(200); v1.push_back(300); v1.push_back(400); v1.push_back(500); v1.push_back(600); v1.push_back(700); vector<int>::iterator pos = find(v1.begin(), v1.end(),200); //对pos位置插入 v1.insert(pos, 150); //pos已经失效 v1.insert(pos, 170); return 0; }
编辑
原理图:
情况一:
编辑
上述代码中的迭代器失效问题也是属于这种情况。
情况二:
编辑
2.insert()
iterator insert(iterator pos,T val) { assert(pos >= _start); assert(pos < _finish); //迭代器失效问题,记录pos的相对位置 int len = pos - _start; if (_finish == _end_of_storage) { capacity() == 0 ? reserve(5) : reserve(capacity() * 2); } //扩容后重新计算pos,没有发生扩容pos不变 pos = _start + len; iterator end = _finish; //数据挪动 while (end >= pos) { (*end) = *(end - 1); end--; } _finish++; (*pos) = val; return pos; }
在使用pos时要注意扩容会使得pos失效,需要重新计算pos位置。
3.erase()
iterator erase(iterator pos) { //判断位置是否合法 assert(pos >= _start); assert(pos < _finish); iterator end = pos ; /挪动数据删除 while (end < _finish) { *end = *(end + 1); end++; } _finish--; return pos; }
九.再看Vector构造函数
std中的vector还支持使用指定个数和初始化值初始化,和迭代器区间初始化。这两个功能在我们平时也是能用到的。
//1.Vector<T> v(5,10);创建一个Vector并且初始化前5个值为10 Vector(size_t n, const T& val = T()) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { reserve(n); for (int i = 0; i < n; i++) { push_back(val); } } //2.迭代器初始化,[frist,lest) template<class InputIterator> Vector(InputIterator frist, InputIterator lest) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { reserve(lest - frist); while (frist != lest) { push_back(*frist); frist++; } } //3.防止构造函数调用错误 Vector(int n, const T& val = T()) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { reserve(n); for (int i = 0; i < n; i++) { push_back(val); } }
第三个构造函数的作用是防止构造调用错误冲突,在我们进行如下的调用时:
Vector<int> v2(5, 10);
编译器会以为我们在调用迭代器区间初始化构造函数,因为经过模板的推导,只有迭代器区间初始化构造函数,更适合这个调用。然后将一个整形当作地址在迭代器区间初始化构造函数里面解引用了,报错是:非法的间接寻址。
编辑
正常调用结果:编辑
十.拷贝构造
今天这里编译器默认生成的拷贝构造显然是不能用了。
1.深浅拷贝
万万不可以直接使用拷贝函数按二进制或者按字节直接拷贝了。
错误代码1:
Vector(const Vector<T>& v) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { reserve(v.capacity()); //万万不可以直接按二进制拷贝 memcpy(_start, v._start, sizeof(T) * v.capacity()); /*error!!!!*/ _finish = _start + v.size(); _end_of_storage = _start + v.capacity(); }
原因:
调用处代码:
int main() { string str("abcdefg"); Vector<string> v2(5,str); Vector<string> v3(v2); return 0; }
编辑
会使得我们同一块空间被delete两次从而引发内存错误。
2.正确的拷贝构造代码:
Vector(const Vector<T>& v) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { reserve(v.capacity()); //这里我们将数据一个一个push进去,这样我们借助push_back底层插入的时候, //会使用string的赋值构造,完成深拷贝。 for (int i = 0; i < v.size(); i++) { push_back(v[i]); } } //现代写法 Vector(const Vector<T>& v) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { Vector<T> tmp(v.begin(), v.end()); swap(tmp); }
错误代码2:reserve()
void reserve(size_t capa) { //仅支持容量扩大,不支持容量减小 if (capacity() < capa) { size_t sz = size(); iterator tmp = new T[capa]; //分清当前的是否已经有了容量,如果已经有了容量需要释放之前的容量, //如果之前没有容量仅需,将新开的空间指向我们的_start. if (_start) { //这里千万不能按二进制直接拷贝. memcpy(tmp, _start, sizeof(T) * capacity()); /*error !!*/ delete[] _start; } //注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start, //然后计算的size也并非是,准确的size。 _start = tmp; _finish = tmp + sz; _end_of_storage = _start + capa; } }
这里我们仍然是使用了memcpy。
调用处代码:
int main() { string str("abcdefg"); Vector<string> v2; for (int i = 0; i < 6; i++) { v2.push_back(str); } return 0; }
编辑
3.正确的 reserve()
void reserve(size_t capa) { //仅支持容量扩大,不支持容量减小 if (capacity() < capa) { size_t sz = size(); iterator tmp = new T[capa]; //分清当前的是否已经有了容量,如果已经有了容量需要释放之前的容量, //如果之前没有容量仅需,将新开的空间指向我们的_start. if (_start) { //这里千万不能按二进制直接拷贝. //memcpy(tmp, _start, sizeof(T) * capacity()); /*ror !!*/ for (int i = 0; i < size(); i++) { //=内置类型直接赋值,自定义类型使用赋值构造 tmp[i]=_start[i]; } delete[] _start; } //注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start, //然后计算的size也并非是,准确的size。 _start = tmp; _finish = tmp + sz; _end_of_storage = _start + capa; } }
这里有一个细节就是在reserve和拷贝构造的拷贝数据的时候我们都是使用了赋值。问题我们并没有重载赋值运算符,编译器自动生成,简单来说就是这里又会是一个浅拷贝。
4.赋值运算符重载
//传统写法 Vector<T>& operator=(const Vector<T>& v) { T* tmp = new T[v.capacity()]; if (_start) { for (int i = 0; i < v.size(); i++) { tmp[i] = _start[i]; } delete[] _start; } _start = tmp; _finish = _start + v.size(); _end_of_storage = _start + v.capacity(); return *this; } //现代写法 void swap(Vector<T>& v ) { std::swap(v._start, _start); std::swap(v._finish, _finish); std::swap(v._end_of_storage, _end_of_storage); } Vector<T>& operator=(Vector<T> v) { swap(v); return *this; }
现代写法利用,拷贝构造拷贝出来的对象,然后交换对象的成员。
十一.总体代码
#pragma once #include<iostream> #include<algorithm> #include<string> #include<cstring> #include<cassert> using namespace std; template<class T> class Vector { public: typedef T* iterator; typedef const T* const_iterator; Vector() :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { } //1.Vector<T> v(5,10);创建一个Vector并且初始化前5个值为10 Vector(size_t n, const T& val = T()) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { reserve(n); for (int i = 0; i < n; i++) { push_back(val); } } //2.迭代器初始化,[frist,lest) template<class InputIterator> Vector(InputIterator frist, InputIterator lest) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { reserve(lest - frist); while (frist != lest) { push_back(*frist); frist++; } } //3.防止构造函数调用冲突 Vector(int n, const T& val = T()) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { reserve(n); for (int i = 0; i < n; i++) { push_back(val); } } //传统写法 拷贝构造 //Vector(const Vector<T>& v) // :_start(nullptr), // _finish(nullptr), // _end_of_storage(nullptr) //{ // reserve(v.capacity()); // //这里我们将数据一个一个push进去,这样我们借助push_back底层插入的时候, // //会使用string的赋值构造,完成深拷贝。 // for (int i = 0; i < v.size(); i++) // { // _start[i] = v[i]; // } //} //现代写法,拷贝构造 Vector(const Vector<T>& v) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { Vector<T> tmp(v.begin(), v.end()); swap(tmp); } //传统写法,赋值拷贝 //Vector<T>& operator=(const Vector<T>& v) //{ // T* tmp = new T[v.capacity()]; // if (_start) // { // for (int i = 0; i < v.size(); i++) // { // tmp[i] = _start[i]; // } // delete[] _start; // } // _start = tmp; // _finish = _start + v.size(); // _end_of_storage = _start + v.capacity(); // // return *this; //} void swap(Vector<T>& v ) { std::swap(v._start, _start); std::swap(v._finish, _finish); std::swap(v._end_of_storage, _end_of_storage); } //现代写法,赋值拷贝 Vector<T>& operator=(Vector<T> v) { swap(v); return *this; } size_t size() const { return _finish - _start; } size_t capacity() const { return _end_of_storage - _start; } void reserve(size_t capa) { //仅支持容量扩大,不支持容量减小 if (capacity() < capa) { size_t sz = size(); iterator tmp = new T[capa]; //分清当前的是否已经有了容量,如果已经有了容量需要释放之前的容量, //如果之前没有容量仅需,将新开的空间指向我们的_start. if (_start) { //这里千万不能按二进制直接拷贝. //memcpy(tmp, _start, sizeof(T) * capacity()); /*ror !!*/ for (int i = 0; i < size(); i++) { //=内置类型直接赋值,自定义类型使用赋值构造 tmp[i]=_start[i]; } delete[] _start; } //注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start, //然后计算的size也并非是,准确的size。 _start = tmp; _finish = tmp + sz; _end_of_storage = _start + capa; } } void resize(size_t n, T val = T()) { //1.n < size;-->删除数据 if (n < size()) { _finish = _start + n; } //2.n > size else { //(1)如果空间不足,需要扩容+初始化 if (n >= capacity()) { reserve(n); } //(2)空间足够,仅需要初始化剩下的空间 while (_finish != _start + n) { *(_finish) = val; _finish++; } } } //穿引用返回 T& operator[](size_t pos) { //判断位置的合法性 assert(pos < size()); return _start[pos]; } const T& operator[](size_t pos) const { assert(pos < size()); return _start[pos]; } void push_back(const T& val) { if (_finish == _end_of_storage) { capacity() == 0 ? reserve(5) : reserve(capacity() * 2); } //内置类型直接赋值,自定义类型使用赋值构造 *(_finish) = val; _finish++; } bool empty() const { return size() == 0; } void pop_back() { //判空 assert(!empty()); //我们仅需将维护尾部数据的指针向前挪一位。 _finish--; } iterator erase(iterator pos) { assert(pos >= _start); assert(pos < _finish); iterator end = pos ; while (end < _finish) { *end = *(end + 1); end++; } _finish--; return pos; } iterator insert(iterator pos,T val) { assert(pos >= _start); assert(pos < _finish); //迭代器失效问题,记录pos的相对位置 int len = pos - _start; if (_finish == _end_of_storage) { capacity() == 0 ? reserve(5) : reserve(capacity() * 2); } //扩容后重新计算pos,没有发生扩容pos不变 pos = _start + len; iterator end = _finish; //数据挪动 while (end >= pos) { (*end) = *(end - 1); end--; } _finish++; (*pos) = val; return pos; } //普通迭代器 iterator begin() { return _start; } iterator end() { return _finish; } //const 迭代器 const_iterator begin()const { return _start; } const_iterator end()const { return _finish; } ~Vector() { delete[] _start; _start = _finish = _end_of_storage = nullptr; } private: iterator _start;//数据存储首地址 iterator _finish;//有效数据尾部地址下一个地址。 iterator _end_of_storage;//容量尾地址下一个地址 int tmp; };
编辑