【C++初阶】STL详解(三)vector的介绍与使用

简介: 【C++初阶】STL详解(三)vector的介绍与使用


vector的介绍

1、vector是表示可变大小数组的序列容器。

2、vector就像数组一样,也采用的连续空间来存储元素,这也意味着可以采用下标对vector的元素进行访问。

3、vector与普通数组不同的是,vector的大小是可以动态改变的。

4、当vector需要重新分配大小时,其做法是,分配一个新的数组,然后将全部元素移到这个数组当中,并释放原来的数组空间。

5、vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因此存储空间比实际需要的存储空间一般更大。不同的库采用不同的策略权衡空间的使用和重新分配,以至于在末尾插入一个元素的时候是在常数的时间复杂度完成的。

6、由于vector采用连续的空间来存储元素,与其他动态序列容器相比,vector在访问元素的时候更加高效,在其末尾添加和删除元素相对高效,而对于不在其末尾进行的删除和插入操作效率则相对较低。

vector的使用

vector的定义方式

方式一: 构造一个某类型的空容器。

vector<int> v1; //构造int类型的空容器

方式二: 构造一个含有n个val的某类型容器。

vector<int> v2(10, 2); //构造含有10个2的int类型容器

方式三: 拷贝构造某类型容器的复制品。

vector<int> v3(v2); //拷贝构造int类型的v2容器的复制品

方式四: 使用迭代器拷贝构造某一段内容。

vector<int> v4(v2.begin(), v2.end()); //使用迭代器拷贝构造v2容器的某一段内容

注意:该方式也可用于拷贝其他容器的某一段内容。

string s("hello world"); vector<char> v5(s.begin(), s.end()); //拷贝构造string对象的某一段内容

vector空间增长问题

size和capacity

通过size函数获取当前容器中的有效元素个数,通过capacity函数获取当前容器的最大容量。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
  vector<int> v(10, 2);
  cout << v.size() << endl; //获取当前容器中的有效元素个数
  cout << v.capacity() << endl; //获取当前容器的最大容量
  return 0;
}

reserve和resize

通过reserse函数改变容器的最大容量,resize函数改变容器中的有效元素个数。

reserve规则:

 1、当所给值大于容器当前的capacity时,将capacity扩大到该值。

 2、当所给值小于容器当前的capacity时,什么也不做。

resize规则:

 1、当所给值大于容器当前的size时,将size扩大到该值,扩大的元素为第二个所给值,若未给出,则默认为0。

 2、当所给值小于容器当前的size时,将size缩小到该值。

int main()
{
  vector<int> v(10, 2);
  cout << v.size() << endl; //10
  cout << v.capacity() << endl; //10
  v.reserve(20); //改变容器的capacity为20,size不变
  cout << v.size() << endl; //10
  cout << v.capacity() << endl; //20
  v.resize(15); //改变容器的size为15
  cout << v.size() << endl; //15
  cout << v.capacity() << endl; //20
  return 0;
}

empty

通过empty函数判断当前容器是否为空。

int main()
{
  vector<int> v(10, 2);
  cout << v.empty() << endl;
  return 0;
}

vector迭代器的使用:

begin与end

通过begin函数可以得到容器中第一个元素的正向迭代器,通过end函数可以得到容器中最后一个元素的后一个位置的正向迭代器。

正向迭代器遍历容器:

int main()
{
  vector<int> v(10, 2);
  //正向迭代器遍历容器
  vector<int>::iterator it = v.begin();
  while (it != v.end())
  {
    cout << *it << " ";
    it++;
  }
  cout << endl;
  return 0;
}

rbegin与rend

通过rbegin函数可以得到容器中最后一个元素的反向迭代器,通过rend函数可以得到容器中第一个元素的前一个位置的反向迭代器。

反向遍历容器:

int main()
{
  vector<int> v(10, 2);
  //反向迭代器遍历容器
  vector<int>::reverse_iterator rit = v.rbegin();
  while (rit != v.rend())
  {
    cout << *rit << " ";
    rit++;
  }
  cout << endl;
  return 0;
}

vector的增删查改

push_back与pop_back

通过push_back函数对容器进行尾插,pop_back函数对容器进行尾删。

int main()
{
  vector<int> v;
  v.push_back(1); //尾插元素1
  v.push_back(2); //尾插元素2
  v.push_back(3); //尾插元素3
  v.push_back(4); //尾插元素4
  cout << "插入" << endl;
  for (auto ch : v)
  {
    cout << ch << endl;
  }
  v.pop_back(); //尾删元素
  v.pop_back(); //尾删元素
  v.pop_back(); //尾删元素
  v.pop_back(); //尾删元素
  cout << "删除" << endl;
  for (auto ch : v)
  {
    cout << ch << endl;
  }
  return 0;
}

insert与erase

通过insert函数可以在所给迭代器位置插入一个或多个元素,通过erase函数可以删除所给迭代器位置的元素,或删除所给迭代器区间内的所有元素(左闭右开)。

int main()
{
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  v.insert(v.begin(), 0); //在容器开头插入0
  
  v.insert(v.begin(), 5, -1); //在容器开头插入5个-1
  v.erase(v.begin()); //删除容器中的第一个元素
  v.erase(v.begin(), v.begin() + 5); //删除在该迭代器区间内的元素(左闭右开)  
  return 0;
}

以上是按位置进行插入或删除元素的方式,若要按值进行插入或删除(在某一特定值位置进行插入或删除),则需要用到find函数。

find函数:

find函数共三个参数,前两个参数确定一个迭代器区间(左闭右开),第三个参数确定所要寻找的值。

