[C++系列] 52. vector使用简介及迭代器失效问题详解

1. vector的介绍及使用

1.1 vector的介绍

1.vector是表示可变大小数组的序列容器。
2.就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3.本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
4.vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5.因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6.与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和forward_lists统一的迭代器和引用更好。

学习方法:使用STL的三个境界:能用,明理,能扩展 ,那么下面学习vector,我们也是按照这个方法去学习。

1.2 vector的使用

vector学习时一定要学会查看文档:vector的文档介绍,vector在实际中非常的重要,在实际中我们熟悉常见的接口就可以,下面列出了哪些接口是要重点掌握的。

1.2.1 vector的定义
构造函数声明
接口说明
vector()无参构造
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector(const vector &x)拷贝构造
vector(InputItrtator first, InputIterator last)使用迭代器进行初始化构造
// constructing vectors
#include <iostream>
#include <vector>

int main()
{
   // constructors used in the same order as described above:
   std::vector<int> first;                                // empty vector of ints
   std::vector<int> second(4, 100);                       // four ints with value 100
   std::vector<int> third(second.begin(), second.end());  // iterating through second
   std::vector<int> fourth(third);                       // a copy of third

   // 下面涉及迭代器初始化的部分
   // the iterator constructor can also be used to construct from arrays:
   int myints[] = { 16,2,77,29 };
   std::vector<int> fifth(myints, myints + sizeof(myints) / sizeof(int));

   std::cout << "The contents of fifth are:";
   for (std::vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)
   	std::cout << ' ' << *it;
   std::cout << '\n';

   return 0;
}
1.2.2 vector iterator 的使用
iterator的使用
接口说明
begin()获取最后一个数据的下一个位置的iterator
end() )构造并初始化n个val
rbegin()获取最后一个数据位置的reverse_iterator
rend()获取第一个数据前一个位置的reverse_iterator
cbegin()获取第一个数据位置的const_iterator
cend()获取最后一个数据的下一个位置的const_iterator

在这里插入图片描述
在这里插入图片描述
迭代器区间均为左闭右开这点很重要

#include <iostream>
#include <vector>
using namespace std;
 
void PrintVector(const vector<int>& v)
{
    // 使用const迭代器进行遍历打印
    vector<int>::const_iterator it = v.cbegin();
    while (it != v.cend())
    {
cout << *it << " ";
        ++it;
    }
    cout << endl;
}
 
int main()
{
    // 使用push_back插入4个数据
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
 
    // 使用迭代器进行遍历打印
    vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
 
    // 使用迭代器进行修改
    it = v.begin();
    while (it != v.end())
    {
        *it *= 2;
        ++it;
    }
 
    // 使用反向迭代器进行遍历再打印
    vector<int>::reverse_iterator rit = v.rbegin();
    while (rit != v.rend())
    {
        cout << *rit << " ";
        ++rit;
    }
    cout << endl;
 
    PrintVector(v);
 
    return 0;
}
1.2.3 vector 空间增长问题
容量空间接口说明
size()获取数据个数
capacity()获取容量大小
empty()判断是否为空
void resize (size_type n, value_type val = value_type());改变vector的size
void reserve (size_type n);改变vector放入capacity

capacity的代码在vs和g++下分别运行会发现,**vs下capacity是按1.5倍增长的,g++是按2倍增长的。**这个问题经常会考察,不要固化的认为,顺序表增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。resize在开空间的同时还会进行初始化,影响size。

 // vector::capacity
#include <iostream>
#include <vector>
 
int main ()
{
  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:运行结果:
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 3
capacity changed: 4
capacity changed: 6
capacity changed: 9
capacity changed: 13
capacity changed: 19
capacity changed: 28
capacity changed: 42
capacity changed: 63
capacity changed: 94
capacity changed: 141
 
g++运行结果:
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128
// vector::reserve
#include <iostream>
#include <vector>
 
int main ()
{
  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';
    }
  }
 
  std::vector<int> bar;
  sz = bar.capacity();
  bar.reserve(100);   // this is the only difference with foo above
  std::cout << "making bar grow:\n";
  for (int i=0; i<100; ++i) {
    bar.push_back(i);
    if (sz!=bar.capacity()) {
      sz = bar.capacity();
      std::cout << "capacity changed: " << sz << '\n';
    }
  }
  return 0;
}
// vector::resize
#include <iostream>
#include <vector>
 
