在C++标准库中,vector
的底层实现是一个动态数组,其核心原理和关键特性如下:
一、底层数据结构
- 连续内存空间
- 使用连续内存块存储元素,支持O(1)时间的随机访问(通过下标或指针算术)。
- 内部通过三个指针管理内存:
_Myfirst
:指向数组的首元素。_Mylast
:指向最后一个有效元素的下一个位置(等价于size()
)。_Myend
:指向已分配内存的末尾(等价于capacity()
)。
- 动态扩容机制
- 当插入元素导致
size() == capacity()
时,触发扩容:- 分配新内存(通常为旧容量的 2倍 或 1.5倍,具体由编译器实现决定)。vs 一般是1.5倍 gcc 2倍。
- 将旧元素移动或拷贝到新内存(C++11后优先使用移动语义)。
- 释放旧内存。
- 扩容使操作均摊时间复杂度为 O(1)(例如,插入n个元素的总时间为O(n))。
- 2倍扩容为什么不好?空间浪费。扩容时无法复用已经释放的内存。优点:扩容次数相对较少。
- 扩容是采用malloc还是new? 分配器对内存分配和构造进行分离。使用malloc 分配内存,使用placement new 构造对象。
- 怎么避免扩容带来的移动代价? 在能预测元素的个数的情况下,预分配空间。
- 当插入元素导致
二、关键操作复杂度
操作 | 时间复杂度 | 说明 |
---|---|---|
随机访问([] , at ) | O(1) | 直接通过内存偏移访问。 |
尾部插入/删除 | O(1) 均摊 | 可能触发扩容/缩容。 |
头部或中间插入/删除 | O(n) | 需要移动后续元素。 |
三、迭代器失效场景
1. 扩容引起迭代器失效,
2. 元素移动导致迭代器失效
3. `reserve()`**/**`shrink_to_fit()`:可能改变内存地址,导致迭代器失效。
四、性能优化技巧
- 预分配内存
使用reserve()
提前分配足够容量,避免多次扩容:
std::vector<int> vec;
vec.reserve(100); // 预分配100个元素的容量
- 减少中间插入
频繁在头部或中间插入时,考虑改用std::deque
或std::list
。 - 利用移动语义
对于占用资源较多的对象(如std::string
),使用std::move
避免拷贝:
std::vector<std::string> vec;
std::string s = "data";
vec.push_back(std::move(s)); // 移动而非拷贝
emplace_back 相对 push_back 有性能提升。主要因为就地构造
五、与其他容器的对比
特性 | vector | std::list | std::deque |
---|---|---|---|
内存布局 | 连续内存 | 双向链表 | 分块连续内存 |
随机访问 | O(1) | O(n) | O(1) |
尾部插入/删除 | 高效 | 高效 | 高效 |
中间插入/删除 | 低效 | 高效 | 低效 |
迭代器失效 | 易失效 | 局部失效 | 局部失效 |