目录
一、vector 的介绍
- vector 是一个类似数组但是可变大小的顺序容器。
- vector 使用连续的存储空间存储元素,所以可以像数组那样使用指针偏移来访问元素。
- vector 与数组不同的是vector的大小(size)可以动态改变。
- vector 容器可能会分配一些额外的空间用于可能的插入操作。因为数据插入时可能会伴随着重新分配空间和拷贝,此时会花费较多的时间。
- 相较于其他动态容器(deques, lists and forward_lists),vector访问元素更加高效(just like arrays)并且可以相当有效地插入或删除它的尾部数据。但是插入和删除尾部数据以外的数据,vector的表现就会更差一些。
二、vector的使用
2.1 构造函数、析构函数和赋值重载
std::vector::vector (构造函数)
- vector() 无参构造,构造空容器。
- vector(size_type n, const value_type& val = value_type())构造填充n个val的容器
- vector (const vector& x); 拷贝构造
- template <class InputIterator>
vector (InputIterator first, InputIterator last);
范围构造模板,构造一个用两个迭代器之间的值填充的容器。(区间左闭右开)
std::vector::~vector (析构函数)
- ~vector(); 销毁容器对象
std::vector::operator= (赋值重载)
- vector& operator= (const vector& x);
分配一个新容器(新容器对x拷贝构造),替换当前的容器,相应地修改容器的大小
注:
- 在使用vector之前,需要包含<vector>头文件。
void Test1()
{
vector<int> v1;
for (auto x : v1)
{
cout << x << " ";
}
cout << endl;
vector<int> v2(10,1);
for (auto x : v2)
{
cout << x << " ";
}
cout << endl;
vector<int> v3(v2);
for (auto x : v3)
{
cout << x << " ";
}
cout << endl;
int nums[] = { 1,2,3,4,5,6,7 };
int sz = sizeof(nums) / sizeof(nums[0]);
vector<int> v4(nums, nums + sz);
for (auto x : v4)
{
cout << x << " ";
}
cout << endl;
v1 = v4;
for (auto x : v1)
{
cout << x << " ";
}
cout << endl;
v1.~vector();
v2.~vector();
v3.~vector();
v4.~vector();
}
2.2 vector 的迭代器
- begin 返回指向vector第一个元素的迭代器。
iterator begin(); const_iterator begin() const;- end 返回指向vector最后一个元素下一个位置的迭代器
iterator end(); const_iterator end() const;- rbegin 返回指向vector最后一个元素的迭代器。
reverse_iterator rbegin(); const_reverse_iterator rbegin() const;- rend 返回指向vector第一个元素前一个位置的迭代器
reverse_iterator rend(); const_reverse_iterator rend() const;
void Test2()
{
int nums[] = { 1,2,3,4,5,6,7 };
int sz = sizeof(nums) / sizeof(nums[0]);
vector<int> v1(nums, nums + sz);
vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
cout << *it << " ";
it++;
}
cout << endl;
vector<int>::reverse_iterator rit = v1.rbegin();
while (rit != v1.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
}
2.3 vector 容量相关的接口
- size 返回容器元素个数
size_type size() const;- capacity 返回容器分配的空间大小
size_type capacity() const;- empty 判断容器是否为空
bool empty() const;- resize 调整容器大小,直到容器可以包含为n个元素。
void resize (size_type n, value_type val = value_type());- reserve 请求至少足够包含n个元素的空间。
void reserve (size_type n);
注:
- reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
- resize在开空间的同时还会进行初始化,影响size。
- resize中参数n小于size,保留前n个元素,移除后面的元素,capacity不变。
参数n大于size但是小于capacity,在尾部插入元素val,直至size等于n。
参数n大于capacity,分配一块更大的容器进行替换,在容器尾部插入元素val,直至size等于n。。 - reserve中参数n小于等于capacity,capacity不变。
参数n大于capacity,分配一块更大的空间进行替换,原有的数据不变,size也不变。
void Test3()
{
vector<int> v1;
cout << v1.empty() << endl;
v1.resize(10, 1);
cout << v1.empty() << endl;
for (auto x : v1)
{
cout << x << " ";
}
cout << endl;
cout << v1.size() << "\t" << v1.capacity() << endl;
v1.reserve(20);
cout << v1.size() << "\t" << v1.capacity() << endl;
v1.resize(5);
cout << v1.size() << "\t" << v1.capacity() << endl;
v1.reserve(2);
cout << v1.size() << "\t" << v1.capacity() << endl;
}
2.4 vector的元素访问和修改
- operator[] 返回一个容器中n位置元素的引用。
reference operator[] (size_type n); const_reference operator[] (size_type n) const;- push_back 在容器尾部插入一个元素val,容器size 加1。
void push_back (const value_type& val);- pop_back 移除容器中最后一个元素,容器size 减1。
void pop_back();- insert 在position指向的元素前面插入一个新的元素val或一系列元素([first,last))
iterator insert (iterator position, const value_type& val);
void insert (iterator position, size_type n, const value_type& val);
template <class InputIterator>
void insert (iterator position, InputIterator first, InputIterator last);- erase 移除一个特定的元素(position),或移除一系列元素([first,last))
iterator erase (iterator position); iterator erase (iterator first, iterator last);- clear 移除容器中的所有元素,size为0
void clear();
注:
- 在使用元素的插入或删除时,有可能使之前的迭代器失效。
- 库中的swap函数只是交换迭代器的地址,并不是挨个赋值。
- erase和clear不会使capacity减少。
void Test4()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
v1.push_back(7);
v1.pop_back();
vector<int>::iterator it = v1.begin();
it = v1.erase(it);
it = v1.insert(it, 0);
for (int i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
v1.clear();
cout << v1.size() << endl;
}
三、迭代器失效
- 在当前学习阶段,我们可以简单地把迭代器理解成一个指针,比如vector的迭代器就是T*。
- 迭代器失效就是迭代器指针指向的空间被销毁或者迭代器指针指向的位置未定义(位置与预期不符),如果继续使用已经失效的迭代器,程序可能会崩溃。
3.1 导致迭代器失效的操作
- 会引起其底层空间改变的操作,都有可能使迭代器失效(例如扩容)。
- 指定位置元素的插入或删除操作
void Test5()
{
vector<int> v{ 1,2,3,4,5 };
for (size_t i = 0; i < v.size(); ++i)
cout << v[i] << " ";
cout << endl;
auto it = v.begin();
cout << "capacity: " << v.capacity() << endl;
v.reserve(100);//扩容可能导致底层空间改变
cout << "capacity: " << v.capacity() << endl;
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
结果报异常:vector迭代器不兼容。
原因:扩容后,vector原本的值拷贝到新空间,旧空间被释放,而 it 指向的是旧空间的第一个元素的位置,对其进行操作就会程序异常
void Test6()
{
vector<int> v{ 1,2,3,4,5,6 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 3 == 0)
{
v.erase(it);
}
it++;
}
for (auto x : v)
{
cout << x;
}
cout << endl;
}
这个代码的原本意思是删除vector 中为3的倍数的元素,但程序还是会报错。
原因:
如果碰到为3的倍数的元素,执行erase操作,此时vector中元素为{1, 2, 4, 5, 6},但是it指向的元素是4(后面的元素向前赋值),此时it 进行++操作,指向5,相当于对4 没有进行检查,这是第一个错误。
第二个错误:当erase使用的迭代器指向最后一个元素,删除后 it 就会超过 end()
总结:
- 插入元素导致迭代器失效:当在vector中插入元素时,如果插入位置之前的元素将会被移动,那么指向这些元素的迭代器就会失效。
- 删除元素导致迭代器失效:当从vector中删除元素时,被删除元素之后的所有元素都会向前移动,导致指向这些元素的迭代器失效。
- 扩容导致迭代器失效:当vector的内部存储空间不足以容纳新的元素时,会进行内部的扩容操作,这可能会导致所有迭代器失效。
3.2 迭代器失效的解决方法
其实vector的编写者们早已注意到这个问题,所以他们令insert()和erase()函数结束后返回一个迭代器,insert()返回的迭代器指向新插入的第一个元素,erase()返回的迭代器指向被删除元素/区间的下一个元素。
所以,我们就可以通过更新迭代器的方式,来解决迭代器失效的问题。
在进行所有可能导致迭代器失效的操作之前,及时检查和更新迭代器的位置。
- 在插入元素之前,保存好要插入位置之后的迭代器。或者使用返回插入位置的迭代器来替代之前的迭代器。
- 在删除元素之后,使用返回删除位置的迭代器或者更新迭代器位置。
- 在进行可能导致扩容的操作之前,尽量避免持有迭代器。或者重新获取迭代器。
void Test7()
{
vector<int> v{ 1,2,3,4,5,6 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 3 == 0)
{
it = v.erase(it);
}
else
{
it++;
}
}
for (auto x : v)
{
cout << x;
}
cout << endl;
}