【C++进阶三】vector深度剖析
- 1.vector的接口函数
-
- 1.1构造和拷贝构造
- 1.2迭代器的使用
- 1.3空间容量相关函数
- 1.4find,swap和sort
- 1.5增删查改
-
- 1.5.1insert和erase
- [1.5.2 随机访问operator[ ]](#1.5.2 随机访问operator[ ])
- 2.什么是迭代器失效?
- 3.vector的深浅拷贝问题
- 4.reserve深度剖析
vector其实就是顺序容器 ,string类只用考虑存储字符,但vector中可以存储任一类型,所以vector的实现需要用模板
1.vector的接口函数
使用cplusplus(点击链接)查看vector
库中的vector的模板参数有两个
后一个是内存池,用来提升空间利用效率
内存池在现阶段暂时无需做深入了解
1.1构造和拷贝构造

三种常见构造
cpp
vector<int> v1;
vector<int> v2(10,1);//构造顺序表并初始化10个1
vector<int> v3(v2);
用迭代器区间初始化
cpp
string str("PTN");
vector<string> v(str.begin(),str.end());
1.2迭代器的使用
和string一样,vector有正向和反向两种迭代器,且使用方法和string相同
begin
:获取第一个数据位置的iterator/const_iterator
end
:获取最后一个数据下一个位置的iterator/const_iterator
rbegin
:获取最后一个数据位置的reverse_iterator
rend
:获取第一个数据前一个位置的reverse_iterato

1.3空间容量相关函数

vector的空间相关的函数和string的机会一模一样,如果不懂可以查看这篇文章string容量操作
1.4find,swap和sort
这三个函数都在头文件:algorithm中
参数是一段迭代器区间,以及在此区间你需要查找的值,找到后返回这个值对应的迭代器位置,若找不到则返回迭代器last
cpp
vector<int> v{0,1,2,3,4,5,6,7,8,9};
auto pos = find(v.begin(),v.end(),5);
cout << *pos;
使用auto是为了简写迭代器也可以用vector<int>::iterator替代
sort内部实现是快排,我们只需要传一个迭代器区间就可以将整个区间排好序
cpp
vector<int> v{5,6,3,7,8,4,1,9,2,0};
sort(v.begin().v.end());
1.5增删查改

1.5.1insert和erase

和string不同,vector的insert的参数pos不是整型,而是迭代器
默认是在pos位置前插入一个数据
insert和find常常配合在一起使用
cpp
vector<int> v{1,2,3,4,5,6,7,8,9};
auto pos = find(v.begin(),v.end(),5);
v.insert(pos,999);

和string的erase不同,vector的erase一次只删除一个数据,然而string如果使用缺省值就是将全部数据删完
vector的erase甚至可以删除一段区间
cpp
vector<int> v{1,2,3,4,5,6,7,8,9,10};
auto pos = find(v.begin(),v.end(),10);
v.erase(pos);
//删除一个区间
v.erase(v.begin()+1,v.end()-1);
1.5.2 随机访问operator[ ]

vector中最常用的是[ ],它支持随机访问
cpp
vector<int> v{0,1,2,3,4,5,6,7,8,9};
for(int i = 0;i < v.size();i++)
{
cout << v[i] << " ";
}
2.什么是迭代器失效?
迭代器失效的本质原因是:扩容后start和finish的地址发生变化,指向原先位置的迭代器失效
2.1insert
vector的每一次扩容都不是在原地扩容,而是新开辟一块儿空间后将原先的数据拷贝到新空间
cpp
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
auto pos = find(v.begin(),v.end(),2);
v.insert(pos,10);
v.insert(pos,20);
这段代码在2前面插入一个10和20,但是这段代码会出错
原因如下图:
库中的vector提供了返回值来解决此问题:

insert会返回一个迭代器,此迭代器是新插入的元素的迭代器
如果想要继续通过迭代器操作vector中的元素,只需给pos重新赋值
cpp
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
auto pos = find(v.begin(),v.end(),2);
pos = v.insert(pos,10);
v.insert(pos,20);
2.1erase
例:删除顺序表中所有的偶数
cpp
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(4);
v.push_back(6);
auto it = v.begin();
while(it != v.end())
{
if(*it % 2 == 0)
{
it = v.erase(it);
}
++it;
}
但是你会发现它并没有删除完
原因如图:
erase删除后,后面的数据会覆盖过来,此时it++无法指向原本的下一位
解决方法:删除后不用再++迭代器,只用在没删除的时候再++
cpp
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(4);
v.push_back(6);
auto it = v.begin();
while(it != v.end())
{
if(*it % 2 == 0)
{
it = v.erase(it);
}
else
{
++it;
}
}
3.vector的深浅拷贝问题
对内置类型调用默认拷贝构造函数会进行浅拷贝,所以需要我们自己来实现深拷贝
cpp
Vector(const Vector<T>& v)
{
assert(v._start && v._finish && v._endofsto);
_start = new T[v.capacity()];//给size或capacity都可以
memcpy(_start, v._start, sizeof(T) * v.size());
}
但自定义类型依旧会报错,因为自己实现的拷贝构造中memcpy也是一种浅拷贝(按字节拷贝)
深拷贝是重新开辟一块与原空间大小相同的新空间,并将原空间的数据拷贝给新空间
但是若为string 类型,本身的_str指向字符串,而新空间只是将_str拷贝过去了,两份_str依旧指向同一字符串

v2先进行析构,会调用delete[ ] ,会对数组上每个成员依次调用析构函数,此时指向的字符串就全部被析构了,v1后进行析构,但没有可以析构的字符串,所以会报错
这就是深拷贝中的浅拷贝
由于这种深浅拷贝问题是因为memcpy导致的,所以这里不能使用memcpy,只需要使用一个for循环就能解决:
cpp
Vector(const Vector<T>& v)
{
assert(v._start && v._finish && v._end_of_storage);
_start = new T[v.capacity()];//给size或capacity都可以
//memcpy(_start, v._start, sizeof(T) * v.size()); //使用memcpy时,数组是二维数组会发生问题
for (size_t i = 0; i < size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
4.reserve深度剖析
reserve只改变capacity大小,而不会改变size的大小
所以如下代码并不正确
cpp
vector<int> v;
v.reserve(10);//开辟10份空间
for(int i = 0;i < 10;i++)
{
v[i] = i;
}
size此时是0,也就是有效长度为0,虽然你开辟了10份空间,但是运算符操作[ ]的内部实现会检查下标
cpp
T& operator[](size_t pos)//[]的模拟实现
{
assert(pos < size());
return _start[pos];
}
所以使用reserve后不能直接使用[ ]
模拟实现的reserve也存在使用memcpy造成浅拷贝的问题:

将旧空间上的_str等拷贝到新空间上,释放旧空间就导致_str所指向的字符串析构
当新空间析构时,_str所指向的字符串就会造成二次析构,从而报错
更改模拟的的reserve: