vector的底层也是一个动态数组,他与 string 的区别就是,string 是专门用来存储字符类数据的,为了兼容C语言,使用C语言的接口,在string的动态数组内都会都开一块空间用来存 \0 ,而vector则不会。
首先我们要知道的就是,vector是一个类模板,他的内存管理是使用空间配置器,我们就简单点直接使用new和delete来管理内存。其他的一些接口什么的,vector与string差别不大,vector有的string基本都有,实现起来我们也挑一些重点来实现。而对于vector的成员变量,我们则不是采用string的一个指针两个整型的实现,而是使用三个迭代器,_start,_finish,_end_of_storage,这是参考Linux的库的实现,而我们实现vector时迭代器就直接使用的原生的指针。
_start 迭代器是数组的开头,_finish是最后一个数据的下一个位置,_end_of_storage指向的是申请的内存块的下一个位置。
使用三个迭代器的实现,在后续需要挪数据的时候会很方便。
无参构造函数与析构
无参构造不用说,很简单也没什么含量,都初始化为空指针就行了
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
}
完成了无参构造函数之后,我们先去实现其他的一些基本的函数,最后再来解决其他的三个构造
析构函数就是释放_start指向的空间
//析构
~vector()
{
delete[]_start;
_start = _finish = _end_of_storage = nullptr;
}
size与capacity和判空
size与capacity的实现其实很简答,我们之前学过数组中同类型指针相减,得到的是这两个指针之间的该类型的数据的个数,于是我们就可以用这三个迭代器来得到size和capacity
//size
size_t size()const
{
return _finish - _start;
}
//capacity
size_t capacity()const
{
return _end_of_storage - _start;
}
//判空
bool empty()const
{
return _start == _finish;
}
方括号的重载与at
vector由于还是动态数组实现的,所以我们还是支持直接是用下标访问的方式,所以方括号重载的实现还是有必要的,方括号重载的越界检查我们直接使用assert暴力检查,库里面实现的就是assert。同时at的功能虽然和方括号一样,但是他和方括号的区别在于 : 1 方括号是运算符重载,而at是普通的成员函数。2 越界的检查不一样,方括号的越界检查使用assert,而at是抛异常,我们可使用vs的库试一下
由于我们还没学习异常。也直接用assert判断就行了。同时,由于有普通对象和const对象的访问,返回的引用的权限不一样,所以我们需要实现两个版本
//普通对象 [ ]
reference operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
//const对象 [ ]
const_reference operator[](size_t pos)const
{
assert(pos < size());
return _start[pos];
}
//普通对象 at
reference at(size_t pos)
{
assert(pos < size());
return _start[pos];
}
//const对象 at
const_reference at(size_t pos)const
{
assert(pos < size());
return _start[pos];
}
对于下标为pos位置的数据的访问,我们可以直接使用 _start[pos]来访问,等价于*(_start+pos)
由于vector也就是普通的数组的比较是没有意义的,所以一般只重载[ ] 和=
swap
为什么要实现swap呢?
首先vector的库里面是有swap的,用于交换两个对象的数据,但是算法库里面也有swap,
他们的区别在于,算法库里的swap有三次拷贝构造,如果要交换的是两个自定义类型的对象,代价很大,而我们的类里面的成员函数swap,我们可以使用算法库的swap对对象里的成员变量进行交换,避免了深拷贝。
//swap
void swap(rvector<T>& v)
{
//要使用算法库里面的swap要指定命名空间域,否则会优先匹配当前命名空间的swap函数
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
同时我们实现swap也是为了下面的拷贝构造和赋值重载做准备
赋值重载
vector的赋值重载显然是深拷贝,我们有一种很简洁的代码写法来完成这个深拷贝,就是利用拷贝构造,我们可以传值传参,将我们的this与生成的临时的形参进行交换
而当v2不为空时也是一样的,无非就是将这临时对象与当前对象的空间换了一下,出作用域之后,临时对象指向的空间就被释放了,不会影响外面等号右边的变量。
//赋值重载
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
push_back和pop_back
尾插的实现就很简单了,首先检查是否扩容,然后直接在 _finish位置插入数据并且更新 _finish就行了。而尾删的操作则是判断是否为空,然后直接 -- _finish 就行了。
void push_back(const_reference x)
{
if (_finish == _end_of_storage)
{
size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
}
*_finish = x;;
++_finish;
//insert(beign(),x);//复用insert
}
void pop_back()
{
assert(size()>0);
--_finish;
}
拷贝构造
我们需要考虑当 T 为需要深拷贝的自定义类型这种情况,要做深层次的深拷贝。
如果数据只是内置类型或者只需要浅拷贝的自定义类型的话,我们就只需要memcpy就行了,但是如果是需要深拷贝的数据的话,比如 vector<vector<int>> ,数据类型是 vector<int> ,需要深拷贝,如果只是简单的memcpy
//如果只是内置类型或者浅拷贝自定义类型
//先扩容
reserve(v.capacity());//函数内部会修改_end_of_storage
//直接内存拷贝
memcpy(_start,v._start,v.size()*sizeof(T));
_finish = _start + v.size();
我们发现这时候两个对象虽然是不同的空间,但是里面的 vector<int> 数据指向的空间是同一块,这其实还没有达到我们想要的深拷贝。
对于我们来说目前也没有什么更好的解决方法,可以利用上面实现的赋值重载,赋值重载去对每一个数据都深拷贝,
//拷贝构造
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
//先扩容
reserve(v.capacity());
//或者先扩size,再使用赋值重载进行深拷贝
_finish = _start + v.size();
for (size_t i = 0; i < size(); ++i)
{
_start[i] = v[i];
}
}
同时,拷贝构造完成之后,不管再多层,他们都有对应的拷贝构造,但是可能有人会对这里的赋值重载产生疑惑,认为赋值重载的前提是实现拷贝构造,而我们这里的拷贝构造又用了赋值重载,会不会递归死循环了,其实是不会的,就比如这里的vector<vector<int>> ,会生成 vector<int> 和vector<vector<int>>的模板,而在vector<vector<int>>中拷贝构造的赋值重载是属于 vector<int>实例出来的赋值重载,而这个赋值重载调用的是 int 的拷贝构造,也就是内置类型的传值调用,所以这里的逻辑是没有问题的,拷贝构造和赋值重载的互相调用会在内置类型(或者浅拷贝的自定义类型)停下来,然后回归。总之就是 赋值重载的深拷贝是调用 T 类型的拷贝构造
reserve
reserve函数与string一样,只会扩容,不会缩容,同时不会改变size。但是扩容之后需要解决将原空间的数据拷贝到新空间的问题,我们可以直接使用指针来进行边界控制。
但是同时,我们也要考虑 数据类型是需要深拷贝的自定义类型的情况,比如 vector<vector<int>> ,如果只是单纯的memcpy,也会造成浅拷贝的问题,也就是原空间的 vector<int>对象与新空间的vector<int>对象指向相同的内存空间,当时放掉原空间之后,新空间指向的内存也被释放掉了。
解决方法还是跟上面的深拷贝一样,使用数据自身的赋值重载来拷贝,虽然效率低了点,但是能确保不会出问题。
迭代器相关
//迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const iterator cbegin()
{
return _start;
}
const iterator cend()
{
return _finish;
}
insert和erase
iterator insert(iterator pos, const_reference x=val_type())//缺省值
{
//检查越界
assert(pos >= _start);
assert(pos <= _finish);//第一次插入pos==_finish 同时要支持尾插
//检查是否扩容
if (_finish == _end_of_storage)
{
//扩容之后pos就失效了,所以要记录pos的相对位置
size_t pos_index = pos - _start;
size_t newcapacity = (capacity() == 0 ? 4 : 2 * capacity());
reserve(newcapacity);
//更新pos
pos = _start + pos_index;
}
//挪动数据 从后往前,往后挪数据
iterator end = _finish;
while (end != pos)
{
*end = *(end - 1);
--end;
}
//更新_finish
++_finish;
//插入新数据
*pos = x;
//返回插入的数据的迭代器
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
//挪数据覆盖
iterator begin = pos+1;
while (begin < _finish)
{
*(begin - 1) = *begin;
begin++;
}
//返回删除的下一个数据
return pos;
}
insert和erase都要注意迭代器失效的问题,比如insert的时候,如果扩容了,那么传过来的pos指向的空间就可能已经被释放了,所以要更新pos指向新空间的该元素的位置,同时,正常来说insert之后,函数外面的pos就不能用了,因为我们是传值的,函数内更新了pos不会影响函数外的pos,这时候如果我们要更新函数外面的pos,可以insert返回心得pos的值。 erase也是一样的,因为erase可能是删除最后一个数据,这时候pos的位置就是_finish 了,虽然pos没有出我们开辟的内存空间,但是在我们看来,它指向finish就已经算是越界了,不能继续使用,同样,要在函数外面更新pos也是用erase的返回值更新。
resize
//resize
void resize(size_t n, val_type x = val_type())//缺省参数
{
//扩容检查
if (n > capacity())
{
reserve(n);
}
//是否需要加数据
if (n > size())
{
while (_finish < _start + n)
{
*_finish=x;
++_finish;
}
}
}
剩下的两个构造函数
一个是迭代器区间来构造初始化。为什么这里的构造函数是模板呢? 因为我们可能不是使用vector<T>的数据结构来初始化我们的对象,也可能是用其他的数据结构比如链表等的数据来初始化vector,这也是可以的,只要传迭代器区间就可以
template<typename InputIterator>
vector(InputIterator begin, InputIterator end)
{
//push_back就行了
while (begin != end)
{
push_back(*begin);
++begin;
}
}
还有n个val初始化,这里我们需要注意的是,我们不能做到跟库里一样,n的参数类型要用int,为什么呢?
//n个val初始化
vector(int n, T val)
{
for (int i = 0; i < n; ++i)
{
push_back(val);
}
}
比如我们可以这样构造 vector<int> v(10,5) 这时候,如果我们的n的类型是size_t 的话,由于编译器会把 10 和5 识别为 int 类型,这时候传给构造函数的两个参数都是 int ,如果去匹配这个构造函数的话, n 还需要进行整型提升。而如果去匹配上面的迭代器区间的模板,因为模板的只有一个参数类型,两个迭代器区域间都是一样的类型,所以不用进行隐式类型转换,因此他与迭代器区间的构造函数更加匹配,编译器就会去调用迭代器区间的构造函数,这时候就会出问题。 有两种解决方法,一种就是我们上面写的,但是这样做就与库不一致了 。另外一种就是我们不要使用原生的指针作为迭代器的类型,这种方法我们目前还不会用
完整代码
namespace MY_vector
{
template<typename T>
class vector
{
public:
typedef T val_type;
typedef T& reference;
typedef const T& const_reference;
typedef T* iterator;
//无参构造
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
}
//析构
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage=nullptr;
}
//size
size_t size()const
{
return _finish - _start;
}
//capacity
size_t capacity()const
{
return _end_of_storage-_start;
}
//empty
bool empty()const
{
return _start == _finish;
}
//尾插
void push_back(val_type x)
{ /*
//检查扩容
if (_finish== _finish)
{
size_t newcapacity = (capacity() == 0 ? 4 : 2 * capacity());
reserve(newcapacity);
}
//插入数据
*_finish = x;
++_finish;*/
insert(begin(), x);
}
//尾删
void pop_back()
{
assert(!empty());
--_finish;
}
iterator insert(iterator pos, const_reference x=val_type())//缺省值
{
//检查越界
assert(pos >= _start);
assert(pos <= _finish);//第一次插入pos==_finish 同时要支持尾插
//检查是否扩容
if (_finish == _end_of_storage)
{
//扩容之后pos就失效了,所以要记录pos的相对位置
size_t pos_index = pos - _start;
size_t newcapacity = (capacity() == 0 ? 4 : 2 * capacity());
reserve(newcapacity);
//更新pos
pos = _start + pos_index;
}
//挪动数据 从后往前,往后挪数据
iterator end = _finish;
while (end != pos)
{
*end = *(end - 1);
--end;
}
//更新_finish
++_finish;
//插入新数据
*pos = x;
//返回插入的数据的迭代器
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
//挪数据覆盖
iterator begin = pos+1;
while (begin < _finish)
{
*(begin - 1) = *begin;
begin++;
}
//返回删除的下一个数据
return pos;
}
//[ ] 重载
reference operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const_reference operator[](size_t pos)const
{
assert(pos < size());
return _start[pos];
}
//at
reference at(size_t pos)
{
assert(pos<size());
return _start[pos];
}
const_reference at(size_t pos)const
{
assert(pos < size());
return _start[pos];
}
//swap
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
//= 重载
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
//扩容 reserve
void reserve(size_t n)
{
if (n > capacity())
{
iterator tmp = new val_type[n];
//记录原来的数据个数,用来更新_finish
size_t oldsize = size();
//判断是否需要挪数据
if(_start) //_start!=nullptr
{
for (size_t i = 0; i < oldsize; ++i)
{
tmp[i] = _start[i];
}
//释放原空间
delete[]_start;
}
//更新迭代器
_start = tmp;
_finish = _start + oldsize;
_end_of_storage = _start + n;
}
}
//拷贝构造
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
//先扩容
reserve(v.capacity());
//或者先扩size,再使用赋值重载进行深拷贝
_finish = _start + v.size();
for (size_t i = 0; i < size(); ++i)
{
_start[i] = v[i];
}
}
//resize
void resize(size_t n, val_type x = val_type())//缺省参数
{
//扩容检查
if (n > capacity())
{
reserve(n);
}
//是否需要加数据
if (n > size())
{
while (_finish < _start + n)
{
*_finish=x;
++_finish;
}
}
}
//迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const iterator cbegin()
{
return _start;
}
const iterator cend()
{
return _finish;
}
template<typename InputIterator>
vector(InputIterator begin, InputIterator end)
{
//push_back就行了
while (begin != end)
{
push_back(*begin);
++begin;
}
}
//n个val初始化
vector(int n, T val)
{
for (int i = 0; i < n; ++i)
{
push_back(val);
}
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
//测试一维
void test1()
{
vector<int>v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
vector<int> v2(v1);
vector<int>v3;
v3.resize(8);
v3 = v1;
v3.reserve(10);
v2.pop_back();
v2.pop_back();
v2.pop_back();
v2.pop_back();
//v2.pop_back();
//v2.pop_back();
//v2.pop_back();
v3.insert(v3.begin(), 6);
v3.insert(v3.begin(), 6);
v3.insert(v3.end()-1, 6);
v3.insert(v3.end() - 1, 6);
v3.insert(v3.end(), 6);
v3.insert(v3.end(), 6);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
}
//测试二维
void test2()
{
vector<int>v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<vector<int>> vv1;
vv1.push_back(v);
vv1.push_back(v);
vv1.push_back(v);
vv1.push_back(v);
vv1.push_back(v);
vector<vector<int>> vv2(vv1);
vv2.pop_back();
vv2.pop_back();
vector<vector<int>> vv3;
vv3.resize(2);
vv3.reserve(6);
vv3.push_back(v);
vv3.push_back(v);
vv3 = vv1;
vv3.push_back(v);
vv3.push_back(v);
cout << "vv1" << endl;
for (auto ev : vv1)
{
for (auto e : ev)
{
cout << e << " ";
}
cout << endl;
}
cout << "vv2" << endl;
for (auto ev : vv2)
{
for (auto e : ev)
{
cout << e << " ";
}
cout << endl;
}
cout << "vv3" << endl;
for (auto ev : vv3)
{
for (auto e : ev)
{
cout << e << " ";
}
cout << endl;
}
}
}