C++ 中的 std::vector
一、底层结构与内存管理
1. 核心数据结构
- 连续内存块:元素在内存中连续存储,支持指针算术运算,实现 O(1) 随机访问。
- 三指针管理(简化模型):
_start:指向首个元素的地址(begin()迭代器)。_finish:指向最后一个有效元素的下一个位置(end()迭代器),size = _finish - _start。_end_of_storage:指向已分配内存的末尾,capacity = _end_of_storage - _start。
2. 动态扩容机制
- 当
size == capacity时插入元素会触发扩容:- 分配新内存:通常扩容至原容量的 2 倍(不同编译器实现可能不同)。
- 迁移数据:将旧元素拷贝/移动到新内存(涉及构造与析构)。
- 释放旧内存:更新三指针指向新空间。
- 均摊时间复杂度:
push_back()的均摊复杂度为 O(1),因扩容频率随元素增长降低。
3. 迭代器与失效问题
- 迭代器类型:随机访问迭代器(支持
+、-、[]操作)。 - 失效场景:
- 扩容后:所有迭代器、指针、引用失效(地址变更)。
- 中间插入/删除:操作位置之后的迭代器失效。
二、常用函数详解
1. 构造与初始化
| 方法 | 示例 | 说明 |
|---|---|---|
| 默认构造 | vector<int> v1; | 创建空 vector |
| 指定大小和初值 | vector<int> v2(5, 10); | 5 个元素,值均为 10 |
| 列表初始化 | vector<int> v3 = {1, 2, 3}; | C++11 初始化列表 |
| 范围构造 | vector<int> v4(v3.begin(), v3.end()); | 复制另一容器的区间 |
2. 元素访问
| 函数 | 示例 | 特性 |
|---|---|---|
operator[] | int a = v[0]; | 不检查边界,效率高 |
at() | int b = v.at(1); | 边界检查,越界抛 out_of_range |
front()/back() | int f = v.front(); | 访问首/尾元素 |
data() | int* p = v.data(); | 返回底层数组指针(C++11) |
3. 容量操作
| 函数 | 说明 |
|---|---|
size() | 返回当前元素个数 |
capacity() | 返回当前分配的内存容量 |
reserve(n) | 预分配内存,避免多次扩容(如 reserve(1000)) |
shrink_to_fit() | 释放多余内存(容量降至 size) |
empty() | 判断容器是否为空 |
4. 修改操作
| 函数 | 示例 | 时间复杂度 |
|---|---|---|
push_back(val) | v.push_back(10); | 尾部插入,均摊 O(1) |
emplace_back(args) | v.emplace_back(10); | 直接构造,避免拷贝(更高效) |
pop_back() | v.pop_back(); | 尾部删除,O(1) |
insert(pos, val) | v.insert(v.begin() + 1, 20); | 中间插入,O(n) |
erase(pos) | v.erase(v.begin()); | 中间删除,O(n) |
clear() | v.clear(); | 清空元素(不释放内存) |
resize(n) | v.resize(10); | 调整元素个数 |
5. 迭代器与遍历
// 1. 下标遍历
for (size_t i = 0; i < v.size(); ++i) {
cout << v[i];
}
// 2. 迭代器遍历
for (auto it = v.begin(); it != v.end(); ++it) {
cout << *it;
}
// 3. 范围 for 循环(C++11)
for (int num : v) {
cout << num;
}
6. 删除元素
6.1 删除具体位置元素
删除元素:vec.erase(vec.begin() + 2);
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 打印原始向量
std::cout << "原始向量,当前向量大小: " << vec.size() << std::endl;
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
// 移除第三个元素(索引 2,值为 3)
int removed_value = vec[2]; // 保存要移除的值
vec.erase(vec.begin() + 2);
// 打印被移除的元素值
std::cout << "移除了元素: " << removed_value << std::endl;
// 打印移除后的向量
std::cout << "移除后的向量: ";
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
// 打印向量大小
std::cout << "当前向量大小: " << vec.size() << std::endl;
return 0;
}
三、关键特性与性能优化
-
与数组/链表对比:
- 优势:随机访问 O(1)、缓存友好(连续内存)、自动内存管理。
- 劣势:中间插入/删除效率低(需移动元素)。
-
性能优化技巧:
- 预分配内存:
reserve()减少扩容开销。 - 使用
emplace_back:避免临时对象构造(尤其对复杂类型)。 - 避免中间修改:频繁插入/删除时考虑
std::list或std::deque。
- 预分配内存:
-
高级用法:
- 二维 vector:
vector<vector<int>> matrix(5, vector<int>(5, 0));。 - 排序与查找:结合
<algorithm>中的sort()、find()。
- 二维 vector:
四、总结
- 适用场景:需频繁随机访问、尾部操作居多的场景(如数据缓存、矩阵运算)。
- 慎用场景:频繁在中间位置插入/删除数据(链表更优)。
- 设计哲学:以空间换时间,通过动态扩容和连续内存实现高效访问,是现代 C++ 高性能容器的代表。
vector 之键值对
在 C++ 标准库中,std::vector 本身不直接支持键值对(Key-Value)结构,但可以通过组合其他类型(如 std::pair 或自定义结构)间接实现类似功能。以下是详细分析:
1. std::vector 的默认设计
- 本质:
std::vector是一个动态数组容器,存储单一类型的元素(如int、string等)。 - 内存布局:元素在内存中连续存储,支持通过索引(
vec[i])高效随机访问(时间复杂度 O(1))。 - 无键值概念:原生不支持通过“键”(Key)查找或管理数据,仅依赖下标索引。
2. 如何实现键值对功能?
虽然 std::vector 不直接支持键值对,但可通过以下方式模拟:
方法一:使用 std::pair 存储键值对
#include <vector>
#include <utility> // for std::pair
std::vector<std::pair<std::string, int>> vec;
vec.push_back(std::make_pair("apple", 10)); // 添加键值对
vec.push_back(std::make_pair("banana", 20));
// 遍历查找(线性搜索,O(n))
for (const auto& kv : vec) {
if (kv.first == "apple") {
std::cout << "Found: " << kv.second << std::endl;
}
}
- 特点:
- 键值对作为整体存储在连续内存中。
- 查找需遍历,效率较低(O(n)),适合少量数据或频繁遍历场景。
方法二:自定义结构体
struct KeyValue {
std::string key;
int value;
};
std::vector<KeyValue> vec;
vec.push_back({"apple", 10});
- 适用场景:需扩展更多字段(如时间戳、状态等)。
3. 与专用键值容器的对比
以下对比 std::vector<std::pair> 与 std::map/std::unordered_map:
| 特性 | vector<std::pair> | std::map | std::unordered_map |
|---|---|---|---|
| 内存连续性 | 连续存储,缓存友好 | 红黑树节点分散 | 哈希表桶分散 |
| 查找复杂度 | O(n)(无序)/ O(log n)(有序+二分查找) | O(log n)(自动排序) | O(1) 平均(哈希表) |
| 插入效率 | 尾部插入 O(1),中间插入 O(n) | O(log n)(需调整红黑树) | O(1) 平均(可能触发 rehash) |
| 内存占用 | ⭐️ 低(仅需存储数据) | ⭐️⭐️ 高(每个节点含指针) | ⭐️⭐️ 高(桶+链表) |
| 是否自动去重/排序 | 需手动维护 | 按键排序且去重 | 去重,无序 |
4. 适用场景分析
-
推荐用
vector<std::pair>的情况:- 数据量小(≤1000),需频繁遍历或按索引访问(如渲染数据列表)。
- 键值对一次性加载,后续仅需遍历(如配置文件读取)。
- 内存敏感场景(嵌入式系统)。
-
推荐用
map/unordered_map的情况:- 需高频按键查找、插入或删除(如缓存、字典)。
- 需自动去重或排序(如词频统计)。
- 数据量较大(>1000)且对查找效率敏感。
5. 注意事项
- 查找效率:
vector的线性查找效率低,若需高效查找,需先排序再用std::binary_search(复杂度 O(log n))。 - 插入开销:在
vector中间插入键值对需移动后续元素,避免频繁操作。 - 线程安全:与所有 STL 容器一样,多线程环境下需手动加锁。
6.总结
std::vector 本身不原生支持键值对,但可通过 std::pair 或自定义结构间接实现。其优势在于内存紧凑和遍历高效,劣势是查找效率低。若需高频按键操作,应优先选择 std::map(有序)或 std::unordered_map(无序哈希表)。
简单来说:用索引访问选 vector,用键名查找选 map!
637

被折叠的 条评论
为什么被折叠?



