
博主名称:deM0one ⭐️C++专栏: C++
文章目录
- [深入理解 vector:从二维数组到 STL 源码剖析](#深入理解 vector:从二维数组到 STL 源码剖析)
-
- 一、引言
- [二、二维数组:vector 的嵌套使用](#二、二维数组:vector 的嵌套使用)
-
- [2.1 基本用法](#2.1 基本用法)
- [2.2 二维 vector 的底层结构](#2.2 二维 vector 的底层结构)
- [三、模拟实现:从 operator\[\] 看模板实例化](#三、模拟实现:从 operator[] 看模板实例化)
-
- [3.1 vector<int> 的 operator\[\]](#3.1 vector
的 operator[]) - [3.2 vector<vector<int>> 的 operator\[\]](#3.2 vector<vector
> 的 operator[]) - [3.3 对比总结](#3.3 对比总结)
- [3.1 vector<int> 的 operator\[\]](#3.1 vector
- [四、实战应用:LeetCode 118 杨辉三角](#四、实战应用:LeetCode 118 杨辉三角)
-
- [4.1 题目要求](#4.1 题目要求)
- [4.2 C 语言解法(指针数组)](#4.2 C 语言解法(指针数组))
- [4.3 C++ vector 解法](#4.3 C++ vector 解法)
- [4.4 两种解法对比](#4.4 两种解法对比)
- [五、源码剖析:push_back 的实现](#五、源码剖析:push_back 的实现)
-
- [5.1 push_back 完整源码(SGI STL)](#5.1 push_back 完整源码(SGI STL))
- [5.2 construct:定位 new(placement new)](#5.2 construct:定位 new(placement new))
- [5.3 迭代器即指针](#5.3 迭代器即指针)
- [六、源码剖析:insert_aux 的扩容机制](#六、源码剖析:insert_aux 的扩容机制)
-
- [6.1 分支一:空间充足](#6.1 分支一:空间充足)
- [6.2 分支二:需要扩容](#6.2 分支二:需要扩容)
- [6.3 扩容流程图解](#6.3 扩容流程图解)
- [6.4 内存图解](#6.4 内存图解)
- [七、reserve 的模拟实现](#七、reserve 的模拟实现)
- 八、迭代器失效
- 九、总结与延伸
-
- [9.1 核心要点回顾](#9.1 核心要点回顾)
- [9.2 延伸思考](#9.2 延伸思考)
深入理解 vector:从二维数组到 STL 源码剖析
一、引言
- 为什么学习 vector 的底层实现
- 本篇博客的学习路径:应用 → 模拟实现 → 源码剖析
- 参考书目:《STL 源码剖析》(侯捷)
二、二维数组:vector 的嵌套使用
2.1 基本用法
cpp
vector<int> v(5, 1);
vector<vector<int>> vv(10, v); // 10 行,每行 5 个 1
vv[2][1] = 2; // 修改第 3 行第 2 列
2.2 二维 vector 的底层结构
vector<vector<int>>的外层元素是vector<int>对象- 外层连续存储"内层 vector 对象",内层各自管理自己的堆内存
- 图解:外层
_start/_finish/_end_of_storage指向内层对象数组
三、模拟实现:从 operator\[\] 看模板实例化
3.1 vector 的 operator\[\]
cpp
class vector {
int& operator[](int i) {
assert(i < _size);
return _a[i];
}
private:
int* _a;
size_t _size;
size_t _capacity;
};
3.2 vector<vector> 的 operator\[\]
cpp
class vector<vector<int>> {
vector<int>& operator[](int i) {
assert(i < _size);
return _a[i];
}
private:
vector<int>* _a;
size_t _size;
size_t _capacity;
};
- 定义方式:先构造一维
vector<int> v(5, 1),再构造vector<vector<int>> vv(10, v),得到一个 10 行 5 列的二维数组 - 访问方式:
vv[2][1],外层operator[]返回vector<int>&,内层再返回int& - 本质:二维 vector 就是"每个元素是一个 vector 的数组",对应 C 语言里的指针数组思想
3.3 对比总结
- 同一个类模板,实例化出不同的具体类型
- 返回值从
int&变为vector<int>&,指针从int*变为vector<int>* - 模板的本质:编译期生成代码
四、实战应用:LeetCode 118 杨辉三角
4.1 题目要求
- 生成前 numRows 行的杨辉三角
4.2 C 语言解法(指针数组)
cpp
int** generate(int numRows, int* returnSize, int** returnColumnSizes) {
*returnColumnSizes = malloc(numRows * sizeof(int));
// ... 手动管理每一行的内存
}
- 缺点:手动 malloc/free,内存管理复杂,容易出错
4.3 C++ vector 解法
cpp
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> vv(numRows);
for (size_t i = 0; i < vv.size(); ++i) {
vv[i].resize(i + 1, 1); // vv[i][0] = vv[i][i] = 1;
}
for (int i = 2; i < vv.size(); ++i) {
for (int j = 1; j < vv[i].size() - 1; ++j) {
vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];
}
}
return vv;
}
};
vector<vector<int>> generate(int numRows)- 步骤:
vector<vector<int>> vv(numRows)先确定行数- 每行
vv[i].resize(i + 1, 1)初始化 - 首尾元素置 1:
vv[i].front()/vv[i].back() - 递推填充中间元素:
vv[i][j] = vv[i-1][j-1] + vv[i-1][j]
4.4 两种解法对比
- 代码量:C++ 版显著更短
- 内存管理:C++ 自动管理,C 需要手动释放
- 安全性:vector 带越界检查(assert),C 指针无保护
五、源码剖析:push_back 的实现
5.1 push_back 完整源码(SGI STL)
cpp
void push_back(const T& x) {
if (finish != end_of_storage) {
construct(finish, x);
++finish;
} else {
insert_aux(end(), x);
}
}
5.2 construct:定位 new(placement new)
cpp
template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
new(p) T1(value); // 在指定内存上构造对象
}
- 与
malloc的区别:malloc 只分配内存,construct 负责构造对象 - 为什么不用
*p = value:T1 可能没有默认构造函数/赋值运算符
5.3 迭代器即指针
cpp
iterator begin() { return start; }
iterator end() { return finish; }
size_type size() const { return finish - start; }
size_type capacity() const { return end_of_storage - start; }
- vector 的迭代器本质就是原生指针
_start、_finish、_end_of_storage三个指针的语义
六、源码剖析:insert_aux 的扩容机制
6.1 分支一:空间充足
cpp
if (finish != end_of_storage) { // 有足够空间
construct(finish, x);
++finish;
copy_backward(position, finish - 1, finish); // 元素后移
}
6.2 分支二:需要扩容
cpp
const size_type old_size = size();
const size_type len = old_size ? 2 * old_size : 2; // 两倍扩容
iterator new_start = data_allocator::allocate(len);
iterator new_finish = uninitialized_copy(start, position, new_start);
construct(new_finish, x); // 在中间构造新元素
++new_finish;
new_finish = uninitialized_copy(position, finish, new_finish);
// 释放旧空间,更新指针
destroy(start, finish);
data_allocator::deallocate(start, old_size);
start = new_start;
finish = new_finish;
end_of_storage = start + len;
6.3 扩容流程图解
原数组: [1][2][3][4][5] 容量 5,插入 30 到 pos
扩容后: [1][2][30][3][4][5][_][_][_][_] 容量 10
↑pos ↑new_finish
- 三步走:前段拷贝 → 构造新元素 → 后段拷贝
- 扩容倍率:
2 * old_size(GCC 两倍增长) - 迭代器失效的本质:旧空间被释放,旧迭代器成为野指针
6.4 内存图解
_start→ 首元素地址_finish→ 最后一个元素的下一个位置_end_of_storage→ 容量终点
七、reserve 的模拟实现
- 手写
reserve流程n > capacity()才扩容new T[n]→memcpy拷贝旧数据 →delete[] start- 更新三个指针:
start = tmp; finish = start + old_size; end_of_storage = start + n;
- 调试器观察
- 监视窗口查看
_start、_finish、_end_of_storage地址变化 - 新分配内存中的
0xCDCDCDCD等调试填充值
- 监视窗口查看
- 坑点:
memcpy是浅拷贝,对vector<string>等非 POD 类型会出错- 调试时出现"读取字符串字符时出错"
- 正确做法:for 循环逐元素赋值(深拷贝),或使用
uninitialized_copy
cpp
void reserve(size_t n) {
if (n > capacity()) {
T* tmp = new T[n];
memcpy(tmp, _start, size() * sizeof(T));
delete[] _start;
_start = tmp;
_finish = _start + size();
_end_of_storage = _start + n;
}
}
- 只在 n 大于当前容量时才扩容
- 三指针更新顺序:先建新空间,再搬运,后释放旧空间
- 注意:memcpy 是浅拷贝,对 string 等含资源的类型不安全(C++11 后应使用拷贝构造/移动语义)
八、迭代器失效
insert后数据发生挪动,原迭代器pos指向的元素已变,视为失效- 失效迭代器不要访问;VS2019 会强制检查,访问即报错
erase返回新位置迭代器:指向被删除元素的后一个元素
九、总结与延伸
9.1 核心要点回顾
- vector 的本质:三个指针管理的动态数组
- 扩容策略:两倍增长,均摊 O(1) 的 push_back
- placement new 在 STL 中的重要性
- 迭代器失效问题的根源
9.2 延伸思考
- 为什么 STL 用
construct/destroy而不是new[]/delete[] uninitialized_copy与普通copy的区别- C++11 之后 vector 的内存管理有何变化(移动语义、右值引用)
- 对比 GCC 与 MSVC 的扩容策略差异
- 动手实现一个自己的
my_vector(完整版:构造/析构/拷贝/赋值/迭代器)
任何的批评与建议欢迎指出,我们共同进步!