C++学习之STL学习:vector的模拟实现
上节课我们已经接触并学习了vector的使用。接下来我们将通过模拟实现的方式深入学习vector的底层,更好的了解学习vector
vector模拟实现前置准备
vector.h:将类、函数的声明与定义放在一起
test.cpp:函数的测试
为什么vectror.h要将类和函数的声明与定义放在一起而不是再创建一个.cpp文件呢?
因为本篇中vector的模拟实现要用到模板,而模板的声明与定义不能放在两个文件里,否则就会编译链接错误(原因后面深入模板学习进行讲解)
首先抓类的成员变量,然后看构造的初始化,再看核心的成员函数(项目中有很多细节的成员函数)
命名空间的设置
为了解决与库中vector 使用冲突的问题,我们需要一个命名空间将其封装起来
namespace Boogiepop
{}
vector的构造
vector 的模拟实现中,我们需要用到模板
template<class T>
而用到模板的情况下,声明与定义是不能分离的
成员变量
private:iterator _start;//第一个元素的指针iterator _finish;//最后一个元素的指针iterator _end_of_storage;//分配的内存的最后一个元素的指针
构造
namespace Boogiepop
{//声明与定义不能分别定义在两个文件template<class T>class vector{public:typedef T* iterator;vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){}private:iterator _start;//第一个元素的指针iterator _finish;//最后一个元素的指针iterator _end_of_storage;//分配的内存的最后一个元素的指针};
}
拷贝构造
//拷贝构造函数
vector(const vector<T>&v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{reserve(v.capacity());for (auto e : v){push_back(e);}
}
析构
//析构函数
~vector()
{if (_start!= nullptr){delete[]_start;_start = nullptr;_finish = nullptr;_end_of_storage = nullptr;}
}
强制编辑器构造
//强制编辑器生成构造
vector()=defalut;
多个值初始化
花括号对多个数值初始化
//列表初始化构造
vector(initializer_list<T> il)
{reserve(il.size());for (auto& e : il){push_back(e);}
}
函数模板初始化
//函数模板初始化
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{reserve(last - first);for (InputIterator it = first; it!= last; ++it){push_back(*it);}
}
vector的成员函数
尾插数据
尾插数据的时候需要考虑内存分配的空间已经满了。因此我们需要判断是否已经满了并进行扩容
在此之前我们需要一个更方便的表达方式,类似于string
//返回一个向量有效元素的个数
size_t size() const
{return _finish - _start;
}
//返回一个向量分配内存中元素的个数
size_t capacity() const
{return _end_of_storage - _start;
}
扩容函数
//扩容
void reserve(size_t n)
{size_t old_size = size();if (n>capacity()){T*tmp=new T[n];//拷贝旧空间数据if (_start!= nullptr){memcpy(tmp,_start,sizeof(T)*size());delete[]_start;}_start = tmp;_finish = _start + old_size;_end_of_storage = _start + n;}
}
因此最后尾插数据是这样的
//尾插数据
void push_back(const T& val)
{if (_finish == _end_of_storage)//空间满了,需要扩容{resverse(capacity() == 0? 4 : 2 * capacity());}*_finish = val;++_finish;
}
[]遍历
//[]运算符重载
T&operator[](size_t i)
{assert(i<size());return _start[i];
}
迭代器
typedef T* iterator;
typedef const T* const_iterator;
//迭代器
iterator begin()
{return _start;
}
iterator end()
{return _finish;
}
//const迭代器
//const不是自身不能修改,而是指向的内容不能修改
const_iterator begin() const
{return _start;
}
const_iterator end() const
{return _finish;
}
测试:
void test1()
{Boogiepop::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//范围for本质上就是迭代器for (auto e : v){std::cout<<e<<" ";}std::cout<<std::endl;
}
尾部删除
判定为空函数
//判断为空
bool empty() const
{return _finish == _start;
}
完整版
//判断为空
bool empty() const
{return _finish == _start;
}
//尾部删除
void pop_back()
{/*assert(_finish > _start);*/assert(!empty());--finish;
}
insert插入
//插入
void insert(iterator pos, const T& val)
{assert(pos>=_start && pos<=_finish);if (_finish == _end_of_storage){size_t len =pos-start();reserve(capacity() == 0 ? 4 : 2 * capacity());}//移动后面的元素iterator end = _finish - 1;while (end >= pos){*(end + 1)=*end;--end;}*pos = val;++_finish;
}
以上代码有问题,会出现迭代器失效的问题
插入就会导致扩容,而扩容会导致迭代器失效问题
如下图所示 ,异地扩容的时候,旧有的空间被销毁,导致指向旧空间的指针指向的内容消失,变成了野指针,因此导致了迭代器失效
因此要更新pos的位置
//插入
void insert(iterator pos, const T& val)
{assert(pos>=_start && pos<=_finish);if (_finish == _end_of_storage){size_t len =pos-start();reserve(capacity() == 0 ? 4 : 2 * capacity());//更新pos,pos失效了pos = _start + len;}//移动后面的元素iterator end = _finish - 1;while (end >= pos){*(end + 1)=*end;--end;}*pos = val;++_finish;
}
再举一个例子
void test4()
{Boogiepop::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);print(v);int x;std::cin >> x;auto it = std::find(v.begin(), v.end(), x);if(it!= v.end()){//迭代器失效v.insert(it,x*10);//C++扩容没有具体固定vector的扩容规则//我们认为扩容后it就失效了,所以不能使用it//it不能继续使用,已经失效了*it = 10;//要更新迭代器位置}print(v);
}
结果为:
删除
//删除
void erase(iterator pos)
{assert(pos>=_start && pos<=_finish);iterator it = pos + 1;while (it !=_finish){*(it - 1) = *(it);++it;}--_finish;
}
test.c
void test6()
{std::vector<int> v;//Boogiepop::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);v.push_back(8);print(v);//删除所有的偶数auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){//erase迭代器也会失效//要重新赋新值才可以it=v.erase(it);}else{++it;}}print(v);
}
resize
//修改大小
void resize(size_t n,T val=T ())
{//匿名对象的使用场景if (n>size()){reserve(n);while (_finish!=_start+n){*_finish = val;++_finish;}}else{_finish = _start + n;}
}
迭代器失效
底层就是野指针
C++迭代器失效详解
一、底层原理
迭代器是容器元素的抽象指针,其底层实现与容器内存结构紧密相关:
-
连续内存容器(vector/string/deque)
-
迭代器本质是原始指针(或封装指针的类)
-
内存重新分配时,原内存块被释放,迭代器成为悬垂指针
-
vector<int> v{1,2,3};
int* p = &v[0]; // 原始指针实现
v.push_back(4); // 可能导致内存重分配
// p 可能指向已释放内存
2.节点式容器(list/set/map)
迭代器是节点指针的封装
删除节点时,该节点内存被释放,迭代器指向无效内存
list<int> l{1,2,3};
auto it = l.begin();
l.erase(it); // 节点内存被释放
// it 指向已删除节点
二、失效原因分类
容器类型 | 导致失效的操作 | 失效范围 |
---|---|---|
vector/string | • 插入导致扩容 • 中间插入/删除 | 所有迭代器失效 |
deque | • 首尾插入(可能分块) • 中间插入/删除 | 部分或全部迭代器失效 |
list | • 删除当前元素 | 仅被删元素的迭代器失效 |
关联容器 (set/map) | • 删除当前元素 • rehash(unordered) | 被删元素迭代器失效 |
三、典型失效场景分析
-
vector插入导致扩容
vector<int> v = {1,2,3}; auto it = v.begin(); v.push_back(4); // 容量不足,触发重新分配内存 *it = 10; // 未定义行为!it已失效
2.删除时的迭代器递增错误
vector<int> v{1,2,3,4}; for(auto it=v.begin(); it!=v.end(); ++it) {if(*it % 2 == 0)v.erase(it); // 错误!erase后it失效,再++导致崩溃 }
3.unordered_map的rehash
unordered_map<int, string> m; auto it = m.begin(); for(int i=0; i<1000; ++i) m[i] = to_string(i); // 可能触发rehash it->second = "test"; // 可能访问失效内存
四、避免失效的解决方案
更新迭代器法(推荐)
// vector删除示例
for(auto it=v.begin(); it!=v.end(); /* 空 */) {if(condition) it = v.erase(it); // erase返回下一个有效迭代器else ++it;
}// 插入操作
it = v.insert(it, new_value); // insert返回新元素位置
it += 2; // 安全移动
索引法(仅随机访问容器)
vector<int> v{1,2,3,4};
for(size_t i=0; i<v.size(); ) {if(v[i] % 2 == 0) {v.erase(v.begin() + i);// 索引自动更新,无需调整} else {++i;}
}
算法组合法(高效安全)
vector<int> v{1,2,3,4};
// 移除-擦除惯用法
v.erase(remove_if(v.begin(), v.end(), [](int x){ return x%2==0; }), v.end());
临时存储法(复杂删除场景)
list<int> l{1,2,3,4};
vector<list<int>::iterator> toErase;// 先标记要删除的元素
for(auto it=l.begin(); it!=l.end(); ++it)if(*it % 2 == 0) toErase.push_back(it);// 批量删除
for(auto& e : toErase) l.erase(e);
五、特殊容器注意事项
-
deque:
-
首尾插入通常不使迭代器失效(除非分块变化)
-
中间插入使所有迭代器失效
deque<int> d{1,2,3}; auto it = d.begin() + 1; d.push_front(0); // it可能失效!
关联容器:
-
插入永不使迭代器失效
-
删除仅使被删元素的迭代器失效
set<int> s{1,2,3}; auto it = s.find(2); s.erase(1); // 不影响it s.insert(4); // 不影响it cout << *it; // 安全输出2
unordered容器:
-
rehash 使所有迭代器失效
-
可通过
reserve()
预分配桶避免unordered_map<int, string> m; m.reserve(1000); // 预分配空间 auto it = m.begin(); for(int i=0; i<500; ++i)m[i] = "test"; // 不会触发rehash it->second = "safe"; // 安全
六、最佳实践总结
-
遍历时修改:优先使用
it = container.op(it)
模式 -
大规模修改:使用STL算法(remove/copy_if等)
-
性能敏感场景:
-
vector预分配空间(
reserve()
) -
unordered容器预分配桶(
reserve()
)
-
-
长期保存迭代器:
-
避免在可能修改容器的代码段保存
-
-
vector.h
#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
namespace Boogiepop
{//声明与定义不能分别定义在两个文件template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//迭代器iterator begin(){return _start;}iterator end(){return _finish;}//const迭代器//const不是自身不能修改,而是指向的内容不能修改const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}//构造函数vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){}//拷贝构造函数vector(const vector<T>&v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(v.capacity());for (auto e : v){push_back(e);}}//强制编辑器生成构造vector()=defalut;//列表初始化构造vector(initializer_list<T> il){reserve(il.size());for (auto& e : il){push_back(e);}}//函数模板初始化template<class InputIterator>vector(InputIterator first, InputIterator last){reserve(last - first);for (InputIterator it = first; it!= last; ++it){push_back(*it);}}vector(size_t n, const T& val){resize(n, val);}//析构函数~vector(){if (_start!= nullptr){delete[]_start;_start = nullptr;_finish = nullptr;_end_of_storage = nullptr;}}//返回一个向量有效元素的个数size_t size() const{return _finish - _start;}//返回一个向量分配内存中元素的个数size_t capacity() const{return _end_of_storage - _start;}//扩容void reserve(size_t n){size_t old_size = size();if (n>capacity()){T*tmp=new T[n];//拷贝旧空间数据if (_start!= nullptr){memcpy(tmp,_start,sizeof(T)*size());delete[]_start;}_start = tmp;_finish = _start + old_size;_end_of_storage = _start + n;}}//尾插数据void push_back(const T& val){if (_finish == _end_of_storage)//空间满了,需要扩容{reserve(capacity() == 0? 4 : 2 * capacity());}*_finish = val;++_finish;}//[]运算符重载T&operator[](size_t i){assert(i<size());return _start[i];} //判断为空bool empty() const{return _finish == _start;}//尾部删除void pop_back(){/*assert(_finish > _start);*/assert(!empty());--_finish;}//插入void insert(iterator pos, const T& val){assert(pos>=_start && pos<=_finish);if (_finish == _end_of_storage){size_t len =pos-_start;reserve(capacity() == 0 ? 4 : 2 * capacity());//更新pos,pos失效了pos = _start + len;}//移动后面的元素iterator end = _finish - 1;while (end >= pos){*(end + 1)=*end;--end;}*pos = val;++_finish;}//删除void erase(iterator pos){assert(pos>=_start && pos<=_finish);iterator it = pos + 1;while (it !=_finish){*(it - 1) = *(it);++it;}--_finish;}//修改大小void resize(size_t n,T val=T ()){//匿名对象的使用场景if (n>size()){reserve(n);while (_finish!=_start+n){*_finish = val;++_finish;}}else{_finish = _start + n;}}private:iterator _start;//第一个元素的指针iterator _finish;//最后一个元素的指针iterator _end_of_storage;//分配的内存的最后一个元素的指针};
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include"vector.h"
//泛型函数模板
template<typename container>
//打印vector
void print(const container& v)
{//范围for本质上就是迭代器for (auto e : v){std::cout << e << " ";}std::cout << std::endl;
}
void test1()
{Boogiepop::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;//范围for本质上就是迭代器for (auto e : v){std::cout<<e<<" ";}std::cout<<std::endl;print(v);
}
void test2()
{Boogiepop::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);print(v);v.pop_back();v.pop_back();print(v);
}
void test3()
{Boogiepop::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);print(v);v.insert(v.begin()+3, - 2);print(v);
}
void test4()
{Boogiepop::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);print(v);int x;std::cin >> x;auto it = std::find(v.begin(), v.end(), x);if(it!= v.end()){//迭代器失效v.insert(it,x*10);//C++扩容没有具体固定vector的扩容规则//我们认为扩容后it就失效了,所以不能使用it//it不能继续使用,已经失效了*it = 10;//要更新迭代器位置}print(v);
}
void test5()
{Boogiepop::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);v.push_back(8);print(v);v.erase(v.begin()+2);v.erase(v.begin()+3);print(v);int x;std::cin >> x;auto it = std::find(v.begin(), v.end(), x);if (it != v.end()){//这里的it迭代器不会失效v.erase(it);*it = 10;}print(v);
}
void test6()
{std::vector<int> v;//Boogiepop::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);v.push_back(8);print(v);//删除所有的偶数auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){//erase迭代器也会失效//要重新赋新值才可以it=v.erase(it);}else{++it;}}print(v);
}
void test7()
{Boogiepop::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);v.push_back(8);print(v);v.resize(10);print(v);v.resize(13,10);print(v);//v.resize(20, 19);
}
void test8()
{Boogiepop::vector<int> v = {1,2,3,4,5,6,7,8,9,10};
}
int main()
{//test1();//test2();//test3();//test4();//test5();//test6();//test7();//test8();return 0;
}
今天的内容就到这里了求一个点赞谢谢。
封面图自取: