😎【博客主页:你最爱的小傻瓜】😎
🤔【本文内容:C++ vector 😍 】🤔
--------------------------------------------------------------------------------------------------------------------------------
在 C++ 的典籍长廊中,vector 恰似一位博闻强识的书吏。
它以动态舒展的卷轴承载万千元素,不必忧虑篇幅局促 —— 新添内容时,只需轻挥 push_back 的笔墨,便有宣纸自然延展开来。那些沉睡的元素,如经卷中的字句般静候翻阅,用下标轻轻一点,便能唤醒某页某行的深意。
当你执迭代器之笔漫溯其间,如同指尖抚过古籍的竹笺,每一个元素都按序铺陈,静待细品。若想为这卷帙重整篇章,sort 函数便是最好的校勘师,转瞬便能将凌乱的字句编排得井然有序。
无论是数字的珠玑、字符的墨韵,还是自定义对象的锦绣文章,它都能妥帖收纳,恰似一座随需而建的藏书楼,让数据在其间各安其位,静待编程者翻阅取用。
---------------------------------------------------------------------------------------------------------------------------------在开始学习vector容器前,我们先要了解vector的概念:
( 一 ) vector 的介绍:
1. vector 是可变大小数组的序列容器。它具备数组采用的连续存储空间来存储元素的特性,又优化了数组无法动态改变大小的缺点。而对于vector容器的动态分配数组,是一个有利也有弊的处理方式。当我们插入新的数据时,且vector容器需要增加存储空间。这时他会重新开一个适合的数组来将原先的数组的数据赋值过去。而这里就有一个耗时的缺点。为了应对它,不同的库会有不同的分配空间策略:(核心)
(二)vector的使用
1.构造:
接口说明 | |
---|---|
vector ()(重点) | 无参构造 |
vector (size_type n, const value_type& val = value_type()) | 构造并初始化 n 个 val |
vector (const vector& x);(重点) | 拷贝构造 |
vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构造 |
无参构造:就是构造一个空vector
//vector()
vector<int>v;
构造并初始化 n 个 val:
//vector (size_type n, const value_type& val = value_type())
vector<int>v(10,1);
拷贝构造:用已存在的 vector
来构造新的 vector
// vector (const vector& x);
vector<int> v1{1, 2, 3};
vector<int> v2(v1);
使用迭代器进行初始化构造:利用其他容器(或 vector
自身部分范围)的迭代器来构造新 vector
// vector (InputIterator first, InputIterator last);
// 示例1:用数组迭代器构造
int arr[] = {1, 2, 3, 4, 5};
vector<int> v(arr, arr + 5);
// 示例2:用vector的部分迭代器构造
vector<int> v1{1, 2, 3, 4, 5};
vector<int> v2(v1.begin() + 1, v1.end() - 1);
2.迭代器访问:
iterator 的使用 | 接口说明 |
---|---|
begin + end(重点) | 获取第一个数据位置的 iterator/const_iterator,获取最后一个数据的下一个位置的 iterator/const_iterator |
rbegin + rend | 获取最后一个数据位置的 reverse_iterator,获取第一个数据前一个位置的 reverse_iterator |
begin
用于获取容器中第一个数据位置的 iterator
(可修改元素)或 const_iterator
(不可修改元素,只读);end
用于获取容器中最后一个数据的下一个位置的 iterator
或 const_iterator
。
// begin + end(iterator 示例,可修改元素)
vector<int> v{1, 2, 3, 4, 5};
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {*it *= 2; // 修改元素值
}
// begin + end(const_iterator 示例,只读)
vector<int> v1{1, 2, 3, 4, 5};
for (vector<int>::const_iterator cit = v1.cbegin(); cit != v1.cend(); ++cit) {cout << *cit << " "; // 只能读取元素值,不可修改
}
rbegin
用于获取容器中最后一个数据位置的 reverse_iterator
(反向迭代器,从后往前遍历的起始位置);rend
用于获取容器中第一个数据前一个位置的 reverse_iterator
(反向遍历的结束位置)。
// rbegin + rend(reverse_iterator 示例)
vector<int> v{1, 2, 3, 4, 5};
for (vector<int>::reverse_iterator rit = v.rbegin(); rit != v.rend(); ++rit) {cout << *rit << " "; // 从后往前遍历并输出元素
}
3.容量的访问:
容量空间 | 接口说明 |
---|---|
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
resize(重点) | 改变 vector 的 size |
reserve(重点) | 改变 vector 的 capacity |
size
用于获取 vector
中数据的个数。
vector<int> v{1, 2, 3, 4, 5};
cout << "数据个数:" << v.size() << endl;
capacity
用于获取 vector
的容量大小(即当前为 vector
分配的存储空间能容纳的元素个数)。
vector<int> v;
v.push_back(1);
cout << "容量大小:" << v.capacity() << endl;
empty
用于判断 vector
是否为空(即是否没有元素)
vector<int> v;
if (v.empty()) {cout << "vector 为空" << endl;
} else {cout << "vector 不为空" << endl;
}
resize
用于改变 vector
的 size
(元素个数)。若新 size
大于原 size
,会用指定值(若未指定则用默认构造值)填充新增位置;若新 size
小于原 size
,则会删除多余元素。
// resize 示例1:新 size 大于原 size,用默认值填充
vector<int> v{1, 2, 3};
v.resize(5);
for (int num : v) {cout << num << " "; // 输出:1 2 3 0 0
}
cout << endl;
// resize 示例2:新 size 大于原 size,用指定值填充
vector<int> v1{1, 2, 3};
v1.resize(5, 10);
for (int num : v1) {cout << num << " "; // 输出:1 2 3 10 10
}
cout << endl;
// resize 示例3:新 size 小于原 size
vector<int> v2{1, 2, 3, 4, 5};
v2.resize(3);
for (int num : v2) {cout << num << " "; // 输出:1 2 3
}
reserve
用于改变 vector
的 capacity
(容量),提前为 vector
分配足够的存储空间,避免后续多次扩容带来的性能开销。
vector<int> v;
v.reserve(10); // 提前分配能容纳 10 个元素的空间
cout << "容量大小:" << v.capacity() << endl;
4.增删查改:
vector 增删查改 | 接口说明 |
---|---|
push_back(重点) | 尾插 |
pop_back(重点) | 尾删 |
find | 查找。(注意这个是算法模块实现,不是 vector 的成员接口) |
insert | 在 position 之前插入 val |
erase | 删除 position 位置的数据 |
swap | 交换两个 vector 的数据空间 |
operator [](重点) | 像数组一样访问 |
push_back
用于在 vector
末尾插入元素(尾插)。
vector<int> v;
v.push_back(1);
v.push_back(2);
// 此时v中的元素为[1, 2]
pop_back
用于删除 vector
末尾的元素(尾删)
vector<int> v{1, 2, 3};
v.pop_back();
// 此时v中的元素为[1, 2]
insert
用于在指定位置(position
迭代器指向的位置之前)插入元素
vector<int> v{1, 3, 4};
// 在第二个元素位置(值为3的位置)前插入2
v.insert(v.begin() + 1, 2);
// 此时v中的元素为[1, 2, 3, 4]
erase
用于删除指定位置(position
迭代器指向的位置)的元素。
vector<int> v{1, 2, 3, 4};
// 删除第三个元素(值为3的元素)
v.erase(v.begin() + 2);
// 此时v中的元素为[1, 2, 4]
operator[]
用于像访问数组元素一样,通过下标访问 vector
中的元素。
vector<int> v{10, 20, 30};
cout << v[0] << " " << v[1] << " " << v[2] << endl; // 输出:10 20 30
v[1] = 25;
cout << v[1] << endl; // 输出:25
(三) vector 模拟实现(代码里面的注释很重要):
开始了解vector:对于vector类,由于要储存不同种类的数据,那么就需要模板来实现了。
namespace xin
{template<class T >class vector{public:typedef T* iterator; //常规的迭代器,可以读写typedef const T* const_iterator;//const修饰的迭代器,只能读无法写。//模拟实现 private:iterator _start = nullptr; //指向vector开头的数据iterator _finish = nullptr; //指向vector最后一个的数据iterator endofstorage = nullptr;//指向vector存储空间最后的位置};
};
我们先是像实现string类构建一个自己的命名空间域。接着写出模板,然后写迭代器(这里是指针),再在类里面的private里面构建成员变量。(这些就是我们先要了解的)
下一步完善vector:
1.空vector的构造:
vector()
{}
2.析构函数:
~vector()
{if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}
}
说到底:析构函数就是将之前申请的空间还给操作系统,然后将之前所用到的指针赋值为空,防止野指针的出现。
3.迭代器:
//迭代器的访问方式:
iterator begin()//可读写,且在函数内部能进行修改所属对象。
{return _start;//返回头指针
}
iterator end()
{return _finish;//返回指向vector最后一个的数据的指针
}
const_iterator begin()const//1.只能读取容器中的元素,不能修改元素的值。2.在该函数内部,不能修改所属对象
{return _start;
}
const_iterator end()const
{return _finish;
}
通过迭代器来去访问vector数据的开头的指针,和指向vector最后一个的数据的指针。
迭代器这个访问方式是每个容器都有的相同访问方式。优势:
1.统一接口
2.抽象底层实现
3.便于算法复用
4.支持范围操作
4.size:
size_t size()const //加这个const是为了在该函数内部,不能修改所属对象。
{return _finish - _start;//这里用指针减指针的方式来计算数据的个数
}
计算vector里面存储的数据大小。
5.capacity:
size_t capacity()const//加这个const是为了在该函数内部,不能修改所属对象
{return _endofstorage - _start;//这里用指针减指针的方式来计算存储空间的大小
}
计算vector 存储空间大小。
6.reserve:
void reserve(size_t n)
{if (n > capacity())//扩容的条件。{size_t sz = size();//计算原数组的数据大小T* tmp = new T[n];//申请新的数组//memcpy(tmp, _start, sizeof(T) * sz);//这里为什么不用 memcpy来将原先的数据赋值给新建的数组,是因为深拷贝的问题 兼容自定义类型的深拷贝需求 for (int i = 0;i < sz;i++)//for循环的更安全可靠。{tmp[i] = _start[i];}delete[] _start;//将这些指针给调到适合的位置。_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}
}
保留:为vector预留空间。
对于不用memcpy是因为:
当拷贝
string
这类包含指针成员的自定义类型时,若使用memcpy
进行拷贝,会引发浅拷贝问题。自定义类型(如
std::string
)一般由栈上的指针(用于管理内部字符数组等)和指针指向的堆内存(存储实际数据)组成。memcpy
只是二进制层面复制栈上的指针值,不会复制指针指向的堆内存,这会导致:
- 两个对象的指针成员指向同一块堆内存(浅拷贝);
- 析构时对同一块内存多次释放(
double free
),造成程序崩溃;- 一个对象修改数据会影响另一个对象,破坏拷贝独立性。
对于
vector
,本身是深拷贝,但当vector
中存储的是string
数组或其他含指针成员的自定义类型数组时,若用memcpy
处理,也会因浅拷贝而出错。
7.resize(调整容器大小):
void resize(size_t n, const T& val = T())//T()是指确保生成一个符合 “默认初始化” 语义的 T 类型对象。
{if (n < size()){_finish = _start + n;//(比size()小时,我们直接截取到我们想要的n个数据)}else{reserve(n);while (_finish != _start + n)//比size()大时,我们就重新申请空间,在将后面的空间赋值T(){*_finish = val;++finish;}}
}
在 C++ 中,
T()
是一种值初始化语法,用于创建T
类型的默认构造对象,这是由 C++ 标准规定的语法规则:
对于内置类型(如 int、double 等)
T()
会生成该类型的 “零值”。例如:
int()
等价于0
double()
等价于0.0
- 指针类型
int*()
等价于nullptr
对于自定义类型(类 / 结构体)
T()
会显式调用该类型的默认构造函数(即无参数的构造函数)。如果类中没有定义任何构造函数,编译器会自动生成一个默认构造函数,此时T()
会使用这个编译器生成的版本。
8.insert(pos的位置插入):
iterator insert(iterator pos, const T& x)
{assert(pos >= _start && pos <= _finish);//判断pos是否在数组内if (_finish == _endofstorage)//判断两种请况:空 ,两个不同概念的尾部指针重合.以及处理方式。{size_t len = pos - _start;//为处理迭代器失效而留下的坐标。size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;reserve(new_capacity);pos = _start + len;//解决迭代器失效的问题}iterator end = _finish - 1;//这里减一是为了更好的访问和挪移数据。while (end > pos)//往后挪移{*(end + 1) = *end;--end;}//插入数据*pos = x;++_finish;return pos;
}
pos位置的插入。这里有个迭代器失效的问题我们要注意一下,并去解决。
迭代器失效的本质
迭代器底层多是指针或指针封装(如
vector
迭代器类似原生指针T*
)。迭代器失效,是指其底层对应指针指向的空间被销毁,继续使用会导致程序崩溃。解决扩容导致内部迭代器失效的关键逻辑通过size_t len = pos - _start;
和pos = _start + len;
两行代码解决:
- 记录偏移量:
len = pos - _start
计算插入位置pos
相对于容器起始地址_start
的偏移量(相对位置,不受内存释放 / 移动影响)。- 重定位迭代器:扩容时旧内存释放、
_start
指向新内存,利用pos = _start + len
重新确定pos
在新内存中的位置。避免失效的原因
- 绝对地址(如旧内存地址)会因内存释放失效,但偏移量是相对位置,不受内存块移动影响。
- 即便
pos
原本指向特殊地址,偏移量计算仍能准确定位新内存中的位置。关于
pos
为 0 的小知识点有效内存空间的地址不会是 0,0 是操作系统预留的 “无效地址”(对应
nullptr
),不会分配给用户程序正常内存空间。所以指向容器有效范围的迭代器,底层指针绝不可能为 0,不存在 “pos
为 0” 的隐患。返回值
pos
的作用用于解决外部迭代器失效,防止类似 “先获取迭代器,再执行插入操作后,原迭代器因容器内部变化而失效” 的问题
外部迭代器失效问题
外部代码若保存插入前的迭代器,当插入触发
vector
扩容时,旧内存被释放,该迭代器会指向无效地址(失效)。若继续使用失效迭代器(如it += 10
),会访问已释放内存,引发未定义行为(如程序崩溃),这类操作属于高危行为。解决方法
insert
操作会返回新的有效迭代器,应改用该返回值来操作,而非使用插入前可能失效的旧迭代器。示例代码:auto it = vec.begin() + 1; // 插入后,it可能失效,改用返回的新迭代器 auto new_it = vec.insert(it, 200); *new_it += 10; // 安全,new_it指向新内存中正确位置
insert
返回值的意义提供扩容后已重定位的有效迭代器,替代可能失效的旧迭代器。
迭代器失效总结
- 内部防失效:通过偏移量
len
记录相对位置,确保护容后pos
在新内存中正确定位,保证insert
内部逻辑正常。- 外部防失效:
insert
返回更新后的有效迭代器,提醒用户放弃使用可能失效的旧迭代器,改用新迭代器。
9.push_back(尾插):
void push_back(const T& x)//尾插
{/*if (_finish == _endofstorage)//判断数据情况,如容器是否为空,为空的处理方式,不为空的处理方式。{size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}//尾插。*_finish = x;++_finish;*/insert(end(),x);
}
10.构造函数和拷贝构造函数:
vector(size_t n, const T& val = T())//这里的构造就像当于截取,但因为是初始化,刚开始_start = _finish,所以他是从开头赋值初始化
{resize(n, val);
}
vector(int n, const T& val = T())//这个是因为当我们在传参的时有我们传<int int>时,编译器会更具“精确匹配优先于需要隐式转换的匹配”,会到迭代器哪里但int又不是迭代器,所以会报错。
{resize(n, val);
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)//对于迭代器的初始化,而这里为什么用push_back呢,这是因为当我们不知道你要存储的数据大小时,我们只能一个一个填下去,尽管他是会遇到vector的这一缺点但还是要这样做,因为不知道有大小。
{while (first != last){push_back(*first);//因为刚开始_start = _finish。++first;}
}vector(const vector<T>& v)
{_start = new T[v.capacity()];//拷贝构造,申请空间//memcpy(_start,v._start,sizeof(T)*v.size);//是因为不适合自定义类形,浅拷贝与深拷贝的问题for (size_t i = 0;i < v.size();i++){_start[i] = v._start[i];}//为其赋值。_finish = _start + v.size();_endofstorage = _start + v.capacity();
}
//旧版
/*vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(v.capacity());for (auto e : v){push_back(e);}}*/
这里有一个概念要拿出来讲:
那便是精确匹配优先于需要隐式转换的匹配
对于:
vector<int>v(10,1);
你认为会用哪构造(如果没有int这个显示的),是size_t, 还模板的那个。他会是模板那个。因为编译器会把10,1都看成int,满足InputIterator first, InputIterator last的参数类型要求(两个参数类型相同)因此它符合精确匹配优先于需要隐式转换的匹配。而int —》size_t这个隐式转换优先级低。
解决方法:
核心就是不让它隐式转换。
1.外:
vector <int> v(10u, 1);
2.内:
vector(int n, const T& value = T()) {resize(n, value); }
11.erase(删除pos位置数据):
iterator erase(iterator pos)
{assert(pos >= _start && pos <= _finish);//判断pos是否在数组内iterator it = pos + 1;//这里加一是为了更好的访问和挪移数据while (it != _finish)//往前覆盖{*(it - 1) = *it;++it;}--_finish;return pos;
}
迭代器失效的定义与标准规定
对
vector
执行erase(pos)
后,被删除元素的迭代器pos
及其之后的所有迭代器都会失效。此时对失效迭代器进行解引用(如*it
)或递增(如++it
)操作,属于未定义行为(标准不规定执行结果,编译器可自由处理,可能崩溃、输出错误结果甚至 “看似正常运行”)。不同平台的表现差异
- VS:对迭代器有效性做严格额外检查,若检测到使用失效迭代器,会直接报错或崩溃,能提前暴露问题。
- Linux:默认不做强制检查,失效迭代器可能恰好指向未回收内存,导致程序 “看似能运行”,但这是巧合,本质仍为错误代码。
错误代码示例与问题
// 错误示例:删除所有偶数for (auto it = v.begin(); it != v.end(); ++it) {if (*it % 2 == 0) {v.erase(it); // 删除后,it已失效// 此处it指向被删除元素的下一个位置,但标准未定义其有效性}}// 输出结果可能异常(如跳过元素、循环提前结束等)for (int num : v) {cout << num << " ";}return 0;
如删除偶数的代码(
erase
后直接++it
),会因迭代器失效出现逻辑错误(如跳过元素),且删除本质是移动数据覆盖待删数据,会导致finish
前移,后续it
可能永远不等于end
,循环无法正确结束。看似 “可行” 代码的隐患
即使在 Linux 下 “通过测试” 的代码,也存在问题:
- 不符合 C++ 标准,
erase(it)
后it
已失效,后续it != v.end()
判断也可能出错。- 移植性极差,换用 VS 等编译器或调试模式会暴露崩溃 / 逻辑错误。
- 若触发扩容(内存重分配),失效
it
指向旧内存块,操作会导致严重错误。正确做法
vector
删除元素时,必须通过erase
的返回值更新迭代器,这是唯一能保证跨平台一致性和程序正确性的做法。其他 “看似可行” 的写法,本质是依赖未定义行为的侥幸,隐藏着难以排查的风险。判断代码正确性,应看是否符合语言标准,而非 “在某平台能运行”。
12.尾删:
void pop_back()
{erase(--end());
}
直接用erase任意位置删除,如果不想用erase也可以在erase里面的代码里找删除的原理再套用在尾部。
13.重载:
void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}// v1 = v2
vector<T>& operator=(vector<T> v)
{swap(v);return *this;
}
T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}const T& operator[](size_t pos) const
{assert(pos < size());return _start[pos];
}
这里的重载都与strin类里面的类似。主要是swap这个交换,以及对_start指针的运用。
完整代码:
#include<assert.h>
#include<string>
#include<iostream>
using namespace std;
namespace xin
{template<class T >class vector{public:typedef T* iterator; //常规的迭代器,可以读写 typedef const T* const_iterator;//const修饰的迭代器,只能读无法写。//析构函数:~vector(){if (_start){delete[] _start; //从头指针开始找 释放空间_start = _finish = _endofstorage = nullptr; //将指针赋值为空}}//迭代器的访问方式:iterator begin()//可读写,且在函数内部能进行修改所属对象。{return _start;//返回头指针}iterator end()//可读写,且在函数内部能进行修改所属对象。{return _finish;//返回指向vector最后一个的数据的指针}const_iterator begin()const//1.只能读取容器中的元素,不能修改元素的值。2.在该函数内部,不能修改所属对象{return _start;}const_iterator end()const//1.只能读取容器中的元素,不能修改元素的值。2.在该函数内部,不能修改所属对象{return _finish;}size_t size()const //加这个const是为了在该函数内部,不能修改所属对象。{return _finish - _start;//这里用指针减指针的方式来计算数据的个数}size_t capacity()const//加这个const是为了在该函数内部,不能修改所属对象{return _endofstorage - _start;//这里用指针减指针的方式来计算存储空间的大小}void reserve(size_t n){if (n > capacity())//扩容的条件。{size_t sz = size();//计算原数组的数据大小T* tmp = new T[n];//申请新的数组//memcpy(tmp, _start, sizeof(T) * sz);//这里为什么不用 memcpy来将原先的数据赋值给新建的数组,是因为深拷贝的问题 兼容自定义类型的深拷贝需求 for (int i = 0;i < sz;i++)//for循环的更安全可靠。{tmp[i] = _start[i];}delete[] _start;//将这些指针给调到适合的位置。_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}void resize(size_t n, const T& val = T())//T()是指确保生成一个符合 “默认初始化” 语义的 T 类型对象。{if (n < size()){_finish = _start + n;//(比size()小时,我们直接截取到我们想要的n个数据)}else{reserve(n);while (_finish != _start + n)//比size()大时,我们就重新申请空间,在将后面的空间赋值T(){*_finish = val;++finish;}}}iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);//判断pos是否在数组内if (_finish == _endofstorage)//判断两种请况:空 ,两个不同概念的尾部指针重合.以及处理方式。{size_t len = pos - _start;//为处理迭代器失效而留下的坐标。size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;reserve(new_capacity);pos = _start + len;//解决迭代器失效的问题}iterator end = _finish - 1;//这里减一是为了更好的访问和挪移数据。while (end > pos)//往后挪移{*(end + 1) = *end;--end;}//插入数据*pos = x;++_finish;return pos;}void push_back(const T& x)//尾插{if (_finish == _endofstorage)//判断数据情况,如容器是否为空,为空的处理方式,不为空的处理方式。{size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}//尾插。*_finish = x;++_finish;insert(_finish, x);}vector(size_t n, const T& val = T())//这里的构造就像当于截取,但因为是初始化,刚开始_start = _finish,所以他是从开头赋值初始化{resize(n, val);}vector(int n, const T& val = T())//这个是因为当我们在传参的时有我们传<int int>时,编译器会更具“精确匹配优先于需要隐式转换的匹配”,会到迭代器哪里但int又不是迭代器,所以会报错。{resize(n, val);}template<class InputIterator>vector(InputIterator first, InputIterator last)//对于迭代器的初始化,而这里为什么用push_back呢,这是因为当我们不知道你要存储的数据大小时,我们只能一个一个填下去,尽管他是会遇到vector的这一缺点但还是要这样做,因为不知道有大小。{while (first != last){push_back(*first);//因为刚开始_start = _finish。++first;}}vector(const vector<T>& v){_start = newT[v.capacity];//拷贝构造,申请空间//memcpy(_start,v._start,sizeof(T)*v.size);//是因为不适合自定义类形for (size_t i = 0;i < v.size;i++){_start[i] = v._start[i];}//为其赋值。_finish = _start + v.size;_endofstorage = _start + v.capacity;}iterator erase(iterator pos){assert(pos >= _start && pos <= _finish);//判断pos是否在数组内iterator it = pos + 1;//这里加一是为了更好的访问和挪移数据while (it != _finish)//往前覆盖{*(it - 1) = *it;++it;}--_finish;return pos;}void swap(const vector<T>& v){_start = v._start;_finish = v._finish;_endofstorage = v._endofstorage;}vector<T>& operator=(vector<T>& v){swap(v);return *this;}T& operator [](size_t pos){assert(pos < size());return _start[pos];}const T& operator [](size_t pos)const{assert(pos < size());return _start[pos];}private:iterator _start = nullptr; //指向vector开头的数据iterator _finish = nullptr; //指向vector最后一个的数据iterator _endofstorage = nullptr;//指向vector存储空间最后的位置};
};