在上一篇文章中我们详细的向大家介绍了vector的一些核心接口的使用,那么本篇文章就来深度的剖析一下vector的底层实现
在模拟实现vector之前我们首先要了解vector的基本成员变量,然后在逐步进入到vector的一些核心接口的实现。如何知道这些成员变量呢?下面通过源码一探究竟:

有了上面的认识,那么我们模拟实现的vector的成员变量就仿照源码来实现:
#include<iostream>
using namespace std;
namespace my_vector
{
template<class T>
class vector
{
public:
//vector的迭代器使用的是一个原生指针,因为原生指针本身就能完成迭代器相关的++ * --等这些操作
typedef T* iterator;
typedef const T* const_iterator
private:
iterator _start;//指向空间头部的指针
iterator _finish;//指向最后一个有效数据的下一个位置
iterator _endofstorage;//指向空间的末尾
};以上就是模拟实现的vector的基本框架,成员变量就是_start、_finish、_endofstorage这三个指针。下面就正式的进入vector的模拟实现,模拟vector的五大步骤:
1、构造相关接口 2、迭代器相关接口 3、空间相关 4、元素访问 5、vector的修改操作
构造相关的接口主要有以下几种:
注意:以下实现的接口均是在vector类的内部实现,不像string那样声明和定义可以分离到两个文件。因为我们要自己实现一个vector的模板,而模板的声明和定义是不能分离到两个不同的文件的,同一个文件下可以。
//vector的默认构造
vector()
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{}
//使用n个val初始化
//这里的T()是调用T对应的构造 是为了能够让任意类型都能够调用 如果T是内置类型 就会调用内置类型的构造 如果T是自定义类型就调用自定义类型自己的默认构造
vector(size_t n, T val = T())
{
resize(n, val);//resize接口后面会讲
}
//拷贝构造
```cpp
//拷贝构造 v2(v1)
vector(const vector<T>& v)
{
//开与v1一样大的空间
reserve(v.size());//reserve接口后面会介绍
//底层也是调用push_back
for (auto e : v)
{
push_back(e);//尾插,后面会介绍
}
}
//使用初始化列表构造 示例:vector<int> v1={1,2,3,4,5}; 花括号的内容其实是转化成了initializer_list对象 这里不理解的可以看上一篇文章!
vector(initializer_list<T> il)
{
//根据所给的对象il开空间然后调用push_back
reserve(il.size());
for (auto e : il)
{
//底层是this指针调用的pus_back this指针存的是要构造的vector对象的地址
push_back(e);
}
}
//迭代器区间构造 搞成函数模板支持泛型 形参可以是任意容器的迭代器
template<class Inputiterator>
vector(Inputiterator first, Inputiterator last)
{
//底层调的还是push_back
while (first != last)
{
push_back(*(first));
first++;
}
}
//赋值重载 底层使用交换函数交换底层的成员变量
vector operator=(const vector<T>& v)
{
swap(v);
return *this;
}
void swap(const vector<T>& v)
{
std::swap(_start,v._start);
std::swap(_finish,v._finish);
std::swap(_endofstorage,v._endofstorage);
}
//析构
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}vector的迭代器主要实现的是普通迭代器和const迭代器:
//普通迭代器
iterator begin()
{
return _start;
}
iterator end()
{
//返回的是finish不是endofstorage
return _finish;
}
//const迭代器
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
//返回的是finish不是endofstorage
return _finish;
}反向迭代器就是与正向迭代器相反,
rbegin()返回end()-1,rend()返回begin()-1。
与空间相关的接口有:
1、size() ——> 记录有效数据个数 2、capccity() ——> 记录总的空间大小 3、empty() ——> 判空 4、resize() ——> 扩容 影响size 5、eserve() ——> 扩容 不影响size
注意:vector中空间相关的接口就属reserve接口最为重要,它主要负责vector的扩容逻辑,而resize接口也可以扩容但是两者有本质的区别,通过下面的底层实现你就能一探究竟:
//size和capacity通过两个指针相减可以计算它们之间的数据个数
size_t size() const
{
return _finish - _start;
}
size_t capacity()
{
return _endofstorage - _start;
}
bool empty() const
{
return _start == _finish;
}
void resize(size_t n, T val = T())
{
if (n > size())
{
reserve(n);
//n>size后面的n-size个空间使用val来填充
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
else//n<size 缩容影响size 有效数据直接缩到n处
{
_finish = _start + n;
}
}
void reserve(size_t n)
{
//在start更新前要保存一下size
auto oldsize = size();
if (n > size())
{
//开辟新空间 拷贝旧数据
iterator tmp = new T[n];
//拷贝旧数据
if (_start)
{
//memcpy深层次的拷贝问题 原因对于自定义类型是浅拷贝 delete的时候析构两次
//memcpy(tmp, _start, size()*sizeof(T));
//使用赋值重载来避免这种问题!!!
for (size_t i = 0; i < old_size; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
//更新有效数据个数
_finish = _start + oldsize;
_endofstorage = _start + n;
}
}注意这里有一个很容易留坑的点:就是memcpy生层次的浅拷贝问题:

怎么才能解决呢?调用赋值重载完成深拷贝就可以。

元素访问相关的接口最常使用的就是operator[],而vector的迭代器使用的是原生指针,那么operator[]无非就是访问某个下标的元素,下面看代码:
//支持读和写操作
const T& operator[](size_t i)
{
//判断下标是否合法
assert(i < size());
return _start[i];//实际上转化为指针的解引用: *(_start+i)
}
//加上const后只读
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];
}vector修改相关的接口无非就是插入删除,插入有尾插,任意位置插入,删除有尾删,以及任意位置的删除,实现这些插入,删除函数时有较多的细节需要注意。下面给出代码再一点点的剖析。
//尾插
void push_back(const T& x)
{
//插入要考虑空间是否足够
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
*_finish = x;
++_finish;
}
//尾删
void pop_back()
{
assert(!empty());
--_finish;
}
iterator insert(iterator pos, const T& x)
{
assert(pos <= _finish);
assert(pos >= _start);
//插入首先判断空间是否足够
if (_finish == _endofstorage)
{
//法一 计算相对位置
size_t n = pos - _start;
//扩容 异地扩容会导致迭代器失效 在这里就是野指针
reserve(capacity() == 0 ? 4 : 2 * capacity());
//扩容后更新pos
pos = _start + n;
}
//挪动数据
iterator end = _finish;
while (end >= pos)
{
*(end + 1) = *(end);
end--;
}
*(pos) = x;
_finish++;
return pos;
}
//版本二 返回迭代器
iterator erase(iterator pos)
{
//检查pos的合法性
assert(pos <= _finish);
assert(pos >= _start);
//删除要判断容器是否为空
if (!empty())
{
//删除也会引发迭代器失效
iterator it = pos;
while (it < _finish)
{
//
*(it) = *(it + 1);
++it;
}
--_finish;
}
return pos;
}void test_vector9()
{
my_vector::vector<int> v{ 1,2,3,4};
auto it = v.begin();
v.push_back(5); //这里会扩容
while (it != v.end())
{
cout << *it << " ";
*it = 100;
++it;
}
cout << endl;
}
注意:上面的插入元素的过程中会引发迭代器失效导致打印的全是随机值(有时候会奔溃),至于为什么会失效我们画图来分析:
1、插入引起的迭代器失效

void test_vector10()
{
//删除v中所有的偶数
my_vector::vector<int> v{1,2,3,4,5,6};
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
v.erase(it);
++it;//erase删除的迭代器如果是最后一个元素++it导致程序崩溃
}
for (auto e : v)
cout << e << " ";
cout << endl;
return 0;
}
2、删除引起的迭代器失效

以上就是vector的模拟实现、memcpy的深层次的浅拷贝问题、迭代器失效的所有内容啦,其中迭代器失效在我们平时的使用中稍不留神就会出错,所以我们要多多留意。有任何问题欢迎加我交流!