当前位置: 首页 > 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  的优势。

相关文章:

  • 机器学习核心算法全解析:从基础到进阶的 18 大算法模型
  • 点云数据处理开源C++方案
  • 神经网络的数学之旅:从输入到反向传播
  • 在串口通信中使用共享指针(`std::shared_ptr`)
  • 用 R 语言打造交互式叙事地图:讲述黄河源区生态变化的故事
  • MCP认证难题破解:常见技术难题实战分析与解决方案
  • 额外篇 非递归之美:归并排序与快速排序的创新实现
  • 基于Redis的3种分布式ID生成策略
  • JAVA文件I/O
  • 大数据平台简介
  • 《Operating System Concepts》阅读笔记:p738-p747
  • Java从入门到“放弃”(精通)之旅——数组的定义与使用⑥
  • 批量创建OpenStack实例
  • 【java实现+4种变体完整例子】排序算法中【堆排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格
  • doris/clickhouse常用sql
  • C++镌刻数据密码的树之铭文:二叉搜索树
  • 与终端同居日记:Linux指令の进阶撩拨手册
  • 区块链木材业务服务平台:商贸物流新变革
  • 18、TimeDiff论文笔记
  • 【综述】一文读懂卷积神经网络(CNN)
  • 谷雨播种正当时,上海黄道婆纪念公园种下“都市棉田”
  • 西安旅游:2024年营业收入约5.82亿元,同比增长5.88%
  • 美政府公布1968年罗伯特·肯尼迪遇刺事件档案
  • 部分人员无资质展业、投资建议无合理依据,天相财富被责令改正
  • 非法收受财物2.29亿余元,窦万贵受贿案一审开庭
  • 对话|听老婆的话,UFC“下山虎”张名扬的铁汉柔情