
文章目录
一、vector的结构
vector的底层本质上是顺序表,但是跟我们之前的实现略微有些不同,最大的不同就是结构的不同,我们翻看stl源码中的stl_vector,我们来看看它的结构是如何定义的,如果没有stl源码可以私信我,如下图:

我们看到它有四个成员,第一个成员我们不管,我们就看框出来的三个成员,这三个成员变量的类型居然是一致的,都是iterator,我们又来看看这个iterator是什么,如图:

从上面图中看来,iterator其实就是T*,也就是数据类型的指针,使用了模板,而不像string已经指定了数据类型为char,而且这里的iterator也是vector的迭代器,是指针,为了方便大家观察,我把上面的话总结一下写成结构,如下:
cpp
//iterator就是T*
T* start;
T* finish;
T* end_of_storage;
那么现在我们先根据stl源码中的结构写出我们自己vector的结构,随后我再给大家一一解释它们的含义,如下:
cpp
#include <iostream>
#include <cassert>
using namespace std;
namespace TL
{
template<class T>
class vector
{
public:
private:
T* _start = nullptr;
T* _finish = nullptr;
T* _endofstorage = nullptr;
};
}
其中start就是数组的起始地址,finish就是数组最后一个元素的下一个元素的地址,endofstorage是容量的最后一个元素的下一个元素的地址,如图:

它们分别指向了一个数组最重要的几个部分,通过这几个指针的运算可以得到这个数据的大小以及容量,和之前结构本质上没有什么不同,各有好处,那么我们的vector就按照这种方式来实现
二、vector容器相关接口
我们来看看vector容器相关接口又哪些:

我们一个一个来实现,顺便帮大家熟悉熟悉这种结构的意义,首先是求这个数组的有效数据的个数,其实非常简单,由于finish指向最后一个元素的下一个元素,而start指向第一个元素,我们直接让这两个指针相减即可得到有效数据个数,如下:
cpp
size_t size() const
{
return _finish - _start;
}
随后我们就来求数组容量大小,大家可能都猜到了,就是用endofstorage减去start,因为它就指向数组最后一个位置的下一个位置,如下:
cpp
size_t capacity() const
{
return _endofstorage - _start;
}
接下来我们来实现判空函数,也很简单,我们只需要看看start和finish是否相等,如果相等,说明数组是空的,如下:
cpp
bool empty() const
{
return _start == _finish;
}
还有两个较为复杂的接口,就是resize和reserve,这个我们在修改接口中讲
三、vector元素访问接口
我们来看看vector元素访问接口有哪些,如下:

我们还是实现前四个,实现方法与string相同,这里我就不再多解析,如下:
cpp
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
T& at(size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
const T& at(size_t pos) const
{
assert(pos < size());
return _start[pos];
}
T& front()
{
assert(!empty());
return *_start;
}
T& back()
{
assert(!empty());
return *(_finish - 1);
}
const T& front() const
{
assert(!empty());
return *_start;
}
const T& back() const
{
assert(!empty());
return *(_finish - 1);
}
四、vector的迭代器
vector的迭代器其实和string是一样的,也是指针,因为它们的底层都是数组,可以用指针直接访问元素,不同的地方就是string类的底层数组的类型已经确定为char,而vector底层数组的类型就不确定了,因为vector是一个类模板
接下来我们就按照string类的迭代器实现方式实现一下vector的迭代器,如下:
cpp
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;
}
五、vector的修改接口
我们来看看vector的修改接口有哪些:

