【C++】vector的使用 以及 迭代器失效问题

简介: 【C++】vector的使用 以及 迭代器失效问题

一、 vector的构造函数

vector的构造函数主要有四个,下面我们来一 一演示

第一个是无参的默认构造,第二个是我们可以用n个val去初始化vector,第三个也就是vector的拷贝构造,第四个是使用迭代器进行初始化构造。

int main()
{
  vector<int> v1;
  v1.push_back(1); //push_back的作用就是插入一个元素
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(4);
  vector<int> v2(4); //用4个匿名对象进行初始化
  vector<int> v3(4, 9); //用4个9进行初始化
  vector<int> v4(++v1.begin(), --v1.end());//用迭代器进行进行初始化
  vector<int> v5(v3); //拷贝构造
  return 0;
}

观察监视:

二、容量相关的函数

我们先来看看前三个:

int main()
{
  vector<int> v1(10, 2);
  cout << "size:" << v1.size() << endl;
  cout << "capacity:" << v1.capacity() << endl;
  cout << "empty:" << v1.empty() << endl;
  return 0;
}

然后我们先来看reserve函数,reserve函数的作用就是开辟新的空间改变capacity,但是不会改变size

  • reserve函数中的 n小于原始的容量时,此函数什么也不做。
  • reserve函数中的n 大于原始的容量时,就会扩大容量,注意里面原有的数据不做改变。

我们来看下面的一段代码:

int main()
{
  vector<int> v1(10, 2);
  v1.reserve(5);
  v1.reserve(15);
  return 0;
}

观察监视窗口我们会发现,执行reserve(5)时函数什么都没有做,执行reserve(15)时函数才会进行扩容。

reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。


下面我们再来看看resize函数,resize函数不仅会改变capacity也会改变size

  • 如果resize的参数n小于原始的size,那么就会保留前n个,后面的数据将会被销毁,但是capacity不变。
  • 如果resise的参数n大于原始的size,就会用第二个参数进行初始化size后面的空间,直到size == capacity
  • 如果resise的参数n大于原始的capacity,就会进行扩容并初始化未使用的空间。

我们来看下面一段代码:

int main()
{
  vector<int> v1(10, 2);
  v1.resize(5);
  v1.resize(15,7);
  return 0;
}

vector 空间增长问题在的讨论

关于vector空间增长机制我们可以用下面的代码来进行测试。

void TestVectorExpand()
{
  size_t sz;
  vector<int> v;
  //记录第一次的capacity的值
  sz = v.capacity();
  cout << "making v grow:\n";
  for (int i = 0; i < 100; ++i)
  {
    //挨个插入100个元素
    v.push_back(i);
    //如果容量发生了变化,就重新更新sz的数据,并打印sz
    if (sz != v.capacity())
    {
      sz = v.capacity();
      cout << "capacity changed: " << sz << '\n';
    }
  }
}

我们先来看一看windows下的扩容机制:

我们会发现windows平台下是1.5倍扩容!

我们再来看看Linux平台下的空间增长:

我们发现是2倍扩容。

原因是这两个平台采用的STL版本不同,vs是PJ版本STL,g++是SGI版本STL。

三、vector 数据的访问

代码演示:

int main()
{
  vector<int> v1;
  v1.push_back(0);
  v1.push_back(1);
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(4);
  cout << v1[2] << endl;
  cout << v1.at(2) << endl;
  cout << v1.front() << endl;
  cout << v1.back() << endl;
  int* pi = v1.data();
  pi[3] = 5;
  cout << pi[3] << endl;
  return 0;
}

四、vector的增删查改

1、assign函数

第一个函数的用一个迭代器区间进行赋值,第二个函数是用n个元素来进行赋值。

int main()
{
  vector<int> v1;
  v1.push_back(0);
  v1.push_back(1);
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(4);
  vector<int>::iterator it1 = v1.begin();
  while (it1 != v1.end())
  {
    cout << *it1;
    ++it1;
  }
  cout << endl;
  vector<int> v2(2, 7);
  vector<int>::iterator it2 = v2.begin();
  while (it2 != v2.end())
  {
    cout << *it2 ;
    ++it2;
  }
  cout << endl;
  //普通赋值
  v2.assign(3, 2);
  for (auto& e : v2)
  {
    cout << e ;
  }
  cout << endl;
  //用迭代器区间赋值
  v2.assign(v1.begin(), v1.end());
  for (auto& e : v2)
  {
    cout << e;
  }
  cout << endl;
  return 0;
}

2、insert函数

第一个函数是用1个元素来进行插入pos位置之前,第二个函数是用n个元素插入pos位置之前

int main()
{
  vector<int> v1;
  v1.push_back(0);
  v1.push_back(1);
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(4);
  v1.pop_back();
  for (auto& e : v1)
  {
    cout << e;
  }
  cout << endl;
  v1.insert(++v1.begin(), 2, 7);
  for (auto& e : v1)
  {
    cout << e;
  }
  cout << endl;
  return 0;
}

3、erase函数

第一个函数是删除pos迭代器位置。第二个是删除迭代器区间的所有元素

int main()
{
  vector<int> v1;
  v1.push_back(0);
  v1.push_back(1);
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(4);
  for (auto& e : v1)
  {
    cout << e;
  }
  cout << endl;
  v1.erase(++v1.begin(), --v1.end());
  for (auto& e : v1)
  {
    cout << e;
  }
  cout << endl;
  return 0;
}

4、swap函数