find函数在所给迭代器区间寻找第一个匹配的元素,并返回它的迭代器,若未找到,则返回所给的第二个参数。

#include <algorithm>
int main()
{
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  vector<int>::iterator pos = find(v.begin(), v.end(), 2); //获取值为2的元素的迭代器 
  v.insert(pos, 10); //在2的位置插入10
  pos = find(v.begin(), v.end(), 3); //获取值为3的元素的迭代器
  v.erase(pos); //删除3
  return 0;
}

注意: find函数是在算法模块(algorithm)当中实现的,不是vector的成员函数。

swap

通过swap函数可以交换两个容器的数据空间,实现两个容器的交换。

int main()
{
  vector<int> v1(10, 1);
  vector<int> v2(10, 2);
  v1.swap(v2); //交换v1,v2的数据空间
  return 0;
}

元素访问:

vector当中实现了 [ ] 操作符的重载,因此我们也可以通过“下标+[ ]”的方式对容器当中的元素进行访问。

int main()
{
  vector<int> v(10, 1);
  //使用“下标+[]”的方式遍历容器
  for (size_t i = 0; i < v.size(); i++)
  {
    cout << v[i] << " ";
  }
  cout << endl;
  return 0;
}

我们上面说到vector是支持迭代器的,所以我们还可以用范围for对vector容器进行遍历。(支持迭代器就支持范围for,因为在编译时编译器会自动将范围for替换为迭代器的形式

vector<int> v(10, 1);
  //范围for
  for (auto e : v)
  {
    cout << e << " ";
  }
  cout << endl;

迭代器失效问题:

迭代器的主要作用就是让我们在使用各个容器时不用关心其底层的数据结构,而vector的迭代器在底层实际上就是一个指针。迭代器失效就是指迭代器底层对应指针所指向的空间被销毁了,而指向的是一块已经被释放的空间,如果继续使用已经失效的迭代器,程序可能会崩溃。

实例一:

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  v.push_back(5);
  //v: 1 2 3 4 5
  vector<int>::iterator pos = find(v.begin(), v.end(), 2); //获取值为2的元素的迭代器
  v.insert(pos, 10); //在值为2的元素的位置插入10
  //v: 1 10 2 3 4 5
  v.erase(pos); //删除元素2 ???error(迭代器失效)
  //v: 1 2 3 4 5
  return 0;
}

在该代码中,我们本意是使用元素2的迭代器在原序列中2的位置插入一个10,然后将2删除,但我们实际上获取的是指向2的指针,当我们在2的位置插入10后,该指针就指向了10,所以我们之后删除的实际上是10,而不是2。

实例二:

int main()
{
  vector<int> v;
  for (size_t i = 1; i <= 6; i++)
  {
    v.push_back(i);
  }
  vector<int>::iterator it = v.begin();
  while (it != v.end())
  {
    if (*it % 2 == 0) //删除容器当中的全部偶数
    {
      v.erase(it);
    }
    it++;
  }
  return 0;
}

该代码看上去实际上并没有什么错误,但如果你画图仔细分析,你就会发现该代码的问题所在,迭代器访问到了不属于容器的内存空间,导致程序崩溃。

不仅如此,而且在迭代器遍历容器中的元素进行判断时,并没有对1、3、5元素进行判断。

解决方法:

实例一:

对于实例一,我们在使用迭代器删除元素2时对其进行重新赋值便可以解决。

int main()
{
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  v.push_back(5);
  //v: 1 2 3 4 5
  vector<int>::iterator pos = find(v.begin(), v.end(), 2); //获取值为2的元素的迭代器
  v.insert(pos, 10); //在值为2的元素的位置插入10
  //v: 1 10 2 3 4 5
  pos = find(v.begin(), v.end(), 2); //重新获取值为2的元素的迭代器
  v.erase(pos); //删除元素2
  //v: 1 10 3 4 5
  return 0;
}

实例二:

对于实例二,我们可以接收erase函数的返回值(erase函数返回删除元素的后一个元素的新位置),并且控制代码的逻辑:当元素被删除后继续判断该位置的元素(因为该位置的元素已经更新,需要再次判断)。

int main()
{
  vector<int> v;
  for (size_t i = 1; i <= 6; i++)
  {
    v.push_back(i);
  }
  vector<int>::iterator it = v.begin();
  while (it != v.end())
  {
    if (*it % 2 == 0) //删除容器当中的全部偶数
    {
      it = v.erase(it); //删除后获取下一个元素的迭代器
    }
    else
    {
      it++; //是奇数则it++
    }
  }
  return 0;
}

相关文章
|
7天前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
15 1
|
20天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
34 7
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
66 4
|
2月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
81 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
63 2
|
19天前
|
存储 对象存储 C++
C++ 中 std::array<int, array_size> 与 std::vector<int> 的深入对比
本文深入对比了 C++ 标准库中的 `std::array` 和 `std::vector`,从内存管理、性能、功能特性、使用场景等方面详细分析了两者的差异。`std::array` 适合固定大小的数据和高性能需求,而 `std::vector` 则提供了动态调整大小的灵活性,适用于数据量不确定或需要频繁操作的场景。选择合适的容器可以提高代码的效率和可靠性。
41 0
|
2月前
|
存储 算法 Linux
【c++】STL简介
本文介绍了C++标准模板库(STL)的基本概念、组成部分及学习方法,强调了STL在提高编程效率和代码复用性方面的重要性。文章详细解析了STL的六大组件:容器、算法、迭代器、仿函数、配接器和空间配置器,并提出了学习STL的三个层次,旨在帮助读者深入理解和掌握STL。
61 0
|
23天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
31 0
|
2月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
113 5
|
2月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
113 4