我已经在图中做好了记号,框起来的那部分就是我们要实现的修改接口,当然还要加上之前在容量接口中欠的resize和reserve,其实这些接口的实现就跟string类差不多,会了string类接口的实现写这个也很轻松,但是我们还是都简单过一下
reserve
vector中reserve的实现和string类的reserve的实现不能说很像,简直是一模一样,所以这里我们直接给出,如果不懂的可以看上一篇string类的实现:
cpp
void reserve(size_t n)
{
size_t Size = size();
size_t Capacity = capacity();
if (n <= Capacity)
{
return;
}
size_t newCapacity = 2 * Capacity;
if (newCapacity < n)
newCapacity = n;
T* tmp = new T[newCapacity];
for (size_t i = 0; i < Size; i++)
{
tmp[i] = _start[i];
}
if (_start)
delete[] _start;
_start = tmp;
_finish = _start + Size;
_endofstorage = _start + newCapacity;
}
resize
resize的实现也和string类似,如下:
cpp
void resize(size_t n, const T& x = T())
{
//优化前
/*if (n < size())
{
_finish = _start + n;
}
else if(n < capacity())
{
for (size_t i = size(); i < n; i++)
{
push_back(x);
}
}
else
{
reserve(n);
for (size_t i = size(); i < n; i++)
{
push_back(x);
}
}*/
//优化后
if (n < size())
{
_finish = _start + n;
}
else
{
//如果n没有capacity大,reserve不会扩容,所以可以优化
reserve(n);
for (size_t i = size(); i < n; i++)
{
push_back(x);
}
}
}
swap
交换两个vector对象的方法就是分别交换它们的三个指针,如下:
cpp
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
clear
清空数据其实就是让finish重新等于start,如下:
cpp
void clear()
{
_finish = _start;
}
push_back与pop_back
push_back和pop_back的实现还是和string差不多,并且还不用设置\0,更加简单了,如下:
cpp
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
reserve(capacity() * 2);
}
*_finish = x;
_finish++;
}
void pop_back()
{
assert(!empty());
_finish--;
}
insert
上面我们实现的大部分接口都和string类的接口差不多,但是insert和erase就稍微有点不同了, vector只能通过迭代器进行操作,但是解决的方法也很简单,因为迭代器其实就是指针,我们可以通过指针相减的方式得到下标等信息,重新转化为string类那种插入与删除的方式
但是要注意的是,vector的insert要重新返回新的迭代器,不能忘了,其它方面就和string类的insert差不多,如下:
cpp
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _endofstorage);
//转化为string类的处理方式,即使用下标处理
size_t Pos = pos - _start;
if (_finish == _endofstorage)
{
reserve(2 * capacity());
}
size_t src = size() - 1;
size_t dest = src + 1;
while ((int)src >= (int)Pos)
{
_start[dest--] = _start[src--];
}
_start[Pos] = x;
_finish++;
//不要忘记返回,因为原本的start可能会被修改
return _start + Pos;
}
iterator insert(iterator pos, size_t n, const T& x)
{
assert(pos >= _start && pos <= _endofstorage);
size_t Pos = pos - _start;
if (size() + n > capacity())
{
reserve(size() + n);
}
size_t src = size() - 1;
size_t dest = src + n;
while ((int)src >= (int)Pos)
{
_start[dest--] = _start[src--];
}
for (size_t i = Pos; i < Pos + n; i++)
{
_start[i] = x;
}
_finish += n;
return _start + Pos;
}
erase
上面的insert我们就是将有关迭代器的问题转化为了下标问题,erase也可以按照insert的思路写,但是为了让大家体会到不同的实现方式,下面我们就直接用迭代器的方式来完成删除操作,如下:
cpp
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos + 1;
while (it < end())
{
//将数据往前挪动
*(it - 1) = *it;
}
_finish--;
return pos;
}
六、vector的默认成员函数重载
接下来我们继续来介绍vector的默认成员函数重载,它们也和string类似,但是有些许不同,如果掌握了string类的实现,下面的实现基本上也没有问题
构造函数
在构造函数部分我们会讲两个接口,一个就是普通的n个value的默认构造,一个就是用大括号初始化的默认构造,我们先来讲第一个比较简单的接口
我们要实现n个value的构造,那么我们就要预先开辟n个空间,然后写个循环将这个值尾插进入vector即可,如下:
cpp
//T()是调用对应类型的默认构造
vector(size_t n = 0, const T& x = T())
{
//start和endofstorage将会在reserve中被修改
reserve(n);
for (size_t i = 0; i < n; i++)
{
//push_back中会修改finish
push_back(x);
}
}
接下来我们就要来实现用大括号初始化vector对象,主要是我们要认识一下initializer_list,它是一个C++11标准提出的容器,拥有类似于数组的结构,它的两个成员变量是两个指针,分别指向这个数组的首和尾
这个容器就是为了拿来支持大括号初始化的,编译器会根据情况用大括号中的元素构造这个容器的对象,随后将这个对象传到构造函数中,并且这个容器也支持了迭代器,我们可以来看看:

所以我们接收到了initializer_list对象之后,可以使用迭代器遍历它,然后将其中取到的元素一一尾插到vector对象中即可,有关initializer_list的更多解析将会在以后的C++11标准介绍中进行,那么这个版本的构造函数实现如下:
cpp
vector(const initializer_list<T>& il)
{
//提前开空间提升性能
reserve(il.size());
for (auto& e : il)
{
//将其中的元素一一尾插到vector对象中
push_back(e);
}
}
拷贝构造函数
拷贝构造函数就是开空间,然后遍历目标vector对象,将其中的元素一一插入,如下:
cpp
vector(const vector& v)
{
//提前预留空间
reserve(v.capacity());
for (auto& e : v)
{
//将目标vector对象的元素一一取出插入
push_back(e);
}
}
赋值重载函数
赋值重载我们可以利用之前实现的swap函数,跟string那里的思路一致,如下:
cpp
vector& operator=(vector v)
{
//确保不是给自己赋值
if (this != &v)
{
//和string差不多,利用swap带走资源
swap(v);
}
return *this;
}
析构函数
析构函数就更简单了,直接释放对应的数组即可,如下:
cpp
~vector()
{
if (_start)
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
七、源码
cpp
#pragma once
#include <iostream>
#include <cassert>
using namespace std;
namespace TL
{
template<class T>
class vector
{
public:
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;
}
vector(size_t n = 0, const T& x = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(x);
}
}
vector(const initializer_list<T>& il)
{
reserve(il.size());
for (auto e : il)
{
push_back(e);
}
}
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
/* vector(const vector& v)
{
vector tmp;
tmp.reserve(v.capacity());
for (size_t i = 0; i < v.size(); i++)
{
tmp.push_back(v[i]);
}
swap(tmp);
}*/
vector(const vector& v)
{
//提前预留空间
reserve(v.capacity());
for (auto& e : v)
{
//将目标vector对象的元素一一取出插入
push_back(e);
}
}
vector& operator=(vector v)
{
if (this != &v)
{
//和string差不多,利用swap带走资源
swap(v);
}
return *this;
}
~vector()
{
if (_start)
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
void reserve(size_t n)
{
size_t Size = size();
size_t Capacity = capacity();
if (n <= Capacity)
{
return;
}
size_t newCapacity = 2 * Capacity;
if (newCapacity < n)
newCapacity = n;
T* tmp = new T[newCapacity];
for (size_t i = 0; i < Size; i++)
{
tmp[i] = _start[i];
}
if (_start)
delete[] _start;
_start = tmp;
_finish = _start + Size;
_endofstorage = _start + newCapacity;
}
void resize(size_t n, const T& x = T())
{
/*if (n < size())
{
_finish = _start + n;
}
else if(n < capacity())
{
for (size_t i = size(); i < n; i++)
{
push_back(x);
}
}
else
{
reserve(n);
for (size_t i = size(); i < n; i++)
{
push_back(x);
}
}*/
if (n < size())
{
_finish = _start + n;
}
else
{
reserve(n);
for (size_t i = size(); i < n; i++)
{
push_back(x);
}
}
}
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
reserve(capacity() * 2);
}
*_finish = x;
_finish++;
}
void pop_back()
{
assert(!empty());
_finish--;
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _endofstorage);
size_t Pos = pos - _start;
if (_finish == _endofstorage)
{
reserve(2 * capacity());
}
size_t src = size() - 1;
size_t dest = src + 1;
while ((int)src >= (int)Pos)
{
_start[dest--] = _start[src--];
}
_start[Pos] = x;
_finish++;
return _start + Pos;
}
iterator insert(iterator pos, size_t n, const T& x)
{
assert(pos >= _start && pos <= _endofstorage);
size_t Pos = pos - _start;
if (size() + n > capacity())
{
reserve(size() + n);
}
size_t src = size() - 1;
size_t dest = src + n;
while ((int)src >= (int)Pos)
{
_start[dest--] = _start[src--];
}
for (size_t i = Pos; i < Pos + n; i++)
{
_start[i] = x;
}
_finish += n;
return _start + Pos;
}
/*iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
size_t Pos = pos - _start;
size_t src = Pos + 1;
size_t dest = Pos;
while (src < size())
{
_start[dest++] = _start[src++];
}
_finish--;
return pos;
}*/
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos + 1;
while (it < end())
{
*(it - 1) = *it;
}
_finish--;
return pos;
}
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
T& at(size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
const T& at(size_t pos) const
{
assert(pos < size());
return _start[pos];
}
T& front()
{
assert(!empty());
return *_start;
}
T& back()
{
assert(!empty());
return *(_finish - 1);
}
const T& front() const
{
assert(!empty());
return *_start;
}
const T& back() const
{
assert(!empty());
return *(_finish - 1);
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
bool empty() const
{
return _start == _finish;
}
void clear()
{
_finish = _start;
}
private:
T* _start = nullptr;
T* _finish = nullptr;
T* _endofstorage = nullptr;
};
template<class T>
void swap(vector<T>& v1, vector<T>& v2)
{
v1.swap(v2);
}
}
那么今天关于vector的实现就讲到这里,有什么不懂欢迎私信问我,我会及时做出解答,下一篇文章开始我们学习list的使用,敬请期待吧!
bye~