当前位置: 首页 > news >正文

深入理解C++ 中的vector容器

一、引言

在C++ 的标准模板库(STL)中, vector  是一个极为常用且功能强大的序列容器。它就像是一个动态数组,既能享受数组随机访问元素的高效性,又能灵活地动态调整大小。在本文中,我们将深入探讨  vector  的方方面面,包括其基本概念、常用接口、迭代器相关问题以及空间增长策略等。

二、vector 基础介绍

2.1 定义与特性

 vector  是表示可变大小数组的序列容器。它采用连续存储空间来存储元素,这使得我们可以像操作数组一样,通过下标高效地访问元素。与普通数组不同的是, vector  的大小能够动态改变,容器会自动处理内存管理相关事宜。

从本质上讲, vector  使用动态分配数组来存储元素。当新元素插入时,如果当前空间不足,它会分配一个新的数组,并将全部元素迁移到新数组中。虽然这个过程相对耗时,但  vector  并非每次插入新元素都进行这样的操作,以平衡效率。

2.2 构造函数

 vector  提供了多种构造函数:

- 无参构造: vector()  ,创建一个空的  vector  。例如: vector<int> v; 

- 指定数量和初始值构造: vector(size_type n, const value_type& val = value_type())  ,构造并初始化  n  个值为  val  的元素。例如: vector<int> v(5, 10);  会创建一个包含5个元素,每个元素值为10的  vector  。

- 拷贝构造: vector(const vector& x)  ,用于复制一个已有的  vector  。例如: vector<int> v1{1, 2, 3}; vector<int> v2(v1); 

- 使用迭代器初始化构造: vector(InputIterator first, InputIterator last)  ,使用迭代器范围初始化  vector  。例如: vector<int> v{1, 2, 3, 4, 5}; vector<int> v3(v.begin(), v.end()); 

三、vector 常用接口

3.1 增删查改接口

- 尾插(push_back):这是一个重点接口,用于在  vector  的尾部插入一个元素。例如:


 

cpp#include <iostream>#include <vector>int main() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);return 0;}

- 尾删(pop_back):用于删除  vector  尾部的元素。例如:

cpp#include <iostream>#include <vector>int main() {vector<int> v{1, 2, 3};v.pop_back();return 0;}

- 查找(find):需要注意的是, find  是算法模块实现,并非  vector  的成员接口。它用于在  vector  中查找特定元素。例如:

cpp#include <iostream>#include <vector>#include <algorithm>int main() {vector<int> v{1, 2, 3};auto it = find(v.begin(), v.end(), 2);if (it != v.end()) {std::cout << "找到元素" << std::endl;}return 0;}

- 插入(insert):在指定位置  position  之前插入值为  val  的元素。例如:

cpp#include <iostream>#include <vector>int main() {vector<int> v{1, 2, 3};auto it = v.begin();v.insert(it, 0);return 0;}

- 删除(erase):删除指定位置  position  的数据。例如:

cpp#include <iostream>#include <vector>int main() {vector<int> v{1, 2, 3};auto it = v.begin();v.erase(it);return 0;}

- 交换(swap):用于交换两个  vector  的数据空间。例如:

cpp#include <iostream>#include <vector>int main() {vector<int> v1{1, 2, 3};vector<int> v2{4, 5, 6};v1.swap(v2);return 0;}

- 像数组一样访问(operator[]):这也是重点接口,允许像访问数组元素一样访问  vector  中的元素。例如:

cpp#include <iostream>#include <vector>int main() {vector<int> v{1, 2, 3};std::cout << v[1] << std::endl;return 0;}

3.2 容量相关接口

- size:用于获取  vector  中数据的个数。例如: vector<int> v{1, 2, 3}; std::cout << v.size() << std::endl; 

- capacity:获取  vector  的容量大小。例如: vector<int> v{1, 2, 3}; std::cout << v.capacity() << std::endl; 

- empty:判断  vector  是否为空。例如: vector<int> v; if (v.empty()) { std::cout << "vector为空" << std::endl; } 

- resize:改变  vector  的大小。如果新大小大于原大小,会使用指定值(默认值为元素类型的默认值)填充新增位置;如果新大小小于原大小,会删除多余元素。例如:

cpp#include <iostream>#include <vector>int main() {vector<int> v{1, 2, 3};v.resize(5, 8);return 0;}

- reserve:改变  vector  的容量,只负责开辟空间,不会对新空间进行初始化。例如: vector<int> v; v.reserve(10); 

四、vector 迭代器

4.1 迭代器基本概念与使用

迭代器是  vector  与算法之间的桥梁,它使得算法无需关心底层数据结构。 vector  的迭代器本质上是原生态指针  T*  。

常用的迭代器相关接口有:

- begin + end: begin()  获取第一个数据位置的迭代器/常量迭代器, end()  获取最后一个数据的下一个位置的迭代器/常量迭代器 。例如:

cpp#include <iostream>#include <vector>int main() {vector<int> v{1, 2, 3};for (auto it = v.begin(); it != v.end(); ++it) {std::cout << *it << " ";}return 0;}

- rbegin + rend: rbegin()  获取最后一个数据位置的反向迭代器, rend()  获取第一个数据前一个位置的反向迭代器 。例如:

cpp#include <iostream>#include <vector>int main() {vector<int> v{1, 2, 3};for (auto it = v.rbegin(); it != v.rend(); ++it) {std::cout << *it << " ";}return 0;}

4.2 迭代器失效问题

迭代器失效是使用  vector  时需要特别关注的问题。当迭代器底层对应指针所指向的空间被销毁,继续使用该迭代器就会导致程序崩溃。

可能导致  vector  迭代器失效的操作包括:

- 引起底层空间改变的操作,如  resize 、 reserve 、 insert 、 assign 、 push_back  等。以下是示例代码及解释:

