前言
上期我们介绍了vector的使用,对vector已经有了一个基本的认识!本期我们在上面的基础上在来拔高一层,来探索一下vector的底层实现!
本期内容介绍
vector 常用接口的模拟实现
基本框架的搭建
OK,我们在开始模拟前先得有个基本的架子吧!比如最基本的成员变量是哪些!我们先来看看库里面的源码是如何做的!(这里采用的SGI的源码,有需要的伙伴直接私信我~!)
这里他有三个iterator的三个成员变量,分别表示元素的开始位置,有效元素的结束位置,和有效容量的下一个位置!
那什么是iterator呢?
我们可以看到,iterator是val_type*的重命名,而val_type是T的重命名!也就是说,iterator是T*。所以我们可以设计出如下的基本结构!
容量相关
size
这里实现size很简单,因为vector是物理空间连续的,所以我们可以直接使用_finish - _start来计算他们之间的元素个数!也就是元素的有效个数!
size_t size() const
{
return _finish - _start;
}
capacity
和size同理,直接使用_endofstorage - _start即可!
size_t capacity() const
{
return _endofstorage - _start;
}
empty
直接复用上面的size,判断size是否 == 0
bool empty() const
{
return size() == 0;
}
reserve
当n > capacity时,开心空间大小为n,将旧空间的数据拷贝到新空间!否则不作处理!
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];//开新空间
memcpy(tmp, _start, sizeof(T) * size());//将旧空间的内容拷贝过去
delete[] _start;//释放掉旧空间
_start = tmp;//让_start指向新空间
_finish = tmp + size();//修改新的_finish
_endofstorage = _start + n;//修改新的_endofstorage
}
}
但是这种方式是有问题的!
这里是把心的数据拷贝到新空间,然后释放旧空间,在让_strat指向新空间!_finish = _start + size(),这里就有问题!在你把新空间给_start时,原来的旧空间已经被释放了!此时的size计算时,_start指向新空间,而_finish指向旧空间!导致最后的奔溃!
解决这个问题的办法其实很简单!就是你在memcpy之前记录一下size即可!
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t len = size();//提起记录一下size
memcpy(tmp, _start, sizeof(T) * len);
delete[] _start;
_start = tmp;
_finish = tmp + len;
_endofstorage = _start + n;
}
}
但这样写也会有一个浅拷贝的问题!如果你_start的置空空间里面原来存的是字符串或数组等就会形成浅拷贝!在析构时就会析构两次!
但是拷贝完了之后,会delete[] _start,此时会把原来的那块空间的资源释放了!等再去访问时就已经是非法的访问了,而且等结束了再去析构就相当于析构两次了!为了解决这个问题我们不用memcpy,而直接用循环赋值形成深拷贝即可!
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t len = size();//提起记录一下size
//memcpy(tmp, _start, sizeof(T) * len);
for (size_t i = 0; i < len; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
_finish = tmp + len;
_endofstorage = tmp + n;
}
}
这样写就没问题了!
resize
resize还是分为三种情况:
n > capacity(), 扩容 + 尾插
size() < n < capacity(), 尾插
n < size(), 删除元素保留前n个
这里的实现思路也很简单,只要是n < size,我们就把_finish设置为_start + n就是前n个即可, 否则其他两种情况先上来把容量扩到n在从size开始插入val到n
void resize(size_t n, const T& val = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
reserve(n);
for (size_t i = size(); i < n; i++)
{
_start[i] = val;
}
_finish = _start + n;
}
}
构造和析构
默认构造
这里vector的成员变量都是内置类型!所以可以不写,编译器会自动生成默认的构造!但是内置类型不做处理,我们可以在声明时给一个缺省值!只在内里面写一个空构造,函数体什么也不用写即可!
namespace cp
{
template<class T>
class vector
{
public:
typedef T* iterator;
vector()
{}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
}
}
当然你也可以显示的写出来自己去初始化!
vector()
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{}
析构
先把_start指向的那块空间给释放掉,然后将三个成员变量置空即可!
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
拷贝构造
开和v一样的空间,然后将v的数据一个个遍历未并尾插即可!
注意:由于我们没有选择缺省值的那种,所以这里还是要对三个成员进行初始化列表初始化的!
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
reserve(v.capacity());
for (auto& e : v)
{
push_back(e);
}
}
用n个val构造
这里可以直接调用resize,也可以直接自己手搓一个!
vector(size_t n, const T& val = T())
{
resize(n, val);
}
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
这样写会与一段迭代器区间构造(就在下面)的冲突!如下:
我们定位到错误的那一行发现根本就没有走到,用n个val初始化的构造里面!而是走到了迭代器区间构造的这里了!为什么了?原因是int和int去匹配size_t和int存在转换,但是这里的迭代器区间的话不需要转换直接搞就可以了!所以这里就选择了迭代器区间!我们看看库里面是如何解决的?
源码里面的处理方式是重载了一个int版本的~!所以我么这里也需要重载一个int版本的即可!
vector(int n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
用一段迭代器区间构造
我们可以使用一个vector的衣服分区初始化另一个vector!初始化和被初始化的类型得相同或可以支持隐式类型转化!
实现思路:当左端的迭代器不等于右端的迭代器时取当前左端迭代器的值尾插
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
以数组的形式初始化
这是C++11提供的,我们在使用时用过但没有介绍!这里先介绍一下!
后面的空间配置器先别管!他就只有一个initializer_list<value_type> 类型的il对象!我们看看initializer_list<value_type> 是啥?
他是一个类模板,表示一组相同类行元素集合!实例化时将T转换为相应的类型的类,也就是说,实际上用vector<int> v = {1,2,3};初始化时先转换为initializer_list<int> il的对象,然后拿这个对象去初始化的!
我们现在要想在我们的vector中支持这个操作其实我们只需要中在一个构造即可!
我们可以先开il。size()一样大的空间,因为他们支持size和迭代器,所以我们直接把il里面的数据拿出来尾插即可!!
vector(initializer_list<T> il)
{
reserve(il.size());
for (auto& e : il)
{
push_back(e);
}
}
赋值拷贝
传统写法:把原来的空间_start释放掉,然后开和要赋值的一样大的空间!然后将v的数据拷贝给_start即可!
注意这里用到了重载后的[],不了解点击目录查看operator[](size_t n)!写在这里是为了完成整!
vector<T>& operator=(const vector<T>& v)
{
delete[] _start;
_start = new T[v.capacity()];
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v[i];
}
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
return *this;
}
现代写法:我们把形参去掉引用,让他变成实现的一份拷贝!然后让他与当前的对象交换即可!
注意:这里用到了swap,不了解点击目录查看:swap(vector<T>& v)
vector<T>& operator=(vector<T> v)
{
if (&v != this)
{
swap(v);
}
return *this;
}
但是我觉得这样写还是不够优雅!因为如果真是自己给自己赋值的话,形参已经拷贝了实参!这样不太好,我们可以还是形参是引用,等判断不是自己给自己赋值后再开空间拷贝并交换!
vector<T>& operator=(const vector<T>& v)
{
if (&v != this)
{
vector<T> tmp(v);
swap(tmp);
}
return *this;
}
元素访问
[ ]
这里和string 的一模一样,直接返回该位置元素的引用(因为访问时可能需要修改!)即可!如果是const的vector或者我们只读的,我们可以加const修饰保证权限
T& operator[](size_t pos)
{
return _start[pos];
}
const T& operator[](size_t pos) const
{
return _start[pos];
}
front
取0号位置的数据!
T& front()
{
return _start[0];
}
back
取size()-1位置的数据!
T& back()
{
return _start[size()-1];
}
修改相关
push_back
先判断扩容,之后再_finish的位置直接插入即可!最后让_finish偏移到下一个位置即可!
void push_back(const T& val)
{
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
*_finish++ = val;
}
pop_back
再删除前一定先判断是否为空!如果为空就别删了!否则让_finish--即可!
void pop_back()
{
assert(!empty());
--_finish;
}
insert
我们先判断要插入的位置是否合法!在判断是否扩容,然后挪动数据,最后插入并返回新插入元素的指针!
iterator insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _endofstorage)
{
size_t len = pos - _start;//一开始扩容前的长度
reserve(capacity() == 0 ? 4 : 2 * capacity());
pos = _start + len;//扩容后调整pos到原来合适的位置
}
iterator end = _finish;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
return pos;
}
erase
pos位置的后一个元素开始逐一往前覆盖,最后--_finish,返回删除元素后面的第一个元素的指针!
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator end = pos + 1;
while (end < _finish)
{
*(end - 1) = *end;
++end;
}
--_finish;
return pos;
}
swap
这里的交换还是对,对象的成员属性直接交换,代价比直接使用库里面的小(库里面的会形成拷贝)!我们直接利用库里面的swap对属性进行交换!
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
clear
本质是让有效元素的个数为0,所以这里直接让_start == _finish即可!
void clear()
{
_finish = _start;
}
迭代器
由于vector的物理空间是连续的,所以它的原生指针就是天然的迭代器!begin就是_start,end就是_finish,如果是const的直接加const返回const_iterator
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
非成员函数
swap
专门实现这个也是为了防止误操作,调到库里面的那个!这样重载后这个就优先被调了!
但是这里_start等都是私有成员,外部无法访问,所以我们可以搞成友元函数!
template<class T>
void swap(vector<T>& v1, vector<T>& v2)
{
std::swap(v1._start, v2._start);
std::swap(v1._finish, v2._finish);
std::swap(v1._endofstorage, v2._endofstorage);
}
全部源码
#pragma once
#include <assert.h>
namespace cp
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
reserve(v.capacity());
for (auto& e : v)
{
push_back(e);
}
}
//vector(size_t n, const T& val = T())
//{
// resize(n, val);
//}
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);
}
}
vector(initializer_list<T> il)
{
reserve(il.size());
for (auto& e : il)
{
push_back(e);
}
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//vector<T>& operator=(const vector<T>& v)//传统写法
//{
// delete[] _start;
// _start = new T[v.capacity()];
//
// for (size_t i = 0; i < v.size(); i++)
// {
// _start[i] = v[i];
// }
//
// _finish = _start + v.size();
// _endofstorage = _start + v.capacity();
// return *this;
//}
vector<T>& operator=(const vector<T>& v)
{
if (&v != this)
{
vector<T> tmp(v);
swap(tmp);
}
return *this;
}
//vector<T>& operator=(vector<T> v)
//{
// if (&v != this)
// {
// swap(v);
// }
// return *this;
//}
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t len = size();//提起记录一下size
//memcpy(tmp, _start, sizeof(T) * len);
for (size_t i = 0; i < len; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
_finish = tmp + len;
_endofstorage = tmp + n;
}
}
void resize(size_t n, const T& val = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
reserve(n);
for (size_t i = size(); i < n; i++)
{
_start[i] = val;
}
_finish = _start + n;
}
}
void push_back(const T& val)
{
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
*_finish++ = val;
}
void pop_back()
{
assert(!empty());
--_finish;
}
iterator insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _endofstorage)
{
size_t len = pos - _start;//一开始扩容前的长度
reserve(capacity() == 0 ? 4 : 2 * capacity());
pos = _start + len;//调整pos到原来合适的位置
}
iterator end = _finish;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator end = pos + 1;
while (end < _finish)
{
*(end - 1) = *end;
++end;
}
--_finish;
return pos;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
void clear()
{
_finish = _start;
}
T& front()
{
return _start[0];
}
T& back()
{
return _start[size()-1];
}
T& operator[](size_t pos)
{
return _start[pos];
}
const T& operator[](size_t pos) const
{
return _start[pos];
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
bool empty() const
{
return size() == 0;
}
private:
template<class T>
friend void swap(vector<T>& v1, vector<T>& v2);
iterator _start;
iterator _finish;
iterator _endofstorage;
//iterator _start = nullptr;
//iterator _finish = nullptr;
//iterator _endofstorage = nullptr;
};
template<class T>
void swap(vector<T>& v1, vector<T>& v2)
{
std::swap(v1._start, v2._start);
std::swap(v1._finish, v2._finish);
std::swap(v1._endofstorage, v2._endofstorage);
}
template<class T>
void print(const vector<T>& v)
{
for (const auto& e : v)
{
cout << e << " ";
}
cout << endl;
}
}
OK,好兄弟本期分享就到这里,我们下一期再见!
结束语:且视他人之凝目如盏盏鬼火,大胆地去走自己的夜路!