C++ vector模拟实现
本文总结了C++ vector模拟实现中的常见错误及解决方案,主要包括:1. 扩容时指针失效问题:在重新分配内存前应保存相对位置;2. const迭代器问题:需提供const版本的begin/end;3. 迭代器失效问题:insert/erase操作后需更新迭代器;4. 深浅拷贝问题:memcpy会导致浅拷贝,应改用深拷贝;5. 构造函数冲突:需精确匹配参数类型;6. 模板参数类型问题:使用typ
错误警示1
当
_start被重新赋值后,size()函数计算_finish - _start时,由于_finish还是指向旧内存的指针,而_start已经指向新内存。也就是说也就是说程序运行到_finish = _start + size();时相当于_finish = _start + _finish - _start ,_finish还是_finish,也就是nullptr从而导致空指针解引用,引发程序崩溃。
解决方案1:先更新_finish(不推荐,存在顺序问题)
解决方案2:在更新_start之前提前算好size的值(oldsize)(推荐)
错误警示2
const对象不能调用非const成员函数,也就是v(const)不能调用iterator begin()(return _start;)而且这里的范围for也不能正常遍历,因为范围for如果是const对象,也会转换成const迭代器,这里还要注意一下,范围for需要
begin() const/end() const。而不是cbegin()/cend()
解决方案:提供const版本的begin和end
拓展:
我们在提供了const版本的begin和end后,PrintVector正常了,但是如果我们实例化出来一个存储double类型的vector之后之前的写法就不支持了,因为我们形参位置给的是const vector<int>& v,而我们需要的是const vector<double>& v,那要不要在写一个double类型的呢?其实不用,我们可以将PrintVector改造成函数模板
template<class T> void PrintVector(const vector<T>& v) { //vector<T>::const_iterator it = v.begin();//编译报错 typename vector<T>::const_iterator it = v.begin();//ok //auto it=v.begin();//当然也可以使用auto ok while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; for (auto e : v) { cout << e << " "; } cout << endl; }但是这里就编译报错,这是因为我们在使用域作用限定符::取类里面的类型或者静态成员变量,这个类是已经存在的,而上面这种编译器从上往下编译,编译到vector<T>::const_iterator it = v.begin();编译器不认识vector<T>,因为类模板有一个原则就是在类模板没有实例化时,不能取类模板里面的类型或者静态成员变量,因为编译器区分不了const_iterator是类型还是静态成员变量,所以为了区分,要在前面加typename,也就是类型而不是静态成员变量
C++标准规定:
根据C++标准,在模板中对于依赖名称(依赖于模板参数的名称),如果要引用嵌套类型,必须使用
typename关键字。
错误警示3
迭代器失效一(类似野指针):我们在实现insert的时候容易引起迭代器失效,我们发现上面这段代码只要进行了扩容操作就会有问题,这里是因为扩容时,会新开一个tmp(局部变量)数组,拷贝完数据后会将原空间释放,接着将迭代器更新,但是这里的pos依然指向原空间的某个位置,从而导致移动数据的逻辑错乱。引发一系列的问题。
void insert(iterator pos, const T& x) { //扩容 if (_finish == _endofstorage) { reserve(capacity() == 0 ? 4 : capacity() * 2); } iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; }
解决方案:在扩容之前记录一个相对位置
void insert(iterator pos, const T& x) { //扩容 if (_finish == _endofstorage) { size_t len = pos - _start; reserve(capacity() == 0 ? 4 : capacity() * 2); //更新pos pos = _start + len; } iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; }
迭代器失效二(位置意义变化):
在
v1.insert(pos, 66);之后,pos已经失效,虽然有更新pos的机制(可能指向无效内存或旧位置)。后续尝试通过(*pos) *= 10;修改pos指向的值是未定义行为(可能导致崩溃或错误结果)。
解决方案:使用返回值更新迭代器:
iterator insert(iterator pos, const T& x) { //扩容 if (_finish == _endofstorage) { size_t len = pos - _start; reserve(capacity() == 0 ? 4 : capacity() * 2); //更新pos pos = _start + len; } iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; return pos; } auto pos = find(v1.begin(), v1.end(), x); if (pos != v1.end()) { pos = v1.insert(pos, 66); // 用返回值更新pos // 现在pos指向新插入的66,原来的x在pos+1位置 *(pos + 1) *= 10; // 安全操作 }
错误警示4
我们在实现erase的时候,依然会有迭代器失效的问题在测试下面程序
- 会出现assert失败:这是因为当最后一个数据是偶数时,当最后一次挪动数据++it,同时--_finish,于是就出现Assertion failed:pos<_finish
- 有时偶数没删除干净:这是因为当出现连续偶数时,会将下一个偶数往前挪动,此时it指向下一个偶数,接着就执行it++结果就把下一个偶数跳过了
void test_vector4() { vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); v1.push_back(4); v1.push_back(5); PrintContainer(v1); //删除所有的偶数 auto it = v1.begin(); while (it!=v1.end()) { if (*it % 2 == 0) v1.erase(it); it++; } PrintContainer(v1); }解决方案:修正erase函数和删除的循环逻辑:
iterator erase(iterator pos) { assert(pos >= _start); assert(pos < _finish); iterator it = pos + 1; while (it != end()) { *(it - 1) = *it; it++; } --_finish; return pos; // 返回被删除元素的位置(现在指向下一个元素) } //正确的循环逻辑 auto it = v1.begin(); while (it != v1.end()) { if (*it % 2 == 0) { it = v1.erase(it); // 用返回值更新it,(间接进行了it++) } else { ++it; // 只有不是偶数时才移动 } }
错误警示5
我们在实现resize的时候,缺省值处会给对象的默认构造,而不是一个具体的值,这是因为当模板类型
T不是数值类型时,0可能无法转换为T类型。比如如果T是std::string,0无法转换为字符串。这里在注意一下C+++标准规定:绑定到const引用的临时对象生命周期会延长,也就是说匿名对象的生命周期延长到引用val的作用域。//void resize(size_t n,const T&val=0))错误 //void resize(size_t n, T val = T())//用默认构造构造匿名对象,在调用拷贝构造 void resize(size_t n,const T&val=T())//用const引用去引用匿名对象 {/*...*/}T如果是:
- 自定义类型-------->去调用该类型的默认构造函数
- 内置类型----------->也支持int i=();int j=int(1);int k(2);
数组类型----------->每个元素进行值初始化
错误警示6
我们在实现完用n个val构造会与之前实现的迭代器区间构造冲突
vector<int> v(10, 20); // 编译器困惑:这是要构造10个20?还是用迭代器区间[10, 20]?为什么之前的不会出错,其实编译器有一个原则,就是编译器回去匹配类型最匹配的,前面两个不会匹配到迭代器是因为类型不匹配
解决方案1:指定访问vector<int> v6(10u,1);(加u就是unsigned int)
解决方案2:提供更匹配的函数(推荐)(之前说过,非模板函数可以与同名函数模板同时存在“有现成用现成的”)
vector(int n, const T& val = T()) { reserve(n); for (int i = 0; i < n; i++) { push_back(val); } }
错误警示7
memcpy的浅拷贝问题:
测试以下程序,我们发现,程序崩溃了,并且打了一堆乱码。打上断点,调试发现当程序在扩容时,临时对象tmp里面的值在运行delete[] _start;之前是正常的,一执行这一段代码tmp里面的值就变成了随机值(乱码),这里的核心问题就是memcpy的浅拷贝问题。
memcpy是浅拷贝,于是将原空间的每个指针(_str)拷贝给tmp的每个_str于是新的空间指针指向了原空间,执行delete[] _start,就会先调用析构函数将每块空间所指向的堆资源清理了,接着调用operator delete[],去把v的空间释放,于是新空间每一个_str就类似于野指针,也就打印了乱码。其实不只是string类有这样的问题vector<T>,只要T是内置类型就正常,T是深拷贝类型就会出现问题如vector<vector<int>>
解决方案:使用深拷贝调用赋值重载
void reserve(size_t n) { size_t oldsize = size(); if (n > capacity()) { T* tmp = new T[n]; //memcpy(tmp, _start, sizeof(T) * size()); for (size_t i=0;i<oldsize;i++) { tmp[i] = _start[i]; } delete[] _start; //更新迭代器 _start = tmp; _finish = tmp + oldsize; _endofstorage = tmp + n; } }这里本质就是调用string的赋值(两个已经存在的对象)
vector的拷贝问题
1.拷贝构造
前面说过,当我们没有显示实现拷贝构造函数时,编译器默认生成的是浅拷贝的拷贝构造函数,在显示实现析构函数时,就会析构同一块空间,引发程序崩溃。所以当我们要实现vector<int> v2 = v1;(区分赋值重载),就要自己实现拷贝构造函数。这里特别注意一下,当我们没有显示写任何构造函数,编译器会默认生成,但是当我们只要写了任意构造函数,编译器就不会生成,而拷贝构造也是构造函数,因此当我们只显示写拷贝构造函数,就不会生成默认构造函数了。
//vector()//走初始化列表 //{} //C++11的用法,强制生成默认构造函数 vector() = default; vector(const vector<T>& v) { reserve(v.size());//减少扩容 for (auto& e : v)//传引用减少拷贝 { push_back(e); } }2.赋值重载
void clear(){_finish = _start;} //v1=v3传统写法 //vector<T>& operator=(const vector<T>& v) //{ // if (this != &v) // { // clear(); // reserve(v.size()); // for (auto& e : v) // { // push_back(e); // } // } // return *this; //} void swap(vector<T>& v) { std::swap(_start,v._start); std::swap(_finish, v._finish); std::swap(_endofstorage, v._endofstorage); } //vector& operator=(vector v)//可以在类里面使用类名来替代类型 vector<T>& operator=(vector<T> v)//现代写法注意传值 { swap(v); return *this; }v1=v3,v3就传给v,v1就传给*this,v3是传值传参调用之前的深拷贝构造函数,于是v就有与v3相同的数据,于是将这块数据利用swap函数与this交换,新空间给了v1(达成目的),旧空间(v1原本的空间)就给了v,v是局部对象,出作用域就调用析构函数释放了。
这里提一个特殊用法:在类作用域内,可以直接使用类名,不需要模板参数。但是我们为了提高代码的可读性,还是建议将模板参数带上
vector迭代器区间构造
在模拟实现vector的迭代器区间构造时,要了解一个概念:类模板的成员函数可以是类模板
//迭代器区间构造 template<class InputIterator> vector(InputIterator first, InputIterator last) { while (first != last)//不建议用<,当空间不连续时就不支持 { push_back(*first); first++; } }这里不使用之前的iterator作为迭代器类型是因为如果使用了之前的iterator迭代器,就只能是vector的迭代器了,就只支持vector的区间构造,后续如果向将一个存储与list数据取一段区间构造出一个vector对象就不兼容了(前提类型匹配)。这里要注意,
<操作需要随机访问迭代器,而!=只需要输入迭代器。vector提供随机访问迭代器,list只提供双向迭代器,因此通用代码应该使用!=来保证兼容性。
vector模拟实现
Vector.h
#include<iostream> #include<vector> #include<assert.h> #include<list> #include<string> using namespace std; namespace name { template<class T> class vector { public: typedef T* iterator; typedef const T* const_iterator; //vector()//走初始化列表 //{} //C++11的用法,强制生成默认构造函数 vector() = default; vector(const vector<T>& v) { reserve(v.size());//减少扩容 for (auto& e : v)//传引用减少拷贝 { push_back(e); } } //类模板的成员函数,还可以继续是函数模板 //为什么不能直接使用iterator,而是有套了一层函数模板? //这是因为如果使用iterator,那么这个迭代器就只能是vector的迭代器,写成模板可以支持任意类型的迭代器 //迭代器区间构造 template<class InputIterator> vector(InputIterator first, InputIterator last) { while (first != last)//不建议用<,当空间不连续时就不支持 { push_back(*first); first++; } } vector(size_t n,const T&val=T()) { reserve(n); for (size_t i = 0; i < n; i++) { push_back(val); } } vector(int n, const T& val = T()) { reserve(n); for (int i = 0; i < n; i++) { push_back(val); } } void clear() { _finish = _start; } //v1=v3 //vector<T>& operator=(const vector<T>& v) //{ // if (this != &v) // { // clear(); // reserve(v.size()); // for (auto& e : v) // { // push_back(e); // } // } // return *this; //} void swap(vector<T>& v) { std::swap(_start,v._start); std::swap(_finish, v._finish); std::swap(_endofstorage, v._endofstorage); } //vector& operator=(vector v)//可以在类里面使用类名来替代类型 vector<T>& operator=(vector<T> v)//现代写法注意传值 { swap(v); return *this; } ~vector() { if (_start != nullptr) { delete[] _start; _start = _finish = _endofstorage = nullptr; } } iterator begin() { return _start; } iterator end() { return _finish; } const_iterator begin() const { return _start; } const_iterator end() const { return _finish; } void reserve(size_t n) { size_t oldsize = size(); if (n > capacity()) { T* tmp = new T[n]; //memcpy(tmp, _start, sizeof(T) * size()); for (size_t i=0;i<oldsize;i++) { tmp[i] = _start[i]; } delete[] _start; //更新迭代器 _start = tmp; _finish = tmp + oldsize; _endofstorage = tmp + n; } } //void resize(size_t n, T val = T())//用默认构造构造匿名对象,在调用拷贝构造 void resize(size_t n,const T&val=T())//用const引用去引用匿名对象 { //n<size size<n<capacity n>capacity if (n < size()) { _finish = _start + n; } else//插入数据 { reserve(n); while (_finish < _start + n) { *_finish = val; ++_finish; } } } size_t size() const { return _finish - _start; } size_t capacity() const { return _endofstorage - _start; } bool empty() { return _start == _finish; } void push_back(const T& x) { if (_finish == _endofstorage) { //扩容 reserve(capacity() == 0 ? 4 : capacity() * 2); } *_finish = x; ++_finish; } void pop_back() { assert(!empty()); --_finish; } T& operator[](size_t n) { assert(n<size()); return _start[n]; } const T& operator[](size_t n)const { assert(n < size()); return _start[n]; } iterator insert(iterator pos, const T& x) { assert(pos>=_start); assert(pos <= _finish); //扩容 if (_finish == _endofstorage) { size_t len = pos - _start; reserve(capacity() == 0 ? 4 : capacity() * 2); //更新pos 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); assert(pos < _finish); iterator it = pos + 1; while (it != end()) { *(it - 1) = *it; it++; } --_finish; return pos; // 返回被删除元素的位置(现在指向下一个元素) } private: iterator _start = nullptr; iterator _finish = nullptr; iterator _endofstorage = nullptr; }; //void PrintVector(const vector<int>& v) //{ // vector<int>::const_iterator it = v.begin(); // while (it != v.end()) // { // cout << *it << " "; // ++it; // } // cout << endl; // for (auto e : v) // { // cout << e << " "; // } // cout << endl; //} template<class T> void PrintVector(const vector<T>& v)//适合vector容器 { //vector<T>::const_iterator it = v.begin();//编译报错 typename vector<T>::const_iterator it = v.begin();//ok //auto it=v.begin();//当然也可以使用auto ok /*while (it != v.end()) { cout << *it << " "; ++it; } cout << endl;*/ for (auto e : v) { cout << e << " "; } cout << endl; } template<class Container>//适合任意容器 void PrintContainer(const Container& v) { auto it = v.begin(); while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; } }
Test.cpp
#include"Vector.h" namespace name { void test_vector1() { vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(5);//二次扩容 for (int i = 0; i < v.size(); ++i) { cout << v[i] << " "; } cout << endl; vector<int>::iterator it = v.begin(); while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; for (auto e : v) { cout << e << " "; } cout << endl; cout << "////////////" << endl; PrintVector(v); } void test_vector2() { vector<double> v1; v1.push_back(1.1); v1.push_back(2.2); v1.push_back(3.3); v1.push_back(4.4); v1.push_back(5.5); PrintVector(v1); } void test_vector3() { vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); v1.push_back(4); v1.insert(v1.begin() + 2, 8); PrintVector(v1); //配合查找插入使用 int x; cin >> x; //c++所有的算法传的迭代器区间都是左闭右开的 //find没有找到就返回最后一个位置 //迭代器失效二(迭代器insert后就失效了,不要访问) /*auto pos = find(v1.begin(),v1.end(),x); if (pos != v1.end()) { v1.insert(pos,66); (*pos) *= 10; }*/ auto pos = find(v1.begin(), v1.end(), x); if (pos != v1.end()) { pos = v1.insert(pos, 66); // 用返回值更新pos // 现在pos指向新插入的66,原来的x在pos+1位置 *(pos + 1) *= 10; // 安全操作 } PrintVector(v1); //PrintContainer(v1); } void test_vector4() { vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); v1.push_back(4); v1.push_back(5); v1.push_back(4); v1.push_back(4); PrintContainer(v1); auto it = v1.begin(); while (it != v1.end()) { if (*it % 2 == 0) { it = v1.erase(it); // 用返回值更新it,不执行it++ } else { ++it; // 只有不是偶数时才移动 } } PrintContainer(v1); } void test_vector5() { int i = int(); int j = int(2); int k(2); vector<int> v; v.resize(10, 1); v.reserve(20); PrintVector(v); cout << v.size() << endl; cout << v.capacity() << endl; v.resize(15, 2); PrintVector(v); v.resize(25, 3); PrintVector(v); v.resize(5); PrintVector(v); } void test_vector6() { /*std::vector<int> v1;*/ vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); PrintContainer(v1); /*std::vector<int> v2 = v1;*/ vector<int> v2 = v1; PrintContainer(v2); vector<int> v3; v3.push_back(10); v3.push_back(20); v3.push_back(30); v3.push_back(40); v1 = v3; PrintContainer(v1); PrintContainer(v3); } void test_vector7() { vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); v1.push_back(5); v1.push_back(6); PrintContainer(v1); vector<int> v2(v1.begin(), v1.end() - 3); PrintContainer(v2); cout << "*****list******" << endl; list<int> lt; lt.push_back(1); lt.push_back(2); lt.push_back(3); lt.push_back(4); vector<int>v3(lt.begin(), lt.end());//也支持使用不同容器的iterator,但要求数据类型匹配 PrintContainer(lt); PrintContainer(v3); cout << "*****string******" << endl; vector<string> v4(10, "string"); PrintContainer(v4); vector<int> v5(10); PrintContainer(v5); vector<int> v6(10, 1); PrintContainer(v6);//选择最匹配的 /*vector<int> v6(10u,1); PrintContainer(v6);*/ } void test_vector8() { vector<string> v; v.push_back("11111111111111111"); v.push_back("11111111111111111"); v.push_back("11111111111111111"); v.push_back("11111111111111111"); PrintContainer(v); v.push_back("11111111111111111");//扩容出现问题 PrintContainer(v); } } int main() { //name::test_vector1(); //name::test_vector2(); //name::test_vector3(); //name::test_vector4(); //name::test_vector5(); //name::test_vector6(); //name::test_vector7(); name::test_vector8(); return 0; }
更多推荐

















所有评论(0)