cpp#include <iostream>#include <vector>int main() {vector<int> v{1, 2, 3, 4, 5, 6};auto it = v.begin();// resize 操作,底层空间可能改变,迭代器失效v.resize(100, 8);// reserve 操作,改变扩容大小,可能使迭代器失效v.reserve(100);// insert 操作,插入元素可能引起扩容,导致迭代器失效v.insert(v.begin(), 0);// push_back 操作,可能引起扩容,导致迭代器失效v.push_back(8);// assign 操作,重新赋值可能引起底层容量改变,导致迭代器失效v.assign(100, 8);// 这里继续使用 it 迭代器会出错,因为 it 已经失效// 解决方法:在上述操作后,重新给 it 赋值,如 it = v.begin();while (it != v.end()) {std::cout << *it << " ";++it;}std::cout << std::endl;return 0;}

-  erase  删除操作:删除元素后,迭代器也可能失效。例如:

cpp#include <iostream>#include <vector>int main() {vector<int> v{1, 2, 3, 4};auto it = v.begin();while (it != v.end()) {if (*it % 2 == 0) {// erase 操作后,it 迭代器失效,需要重新赋值it = v.erase(it); } else {++it;}}return 0;}

五、vector 空间增长策略

 vector  的空间增长策略在不同编译器下有所不同。在VS(P版本STL)下, capacity  通常按1.5倍增长;在G++(SGI版本STL)下, capacity  按2倍增长 。以下是测试代码:


 

cpp// 测试vector的默认扩容机制void TestVectorExpand() {size_t sz;vector<int> v;sz = v.capacity();std::cout << "making v grow:\n";for (int i = 0; i < 100; ++i) {v.push_back(i);if (sz != v.capacity()) {sz = v.capacity();std::cout << "capacity changed: " << sz << '\n';}}}

了解这一策略有助于我们在编写代码时更好地预估  vector  的内存使用情况,并且在需要提前分配足够空间以避免频繁扩容时,可以使用  reserve  接口。例如:

cpp// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够// 就可以避免边插入边扩容导致效率低下的问题了void TestVectorExpandOP() {vector<int> v;size_t sz = v.capacity();v.reserve(100); std::cout << "making bar grow:\n";for (int i = 0; i < 100; ++i) {v.push_back(i);if (sz != v.capacity()) {sz = v.capacity();std::cout << "capacity changed: " << sz << '\n';}}}

六、vector 在OJ中的应用实例

6.1 只出现一次的数字

题目要求在给定的整数数组( vector<int> )中找到只出现一次的数字,其他数字都出现两次。解题思路是利用异或运算的性质:一个数异或它本身结果为0,异或0结果为它本身。代码如下:

cppclass Solution {public:int singleNumber(vector<int>& nums) {int value = 0;for (auto e : nums) {value ^= e;}return value;}};

6.2 杨辉三角

题目要求生成杨辉三角的前  numRows  行。解题的核心思想是找出杨辉三角的规律,即每一行头尾都是1,中间第  j  个数等于上一行第  j - 1  个数加上第  j  个数 。代码如下:

cppclass Solution {public:vector<vector<int>> generate(int numRows) {vector<vector<int>> vv(numRows);for (int i = 0; i < numRows; ++i) {vv[i].resize(i + 1, 1);for (int j = 1; j < i; ++j) {vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];}}return vv;}};

七、总结

 vector  作为C++ STL中重要的容器之一,功能丰富且使用广泛。通过深入了解其基本概念、常用接口、迭代器特性以及空间增长策略等方面,我们能够更加得心应手地使用它来解决各种实际问题。无论是日常开发还是算法竞赛,熟练掌握  vector  的使用都能为我们带来极大的便利和效率提升。在实际编程中,要特别注意迭代器失效问题,合理运用其接口,以充分发挥  vector  的优势。

http://www.xdnf.cn/news/25651.html

相关文章:

  • docker架构
  • 【MySQL】SQL语句在MySQL中的执行过程?主要存储引擎区别?
  • LLM做逻辑推理题 - 如何找出不标准的球?
  • kafka认证部署
  • LINUX419 更换仓库(没换成)find命令
  • 中间件--ClickHouse-11--部署示例(Linux宿主机部署,Docker容器部署)
  • Python实现对目标Word文档进行自动化排版【4万字精讲】(14)
  • 多道程序和多任务操作系统区别
  • 设计测试用例模板
  • 意志力的源头——AMCC(前部中扣带皮层)
  • 相机模型--CMOS和CCD的区别
  • 致远OA——数据回填表单
  • 【记录】服务器用命令开启端口号
  • sklearn基础教程
  • 数据结构实验7.2:二叉树的基本运算
  • Neovim插件深度解析:mcphub.nvim如何用MCP协议重构开发体验
  • WPF 点击按钮,显示隐藏另一个控件
  • C++高并发内存池ConcurrenMemoPool
  • Shell脚本-什么时候需要定义变量
  • 【Netty篇】ByteBuf 详解 (下)
  • 绕过UI的cooke和token的验证
  • 2025年最新版 Git和Github的绑定方法,以及通过Git提交文件至Github的具体流程(详细版)
  • keil5 µVision 升级为V5.40.0.0:增加了对STM32CubeMX作为全局生成器的支持,主要有哪些好处?
  • Elasticsearch只返回指定的字段(用_source)
  • 实现AWS Step Function安全地请求企业内部API返回数据
  • c# MES生产进度看板,报警看板 热流道行业可用实时看生产进度
  • 【问题笔记】解决python虚拟环境运行脚本无法激活问题
  • Flink框架十大应用场景
  • 基于SpringBoot的网上找律师管理系统
  • 四月下旬系列