int main ()
{
  std::vector<int> myvector;
 
  // set some initial content:
  for (int i=1;i<10;i++)
      myvector.push_back(i);
 
  myvector.resize(5);
  myvector.resize(8,100);
  myvector.resize(12);
 
  std::cout << "myvector contains:";
  for (int i=0;i<myvector.size();i++)
    std::cout << ' ' << myvector[i];
  std::cout << '\n';
 
  return 0;
}
1.2.3 vector 增删改查
vector增删查改接口说明
void push_back (const value_type& val);尾插
void pop_back();尾删
InputIterator find (InputIterator first, InputIterator last, const T& val);查找。(注意这个是算法模块实现,不是vector的成员接口)
iterator insert (iterator position, const value_type& val);在position之前插入val
iterator erase (iterator position);删除position位置的数据
void swap (vector& x);交换两个vector的数据空间
reference operator[] (size_type n);像数组一样访问
// push_back/pop_back
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	int a[] = { 1, 2, 3, 4 };
	vector<int> v(a, a + sizeof(a) / sizeof(int));

	vector<int>::iterator it = v.begin();
	while (it != v.end()) {
		cout << *it << " ";
		++it;
	}
	cout << endl;

	v.pop_back();
	v.pop_back();

	it = v.begin();
	while (it != v.end()) {
		cout << *it << " ";
		++it;
	}
	cout << endl;

	return 0;
}
// find / insert / erase
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
 
int main()
{
    int a[] = { 1, 2, 3, 4 };
    vector<int> v(a, a + sizeof(a) / sizeof(int));
 
    // 使用find查找3所在位置的iterator
    vector<int>::iterator pos = find(v.begin(), v.end(), 3);
    
    // 在pos位置之前插入30
    v.insert(pos, 30);
 
    vector<int>::iterator it = v.begin();
    while (it != v.end()) {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
 
    pos = find(v.begin(), v.end(), 3);
    // 删除pos位置的数据
    v.erase(pos);
 
    it = v.begin();
    while (it != v.end()) {
        cout << *it << " ";
        ++it;
    }

    cout << endl;

	return 0;
}
// operator[]+index 和 C++11中vector的新式for+auto的遍历
// vector使用这两种遍历方式是比较便捷的。
#include <iostream>
#include <vector>
using namespace std;
 
int main()
{
    int a[] = { 1, 2, 3, 4 };
    vector<int> v(a, a + sizeof(a) / sizeof(int));
 
    // 通过[]读写第0个位置。
    v[0] = 10;
    cout << v[0] << endl;
 
    // 通过[i]的方式遍历vector
    for (size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << " ";
    }
    cout << endl;
 
    vector<int> swapv;
    swapv.swap(v);
 
    cout << "v data:";
    for (size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << " ";
    }
    cout << endl;
 
    cout << "swapv data:";
    for (size_t i = 0; i < swapv.size(); ++i)
    {
        cout << swapv[i] << " ";
    }
    cout << endl;
    
    // C++11支持的新式遍历
    for(auto x : v)
    {
        cout<< x << " ";
    }
    cout<<endl;
 
    return 0;

}
1.2.4 vector 迭代器失效问题(重点)
 // insert/erase导致的迭代器失效
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
 
int main()
{
    int a[] = { 1, 2, 3, 4 };
    vector<int> v(a, a + sizeof(a) / sizeof(int));
 
    // 使用find查找3所在位置的iterator
    vector<int>::iterator pos = find(v.begin(), v.end(), 3);
 
    // 删除pos位置的数据,导致pos迭代器失效。
    v.erase(pos);
    cout << *pos << endl; // 此处会导致非法访问
 
    // 在pos位置插入数据,导致pos迭代器失效。
    // insert会导致迭代器失效,是因为insert可
    // 能会导致增容,增容后pos还指向原来的空间,而原来的空间已经释放了。
    pos = find(v.begin(), v.end(), 3);
    v.insert(pos, 30);
    cout << *pos << endl; // 此处会导致非法访问
 
    return 0;
}
// 常见的迭代器失效的场景
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
 
int main()
{
    int a[] = { 1, 2, 3, 4 };
    vector<int> v(a, a + sizeof(a) / sizeof(int));
 
    // 实现删除v中的所有偶数
    // 下面的程序会崩溃掉,如果是偶数,erase导致it失效
    // 对失效的迭代器进行++it,会导致程序崩溃
    vector<int>::iterator it = v.begin();
    while (it != v.end())			// 迭代器尽量用for循环来写吧,更加清楚,起始位置、终止位置、++ --很清楚也主要使用前置++,如果C的原生类型编译器自动优化,构造类型前置++写法较好
    {
        if (*it % 2 == 0)

            v.erase(it);			// erase操作过后,原有的迭代器失效,需要记录其返回值才能正常使用
             ++it;
    }
 
    // 以上程序要改成下面这样,erase会返回删除位置的下一个位置
    vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        if (*it % 2 == 0)
            it = v.erase(it);		// 记录返回值,解决迭代器失效问题
        else
            ++it;
    }
    
    return 0;
}

总结: insert会导致迭代器失效,是因为insert可能会导致增容,增容后迭代器还指向原来的空间,而原来的空间已经释放了。erase操作过后该迭代器已经失效,对失效的迭代器再进行操作,会导致程序崩溃。

解决方法: 不论删除还是插入,若该迭代器还需要继续使用,则需要接insert、erase的返回值,从而能够正常使用。插入过程不会导致迭代器指向改变,在开辟新空间的情况时仍指向原来的初始位置,但是空间不同,旧空间释放新空间开辟,有一个 搬家的过程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值