ps:本文并不是对vector进行全面的讲解,笔者只针对典型的容易犯错的地方进行了详解
1.vector概述:
vector的数据安排以及操作方式,与array非常相似。俩者唯一的区别就是在于空间应用的灵活性。array是静态空间,空间一旦配置就不能再进行改变,如果需要更换大一点的空间我们需要重新开辟一块空间,将原来的数据拷贝到新空间,然后将旧空间释放。而vector会以动态开辟空间的方式进行增容。
2.vector的迭代器
实际上vector的迭代器就是一个普通指针,iterator支持iterator++,iterator–,*iterator,iterator-=,iterator+=等所有的操作
template<class T>//模板
class vector
{
public:
typedef T* iterator;//iterator为T*类型
};
2.1vector迭代器失效问题
ps:本部分有些知识可能在下文讲解
来看一段代码:
场景一(报错,程序奔溃)
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
it = find(v.begin(), v.end(), 3);
v.insert(it, 30);
v.erase(it);//程序奔溃,迭代器已经失效
场景二:(vs检查的相对严格会报错,g++不会)
将上面push的4个数据中偶数删除掉
auto it = v.begin();
while (it != v.end())//会跳过某些数据导致的迭代器失效
{
if (*it % 2 == 0)
{
v.erase(it);//对删除或者插入后的迭代器进行操作可能导致迭代器失效出现问题
}
it++;
}
2.2vector的数据结构
vector的底层实际上是由3个迭代器实现的,如下面的代码。
template<class T>
class vector
{
public:
typedef T* iterator;
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
而这3个迭代器是怎么样维护vector的呢?如图所示
从图中我们可以得出
size_t size(){ return finish - start;}
size_t capicaty(){ return endofstorage - start;}
3.vector的增容问题
3.1不同编译器下的vector增容
size_t sz;
std::vector<int> foo;
sz = foo.capacity();
std::cout << "making foo grow:\n";
for (int i = 0; i < 100; ++i) {
foo.push_back(i);
if (sz != foo.capacity()) {
sz = foo.capacity();
std::cout << "capacity changed: " << sz << '\n';//vs下1.5倍增容
}
}
我们在vs2013下测试vector的容量为1.5倍的增长
注意:并不是所有vector都是1.5倍增长,比如在g++就是2倍增长。
3.2vector增容源码刨析
vector下的增容有一个非常重要的函数叫reserve(),这个函数是将空间直接增大到n
void Reserve(size_t n);//参数为你所要增大到的空间长度
我们接下来得谈一谈vector的增容过程了,虽然说vector是动态开辟,但是实际上我们在增容时还是避免不了以下几个步骤:
- 重新配置空间
- 元素移动
- 释放空间
那么代码来了:
void Reserve(size_t n)
{
if (n > capicaty())
{
T* tmp = new T[n];//开空间
memcpy(tmp,_start,size(T)*size());//拷贝数据
delete[] _start;//释放旧空间
_start = tmp;//重置迭代器
_finish = _start + size();
_endofstorage = _start + capicaty();//this指针调用size()capicaty()
}
}
现在问题来了,请找出上面代码中的错误?这个代码可以导致程序奔溃一万次。。。。
不妨我们一点一点来看:
问题1:
memcpy(tmp,_start,size(T)*size());
实际上这句代码本身时没有毛病的,我们来看一眼无参构造函数源码
Vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
有的同学很快发现了问题,_start在无参构造时初始化成了空指针,假设我们对空指针进行解引用就会导致程序奔溃
所以我们进行了第一次修改:
if(size() > 0)//顺表表中有数据才进行拷贝,否则只开空间
memcpy(tmp,_start,size(T)*size());
问题2:
仔细思考这里加size()和capicaty()对么?
_start = tmp;
_finish = _start + size();
_endofstorage = _start + capicaty();
我们这样是在用旧的finish和end减去新的start,所以会减出一个非常大的值(可能),实际上这是一个迭代器失效的问题(上文讲解)
我们来进行第二次修改:
void Reserve(size_t n)
{
if (n > capicaty())
{
size_t size = size();//记录原空间大小
T* tmp = new T[n];//开空间
if(n > size())
{
memcpy(tmp,_start,size(T)*size());//拷贝数据
}
delete[] _start;//释放旧空间
_start = tmp;//重置迭代器
_finish = _start + size;
_endofstorage = _start + n;//直接加上n就是新空间大小
}
}
当我们兴高采烈的觉得终于结束的时候,程序又奔溃了。。。
我们写下了如下代码:我们在打印时正常,却在最后奔溃,经过调试以及推断,我们将问题锁定到了析构函数
vector<string> v;
v.push_back("hello");
深拷贝浅拷贝
我们知道c++6大默认成员函数中,编译器生成的的拷贝构造函数,它事实上做的事情为浅拷贝,也叫值拷贝
我们使用日期类举个例子:
Date(const Date& d)//只拷贝了值
{
_year = d._year;
_month = d._month;
_day = d._day;
}
来看字符串的拷贝,他们经历了图中的代码后实际上指向了同一个字符串,然后析构函数调用将同一块空间释放俩次,打个比方你在某个保险柜放了一堆软妹币,你将这笔钱归还了银行,你走人了,下一次有个人又存了一笔钱在这个保险柜,你非要再一次归还给银行,emmmm。。。。
所以深拷贝必须重新开辟空间并把值拷贝下来
我们进行了第三次修改:
这也就是我们函数的终极版本
void Reserve(size_t n)
{
if (n > Capicaty())
{
size_t size = Size();
T* tmp = new T[n];
if (size > 0)
{
for (size_t i = 0; i < size; i++)
{
tmp[i] = this->_start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + size;
_endofstorage = _start + n;
}
}
4.vector的插入问题
我们先来看看我们是怎么push_back的:
实际上我们会复用insert这个接口:
void PushBack(const T& x)
{
Insert(_finish, x);
}
我们来看看insert怎么实现
void Insert(iterator pos, const T& x)
{
if (_finish == _endofstorage)
{
size_t newcapicaty = Capicaty() == 0 ? 15 : Capicaty()*2;
Reserve(newcapicaty);
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
++_finish;
}
上面的代码实际上是有问题的,通过我们上面对迭代器失效的讲解,很多同学一眼就能看出这个问题,通过第一条判断语句,我们在插入之前很有可能已经进行了增容,会导致pos位置迭代器的失效,所以我们必须重置pos位置的迭代器
下面的代码基本没问题了
void Insert(iterator pos, const T& x)
{
size_t n = pos - _start;
if (_finish == _endofstorage)
{
size_t newcapicaty = Capicaty() == 0 ? 15 : Capicaty()*2;
Reserve(newcapicaty);
}
pos = _start + n;
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
++_finish;
}
5.vector的拷贝构造和重载=问题
相信大家对拷贝构造已经有了明确的了解,使用下面方式就可以避免浅拷贝的问题
Vector(const Vector<T>& v)
{
_start = new T[v.Size()];
for (size_t i = 0; i < v.Size(); i++)
{
_start[i] = v[i];
}
_finish = _start + v.Size();
_endofstorage = _start + v.Capicaty();
}
5.1operator=问题
一种非常现代的写法
Vector<T>& operator=(Vector<T> v)//形式参数就是一份临时的拷贝
{
Swap(v);
return *this;
}
void Swap(Vector<T>& v)
{
swap(_start, v._start);
swap(_finish, v._finish);
swap(_endofstorage, v._endofstorage);
}
上面代码怎么理解???
总结
实际上到这里我们所讲的上文的知识重点在于理解深拷贝和迭代器失效问题,如果读者掌握了以上俩个关键的知识点,其余的问题我相信都会迎刃而解。
附上笔者的vector,有问题欢迎指出
namespace my_vector//简单的实现vector
{
template<class T>
class Vector
{
public:
typedef T* iterator;
Vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
Vector(const Vector<T>& v)
{
_start = new T[v.Size()];
for (size_t i = 0; i < v.Size(); i++)
{
_start[i] = v[i];
}
_finish = _start + v.Size();
_endofstorage = _start + v.Capicaty();
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
size_t Size() const
{
return _finish - _start;
}
size_t Capicaty() const
{
return _endofstorage - _start;
}
T& operator[](size_t pos)
{
assert(pos < Size())
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < Size());
return _start[pos];
}
void Reserve(size_t n)
{
if (n > Capicaty())
{
size_t size = Size();
T* tmp = new T[n];
if (size > 0)
{
for (size_t i = 0; i < size; i++)
{
tmp[i] = this->_start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + size;
_endofstorage = _start + n;
}
}
void Resize(size_t n, const T& value = T())
{
if (n > Capicaty())
{
Reserve(n);
}
if (n < Size())
{
_finish = _start + n;
}
else
{
while (_finish < _start + n)
{
*_finish = value;
++_finish;
}
}
}
void PushBack(const T& x)
{
Insert(_finish, x);
}
void PopBack()
{
Erase(_finish - 1);
}
void Insert(iterator pos, const T& x)
{
size_t n = pos - _start;
if (_finish == _endofstorage)
{
size_t newcapicaty = Capicaty() == 0 ? 15 : Capicaty()*2;
Reserve(newcapicaty);
}
pos = _start + n;
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
++_finish;
}
void Erase(iterator pos)
{
assert(pos < _finish && pos >= _start);
iterator end = pos + 1;
while (end < _finish)
{
*(end - 1) = *end;
++end;
}
--_finish;
}
Vector<T>& operator=(Vector<T> v)
{
Swap(v);
return *this;
}
void Swap(Vector<T>& v)
{
swap(_start, v._start);
swap(_finish, v._finish);
swap(_endofstorage, v._endofstorage);
}
~Vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}