创作初心:在加深个人对知识系统理解的同时希望可以帮助到更多需要的同学
🛠️柯一梦主页详情
座右铭:心向深耕,不问阶序;汗沃其根,花自满枝。
因为我们是要实现一个和库里面的vector几乎一样的容器,所以我们可以先查看一下库里面的实现

接下来就开始我们的vector之旅
各函数的接口总览:
c
namespace rxj
{
//模拟实现vector
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
//默认成员函数
vector(); //构造函数
vector(size_t n, const T& val); //构造函数
template<class InputIterator>
vector(InputIterator first, InputIterator last); //构造函数
vector(const vector<T>& v); //拷贝构造函数
vector<T>& operator=(const vector<T>& v); //赋值运算符重载函数
~vector(); //析构函数
//迭代器相关函数
iterator begin();
iterator end();
const_iterator begin()const;
const_iterator end()const;
//容量和大小相关函数
size_t size()const;
size_t capacity()const;
void reserve(size_t n);
void resize(size_t n, const T& val = T());
bool empty()const;
//修改容器内容相关函数
void push_back(const T& x);
void pop_back();
void insert(iterator pos, const T& x);
iterator erase(iterator pos);
void swap(vector<T>& v);
//访问容器相关函数
T& operator[](size_t i);
const T& operator[](size_t i)const;
private:
iterator _start; //指向容器的头
iterator _finish; //指向有效数据的尾
iterator _endofstorage; //指向容器的尾
};
}
默认成员函数
1构造函数
vector首先要实现一个无参的默认构造函数,也就是给是哪个成员变量直接设置成空指针即可
c
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_capacity(nullptr)
{
}
2迭代器构造函数
我们要构造一个可以使用任意容器迭代器初始化的构造函数
前提是解引用得到的底层数据和我们vector<>里面定义的类型一样
比如我们传入的是一个string的迭代器,解引用得到的就是char,可以进行转换,转换成int,所以我们这个函数我们也要使用模版
c
template<class Inputiterator>
vector(Inputiterator first, Inputiterator end)
{
Inputiterator it = first;
while (it != end)
{
push_back(*it);//其他容器里面的迭代器都是封装过了的,我们解引用可以直接得到容器里面所存的元素
++it;
}
}
3initializer_list构造函数
这构造函数我们之前讲过,可以使用元素序列(使用{}包裹的元素序列)来构造函数,initializer_list是一个对象 ,我们包一下他的头文件就可以直接使用了
initializer_list也是一个模版容器,我们需要给他传入模版参数T
c
vector(initializer_list<T> li)
{
reserve(li.size());
for (auto e : li)
{
push_back(e);//e直接得到了li里面的数据
}
}
拷贝构造函数
传统写法
传统写法就是使用reserve开辟内存,然后使用[]进行挨个赋值
c
vector(const vector<T>& v)
{
_start = new T[v.capacity()];
for (size_t i = 0;i < v.size();i++)
{
_start[i] = v[i];
}
_finish = _start+v.size();//有效元素个数
_end_of_storage = _start + v.capacity();
}
1 注意:构造出来的对象的_end_of_capacity和_finish不能够实用源对象直接赋值,只能使用函数进行加减!!!
2 注意:我们在进行所开空间的赋值的时候我们不能使用memcpy,因为memcpy进行的是浅拷贝,如果我们的vector里面存放的是string,那么新构造出来的对象里面的各个string对象和源对象指向的空间是一样的!!!所以我们只能使用赋值,它的本质就是利用了运算符重载
现代写法
我们直接使用reserve开辟空间(直接重置好了内存大小和真实元素个数),然后再使用push_back进行赋值(push_back传入的就是构造函数)
c
vector(const vector<T>& v)
{
reserve(v.capacity());
for (auto& e : v)//这里记得使用语法糖
{
push_back(e);
}
}
赋值运算符重载
传统写法
传统写法和拷贝构造的传统写法一样,唯一不同就在于要检查是否是自己赋值给自己
c
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)//防止连续赋值
{
delete[] _start;//先释放原有空间里面的值
_start = new T[v.capacity()];
for (size_t i = 0;i < v.size();i++)
{
_start[i] = v[i];//这里vector的[]运算符重载需要自己实现
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
return *this;//支持连续赋值
}
现代写法
现代写法其实就是运用自定义类型传值的时候会调用构造函数,构造出来一个新对象,并且在函数结束以后那个对象会被销毁。然后使用swap将两个对象里面的值进行替换
c
vector<T>& operator=(vector<T> v)
{
(*this).swap(v);
return *this;
}
void swap(vector<T>& v)//这里需要使用引用
{
::swap(_start,v._start);
::swap(_finish, v._finish);
::swap(_end_of_capacity, v._end_of_capacity);
}
析构函数
析构函数特别好实现,我们在使用delete去释放_start的内存时,delete会自动调用_start里面存放的自定义类型的析构函数!
c
~vector()
{
if (_start)
{
delete[]_start;
_start = nullptr;
_finish = nullptr;
_end_of_storage = nullptr;
}
}
迭代器相关函数
begin和end
begin和end两个传值时候是否要用const修饰this指针呢?答案是只需要对cbegin和cend去修饰,因为他们迭代器指向的内容是不能被修改的
c
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
容量和大小相关的函数
capacity和size
c
size_t size()const
{
return _finish-_start;
}
size_t capacity()const
{
return _end_of_storage - _start;
}
reserve
我们开辟的是n个T类型数据的空间,也就是我们只需要更换vector里面的_satrt,此外我们要记得存一下原数组的大小,否则size()函数失效之类的会导致大小无法确定
c
void reserve(size_t n)
{
if (n > capacity())
{
size_t len = size();
T* tmp = new T[n];
if (_start)//我们要检查原数组有没有元素,没有的话就不需要管了
{
for (size_t i = 0;i < len; i++)
{
tmp[i] = _start[i];
}
delete[]_start;
}
_start = tmp;
_finish = _start + len;
_end_of_storage = _start + n;
}
}
resize
resize的思路:n与size进行比较,1、n<size()就直接缩 2、n>size()就直接使用reserve,reserve会自动检查你是否需要扩容。其次 1、size()的意思实际存储数据的个数 2、你在进行赋值的时候使用的是*finish进行赋值,这样的话就确保了实际数据之后到所传的大小n之间所有元素都会被赋上第二个参数
值得一提的是他有两个参数,第二个参数是一个缺省值T& val = T();
c
void resize(size_t n,T val = T())
{
if (n > size())
{
reserve(n);
while (_finish != _start+n)
{
*_finish = val;
++_finish;
}
}
else
{
_finish = _start + n;
}
}
empty
empty需要使用const去修饰
c
bool empty()const
{
return (_start == _finish);
}
修改容器内容的相关函数
push_back
唯一值得注意的就是如果满了以后的扩容
c
void push_back(const T& v)//传进来的元素类型要注意一下
{
if (_finish == _end_of_storage)
{
//capacity()==0?reserve(4) : reserve(capacity() * 2);
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = v;
++_finish;
}
pop_back
c
void pop_back()
{
if (!empty())
{
--_finish;
}
}
insert
在这里我们可以更直观的感受到上一节课我们讲到的迭代器失效的问题
c
iterator insert(iterator pos, const T& val)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _end_of_storage)
{
size_t len = pos - _start;//药剂一下相对位置
//capacity()==0?reserve(4) : reserve(capacity() * 2);
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator it = _finish;
while (it > pos)
{
*it = *(it - 1);
--it;
}
*pos = val;
++_finish;
return pos;
}
erase
c
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos;
while (it < _finish - 1)
{
*it = *(it + 1);
++it;
}
--_finish;
return pos;
}
访问容器相关内容函数
operator[]
const 修饰的operator[]
c
const T& operator[](size_t n)const
{
assert(n < size());
return *(_start + n);
}
没有const修饰的operator
c
T& operator[](size_t n)
{
assert(n < size());
return *(_start + n);
}
c
template<class Container>
void Print(const Container& v)
{
for (auto e : v)
{
cout << e << " ";
}
}
