什么是vector?
上次介绍了STL的string类,这次介绍一下STL中的vector,vector类似于数据结构中的顺序表,那么string也是一个数组,它与vector的区别是什么呢?首先对于string类,因为其是字符数组,里面存有'\0',其次其可以实现字符的比较或者'+='之类的操作符重载。而vector里没有’\0‘,'+=',因为’+=‘对于其毫无意义。
1、vector是一个表示可变大小数组的序列容器。
2、就像数组一样,其也采用连续的空间来存储元素,也就是支持随机访问,和数组一样高效,但其大小是可以动态改变的。
vector的底层实现
成员变量
首先为了泛化模板,我们引入一个类模板
template<class T>
首先,我们给出vector类的成员变量
iterator _start;
iterator _finish;
iterator _endofstorage;
之前的成员变量都是用一个指针两个变量表示,现在用三个指针表示,这里定义vector的三个成员变量为迭代器,_start指向的是第一个元素,_finish指向的是容器中最后一个有效元素的下一个位置,_endofstorage表示的是容器的容量,也用指针来表示。
构造函数
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
这里利用初始化列表初始化,给三个指针赋空。
拷贝构造
因为我们在拷贝构造时需要开辟新的空间,所以不能使用编译器默认的拷贝构造函数,编译器给的默认的拷贝构造函数只是浅拷贝(值拷贝),在调用析构函数时会报错,所以这里我们必须自己实现拷贝构造函数。
vector(const vector<T>& v)
{
//开空间
_start = new T[v.capacity()];
_finish = _start;
_endofstorage = _start + v.capacity();
//把值拷贝过来
for(size_t i = 0; i < v.size();++i)
{
*_finish = v[i];
++_finish;
}
}
上面的方法是对调用拷贝构造的对象开跟拷贝对象一样大的空间再用循环不断赋值。下面给出第二中方法,考虑先对调用拷贝构造对象的成员变量给空指针,再用调用reserve函数开好空间(reserve函数的作用是开固定大小的空间),再将拷贝对象中的数据不断push进去。
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
reserve(v.capacity());
for (const auto& e : v)
{
push_back(e);
}
}
这里需要注意的是利用初始化列表提前给空指针(可能会出现内存泄漏的问题),其次我们必须提前开好空间,不然后面调用push_back会一直申请空间。上面的reserve函数和push_back函数会在后面给出。push_back和reserve函数后面会给出。
析构函数
~vector()
{
if (_start)
{
delete[]_start;
_start = _finish = _endofstorage = nullptr;
}
}
reserve
void reserve(size_t n)//开辟n个空间
{
size_t sz = size();//必须先给出这个
if (n > capacity())//这里直接比较的是容量大小,不是判断空间够不够
{
T* tmp = new T[n];//先开新空间
if (_start)
{
for (size_t i = 0; i < sz; ++i)
{
tmp[i] = _start[i];
}
delete[]_start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
在实现reserve的时候需要注意的是:
1、在创建新空间之后,要将原来的数据拷贝过来时,如果vector里面是自定义类型的话会涉及到深拷贝问题,所以这里需要利用循环进行赋值。
2、为什么刚开始就要给出用一个变量来存储size()呢?因为在最后重新赋值时,_finish不能直接用size()+_start来赋值,因为类中的size()是_finish-_start出来的,而这个时候_start已经指向了一段新的空间了而_finish指向的还是原来的那段空间,所以两个减出来是四不像。
resize
void resize(size_t n, const T& val=T())
if (n < size())
{
_finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
}
resize函数的作用是将容器的容量变为指定大小,对于容器中多出来的没有存放有效字符的数组空间赋指定的值。
该函数在传参的时候,因为不知道调用该函数时要传过来的类型是什么,所以要给出缺省值,在调用该函数时,如果没有指定要赋的值,则会调用默认的参数。
给出的缺省值的形式为T&= val=T(),这里可以理解为C++对内置类型做了一些重新解释,可以认为其有构造函数,如
int i=int(); int i=int(1);double d=double();double e=double(1.1);
insert
iterator insert(iterator pos, const T& x)//在pos处插入x
{
assert(pos <= _finish);
//先判断容量够不够
if (_finish == _endofstorage)
{
size_t n = pos-_start;
size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
reserve(newcapacity);
pos = _start + n;
}
iterator end = _finish - 1;
while (end >= pos)//
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
}
insert的作用是在指定位置插入指定的值, 判断容量不够时需要开辟新空间来增容,这里需要注意点是我们需要在后面挪动数据,我们用的是迭代器挪动数据,所以pos点我们需要提前给好,不然会出现迭代器失效的问题,因为我们使用reserve扩容了,pos如果不改的话指向的还是原来的位置。
push_back
void push_back(const T& x)//这里用引用,如果T是string或者vector会涉及到深拷贝,为了避免拷贝构造
{
//判断容量够不够
if (_finish == _endofstorage)//这里判断容量能否用类里面的函数size()和capacity(),不行
{
size_t newcapacity = capacity() == 0 ? 2 :capacity() * 2;
reserve(newcapacity);//开辟空间
}
//赋值
*_finish = x;
++_finish;
}
既然我们前边已经实现了指定位置插入函数insert,push_back就是在容易尾部进行插入,所以直接调用insert函数就行
insert(end(), x);
erase
iterator erase(iterator pos)
{
assert(pos <= _finish);
iterator ps = pos;
iterator end = _finish - 1;
while (end >= ps)//挪动数据
{
*ps = *(ps+ 1);
++ps;
}
--_finish;
return pos;
}
赋值重载
vector<T>& operator=(const vector<T>& v)//赋值拷贝涉及到深拷贝,自己写
{
if (this != &v) {
delete[]_start;
_start = new T[v.capacity()];
memcpy(_start, v._start, sizeof(T) * v.size());
}
return *this;
}
下面给出简单的写法
vector<T>& operator=( vector<T>v)//这里传值拷贝v就成了v1,v3再与其交换,v出了作用域之后会自己调用析构函数,一举两得
{
swap(v);
return *this;
}
这里需要自己实现swap函数,自带的swap函数不行
void swap(vector<T> v)//自己实现一个swap函数,不然调用库里面的swap函数代价会比较大,可能会涉及到三个深拷贝
{
::swap(_start,v._start);//两个swap重名,但是下面这里用的是库函数里面的,是全局的函数,所以加上限定符
::swap(_finish, v._finish);
::swap(_endofstorage, v._endofstorage);
}
迭代器
typedef T* iterator;//可读可写的迭代器
typedef const T* const_iterator;//只读的迭代器
//迭代器
iterator begin()
{
return _start;
}
const_iterator begin() const
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator end()const
{
return _finish;
}
其他函数运算符重载
T& operator[](size_t i)
{
assert(i < size());//这里可以不用size_t sz = _finish - _start;来判断直接调用size()
return _start[i];
}
const T& operator[](size_t i)const
{
assert(i < size());
return _start[i];
}
void pop_back()//尾删
{
/*assert(_start < _finish);
--_finish;*/
erase(end() - 1);
}
size_t size()const
{
return _finish-_start;
}
size_t capacity()const
{
return _endofstorage-_start;
}