int main()
{
  vector<int> v1;
  v1.push_back(0);
  v1.push_back(1);
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(4);
  for (auto& e : v1)
  {
    cout << e;
  }
  cout << endl;
  vector<int> v2(9, 7);
  v1.swap(v2);
  for (auto& e : v1)
  {
    cout << e;
  }
  cout << endl;
  return 0;
}

5、clear函数

int main()
{
  vector<int> v1;
  v1.push_back(0);
  v1.push_back(1);
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(4);
  for (auto& e : v1)
  {
    cout << e;
  }
  cout << endl;
  v1.clear();
  cout << "size:" << v1.size() << endl;
  cout << "capacity :" << v1.capacity() << endl;
  return 0;
}

6、find函数

find函数并不是vector里面的成员函数,而是C++里面的一个算法库里面的函数,其作用就是帮我们寻找我们想要的数据。

函数参数:两个迭代器,确定一个一个区间,最后面的参数是我们想要查找的元素。

返回值:当找到该元素后,返回该位置的迭代器,如果找不到,返回last迭代器。

(注意:所有的迭代器区间都是左闭右开的,所以返回last迭代器代表找不到)

int main()
{
  vector<int> v1;
  v1.push_back(0);
  v1.push_back(1);
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(4);
  //删除元素 2
  vector<int>::iterator it = find(v1.begin(), v1.end(), 2);
  v1.erase(it);
  for (auto& e : v1)
  {
    cout << e;
  }
  return 0;
}

五、vector 迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际上就是一个指针,或者是对指针进行了封装

迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果可能未定义的或者是程序崩溃

但是由于vsg++采用的STL版本分别是PJ版本和SGI版本,因此对于迭代器的失效问题,在两个版本中的表现也不太相同。

迭代器的失效主要有两种方式:

  • 由于删除元素而导致的迭代器失效。
  • 由于容量改变而导致的迭代器失效

我们先来看下面由于删除元素而导致的迭代器失效:

int main()
{
  vector<int> v1{ 1,2,3,4,5 };
  vector<int>::iterator it = v1.begin();
  //删除it指向的元素
  v1.erase(it);
  //再对it指向的元素进行自增
  (*it)++;
  for (auto& e : v1)
  {
    cout << e ;
  }
  cout << endl;
  return 0;
}

erase删除it位置元素后,it位置之后的元素会往前搬移,但是我们再对it进行访问已经不合理了,因为最初时我们是想让it指向元素 1,元素1消失了,我们也不应该再对it进行访问了。

上面的代码在vs下的PJ版本会直接报错,而在g++下的SGI版本会访问下一个元素2

  • vs下执行(*it)++时会直接报错:


  • g++下执行(*it)++时会直接访问后面移动到该位置上的元素,从而导致了导致2变成了3


看到这里你可能觉得这里的迭代器好像也可以看成不失效啊,g++下还能通过it直接访问元素也是合理的啊,但是当我们上面的代码删除的是最后一个元素5,g++下还执行(*it)++时就发生了越界行为!

因此对于这个it如果我们还想使用我们就要对迭代器进行重新赋值,这也是解决迭代器失效的通用的方法。

下面我们再来看另外一种由于容量改变而导致的迭代器失效:

int main()
{
  vector<int> v1{ 1,2,3,4 };
  //it是未扩容之前的迭代器
  vector<int>::iterator it = v1.begin();
  v1.resize(50,0);
  //v1.begin()是扩容后的迭代器
  while (it != v1.end())
  {
    cout << *it << " ";
    ++it;
  }
  cout << endl;
  return 0;
}
  • vs下执行时会直接报错:

  • g++下执行时也会执行错误:


    发生了许多越界访问。

这里迭代器失效的原因:是我们对v1进行了扩容,而it是指向没有扩容之前的开始位置的迭代器,扩容以后内存地址发生了变化,而it没有及时更新。我们还使用以前的迭代器就有可能造成许多越界行为。

对于第二种迭代器失效,只要会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。

因此同时使用可能引起其底层空间改变的函数和迭代器时要注意迭代器的更新!!

相关文章
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
90 4
|
1月前
|
存储 对象存储 C++
C++ 中 std::array<int, array_size> 与 std::vector<int> 的深入对比
本文深入对比了 C++ 标准库中的 `std::array` 和 `std::vector`,从内存管理、性能、功能特性、使用场景等方面详细分析了两者的差异。`std::array` 适合固定大小的数据和高性能需求,而 `std::vector` 则提供了动态调整大小的灵活性,适用于数据量不确定或需要频繁操作的场景。选择合适的容器可以提高代码的效率和可靠性。
65 0
|
1月前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
42 0
|
3月前
|
存储 C++ 索引
【C++打怪之路Lv9】-- vector
【C++打怪之路Lv9】-- vector
32 1
|
3月前
|
编译器 C++
【C++】—— vector模拟实现
【C++】—— vector模拟实现
|
3月前
|
算法 C++ 容器
C++之打造my vector篇(下)
C++之打造my vector篇(下)
41 0
|
3月前
|
存储 编译器 C++
C++之打造my vector篇(上)
C++之打造my vector篇(上)
41 0
|
3月前
|
算法 C++ 容器
【C++】—— vector使用
【C++】—— vector使用
|
3月前
|
存储 缓存 C++
C++番外篇——list与vector的比较
C++番外篇——list与vector的比较
32 0
|
3月前
|
C++
C++番外篇——vector的实现
C++番外篇——vector的实现
53 0