从vetor的模拟实现开始,我们就会开始涉及到泛型编程,模板类。作为模板类你要知道,vectro的元素可以有基本的数据类型: 字符,整形,空类型,浮点。 和自定义类型: 库里面的就有string,甚至可以vector<vector<int>>这样二维的。
所谓万事开头难? 我们该如何实现它呢? 从确定成员变量开始!
1. 成员变量和迭代器

这个思路其实很像顺序表升级为了模板类,原来只能放int 但是现在能放基本数据类型和自定义类型了,一一对应原来的( int*_arr(数组的首地址), size_t _size() // 个数,_capacity // 顺序表容量)
但是现在是模板类,不能很多时候可能不能用这些下标来操控,要时候指针,所以就定义三个指针: T* 类型的,分别指向 vector的首地址,和元素的末尾下一个, 以及容器的容量的位置。 但是为了统一容器操作,所以封装T*为 iterator 类型的。
其次为了更加方便的实现begin, end 这些迭代器,可以提取把 size() 和capacity实现
成员变量
cpp
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
迭代器声明 和析构函数实现:
这里也可以写一个const的迭代器,会好一些符合库里面写的标准:
cpp
class vector {
public:
typedef T* iterator;
typedef const T* const_iterator;
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 = nullptr;
_finish = nullptr;
_end_of_storage = nullptr;
}
size和capacity:
因为我们的成员变量都是指针嘛,所以我的建议是最好把这个提前写出来 后面用起来就很方便。
cpp
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _end_of_storage - _start;
}
然后写完这些,我觉得开始写一个reserve,然后再写一个push_back. 会更好哦
2.扩容的reserve(size_t n)和push_back(T val)
reserve的实现:
reserve的实现思路,最好提前存一下size,标记sz ,然后判断是否需要扩容。
扩容的操作是,1. 提前开好n个空间, 2.如果原来空间不是空的(所以需要用_start来判空)注意这里最好不要用memcpy, 你会发现我这样是循环遍历然后一个一个的赋值的操作。 下面会讲解原因 ) 不是空的,那就赋值,最好是销毁delete原来空间 。
最后是更新成员变量。
为什么把原来的值拷贝的时候不用memcpy:
这需要你对memcpy有一定的了解,memcpy是按照字节,一个一个的拷贝到新的数组中,有浅拷贝的意味,它并不会调用自定义函数的构造函数,这就是为什么要赋值操作一个一个的原因。 c中的内存管理都没有对自定义类型调用构造函数的功能,所以它的拷贝几乎都是浅拷贝。
所以我们要用赋值,举个例子,如果这里是 vector<string>, 成员变量是string,如果是通过memcpy过去的它拷贝的时候按照string的成员变量拷贝的是它的指针数组的首地址,这是浅拷贝
当我们析构原来数组的时候,这时候这块string指向的内存已经被销毁了,所以出现二次析构的现象,会造成程序崩溃。 这就是浅拷贝的危害。
cpp
void reserve(size_t n)
{
size_t sz = size();
if (n > capacity())
{
T* temp = new T[n];
if (_start) {
// 不要使用 memcpy(temp,_start)
for (int i = 0;i < size();i++)
{
temp[i] = _start[i];
}
delete[] _start;
}
_start = temp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
void push_back(T val)的实现
这个实现思路很简单的,就是 让finish指向的内存为插入的值,值得注意的地方就是,需要我们对他进行扩容判断。
不同编译器传递扩容的形式是不一样的,简单来看课vs编译器下对库里面的扩容是什么样的 和g++编译器 (linux x86内核)
g++

和vs下的:

显然linux中g++编译器使用的策略是每次都是2被扩容,vs中比较灵活,几乎都是1.5倍扩容,这是为了减少内存的浪费。
下面我的思路是效仿g++的2被扩容
cpp
void push_back(T val) {
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
_start[size()] = val;
_finish++;
}
前面的 基本的迭代器啊,扩容搭建好了,后面写其他的就很顺手了,我这里是先写resize,然后进一步拷贝构造和赋值重载写了,再顺手把 insert 和eraser写了。
顺便把resize写了:
思路就是 判断n,然后小于等于size()自然就是之间覆盖就行了。
另一种情况则需要扩容然后就是赋值。 这里的建议就是赋值的时候之间利用_finishi这个指针就好了不用创建其他变量 处理结束赋值 _finish也到了指定位置了。
还有就是这里的为什么是小于等于 ,等于这种情况也算进去就因为方便,不浪费资源没必要去扩容了。
cpp
void resize(size_t n, T val = T())
{
if (n <= size())
{
_finish = _start + n;
}
else {
// 扩容 和赋值
reserve(n);
while(_finish <_start+n)
{
*_finish = val;
_finish++;
}
}
}
3.构造函数:
主要写了3种。 空参构造 :我们这里就没有初始化列表了,因为在声明的时候就已经写好了,其次就是迭代器构造,这个是最典型的
提前写一个迭代器类型哦,因为你并不知道这个迭代器会是什么样的,它可能是一个数组传过来的,还可能是另一个vector等等 vector(InputIterator begin,InputIterator last)
实现起来就是遍历这个迭代器然后push_back一下
还有两种就是 vector (size_t n,T val=T()) ,含义就是 构造一个具有n个的元素都是Val的vector但是有一个前提就是,这里对val的缺省值是用了T()这是一个比较新的东西对刚接受实现vector来说,这是因为你传递的值还有可能是自定义类型的值,对应内置类型也是有基类的所以这样调用的构造函数是没有问题的。 这里的本质就是调用一个匿名函数然后拷贝构造给val。 这是很常见用于缺省值的。
存在一个经常报经过的问题传递参数是vector(5,1) 这里的数字5默认是int的,然后被强转为size_t 所以同时还可以重载一个vector (int n,T val=T())
cpp
// 1. 空参构造 初始化在成员变量声明就已经初始化了
vector() {
}
template<class InputIterator>
// 迭代器插入
vector(InputIterator begin, InputIterator last) {
while (begin < last)
{
push_back(*begin);
begin++;
}
}
// n val 为了兼容同时可以写一个int n
vector(size_t n, T val = T())
{
reserve(n);
for (int i = 0;i < n;i++)
push_back(val);
}
vector(int n, T val = T())
{
reserve(n);
for (int i = 0;i < n;i++)
push_back(val);
}
4.现代写法的拷贝构造和赋值重载 以及swap
其实写法跟string的实现很类似,先把swap实现了 ,然后就是拷贝构造中利用构造函数创建了一个temp中间变量。 然后和现在对象swap。
赋值重载则是在传参的时候不穿引用会利用拷贝构造创建的temp 然后在swap一下
swap的实现
swap的实现 就是把当前对象的所有成员变量的值一一交换一下就行。
cpp
void swap(vector<T>& v)
{
std::swap(_start,v._start);
std::swap(_end_of_storage,v._end_of_storage);
std::swap(_finish,v._finish);
}
拷贝构造和赋值重载
你看吧挺巧妙的,层层利用: 拷贝构造利用构造
赋值重载 利用拷贝构造
cpp
//拷贝构造和赋值重载
vector(const vector<T>&v)
{
vector temp(v.begin(), v.end());
swap(temp);
}
vector& operator=(vector<T>temp)
{
swap(temp);
return *this;
}
5.erase和insert
首先你心里面有这张图很重要。

erase 实现:
eraser, iterator eraser(iterator pos)
删除指定位置的参数。
首先最后是断言一下pos在_start 和_finish之间,其次只需要进行一个覆盖就行从_finishi-1到pos覆盖就行了。
可是从后往前覆盖会丢失数据使用可以从前往后覆盖 写一个临时迭代器it=pos+1
然后 *(it-1 )=*it 一直到it==finish
最后记得让_finish--
cpp
// resize(n,T val=T());
iterator eraser(iterator pos)
{
assert(pos>=_start&&pos<_finish);
iterator it = pos+1;
while (it<_finish)
{
*(it - 1) = *it;
it++;
}
_finish--;
return pos;
}
insert的实现

iterator insert(iterator pos,const T&val)
在pos的位置插入val的值,那就从后往前覆盖把位置空处理。然后进行插入即可
迭代器失效问题

迭代器失效问题还有好几种,下面我会说明的。这里的修改后的结果:
cpp
// insert的实现
iterator insert(iterator pos, const T& val)
{
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : 2 * capacity());
pos = _start + len;
}
iterator it = _finish;
while (it > pos)
{
*it = *(it - 1);
it--;
}
*pos = val;
_finish++;
return pos;