除了是"动态数组",大小可变 (运行时自动调整),vector更重要的一点是能自动管理内存。
它是一个在栈上的控制者,管理着在堆上的数据。
假设你写了这样一行代码:
cpp
// 在栈上声明了一个 vector,并初始化了 3 个元素
std::vector<int> v = {10, 20, 30};
内存里发生了什么?请看下图:
| 内存区域 | 变量/内容 | 说明 |
|---|---|---|
| 栈 (Stack) | 变量 v (控制块) |
这里只有 3 个指针(_start, _finish, _end)。 通常占用 24 字节 (64位系统)。 它是自动销毁的。 |
| ↓ 指向 | ||
| 堆 (Heap) | 数据 {10, 20, 30} |
真正存数据的地方。 由 v 内部的 allocator 动态申请。 大小取决于元素数量。 |
cpp
#include <iostream>
#include <vector>
int main() {
// 1. 这是一个局部变量,v 本身在栈上
std::vector<int> v = {1, 2, 3, 4, 5};
// 2. 打印 v 这个"对象本身"的地址 (栈)
std::cout << "Vector 对象本身的地址 (Stack): " << &v << std::endl;
// 3. 打印 v 管理的"第一个数据"的地址 (堆)
// v.data() 返回的是堆内存的首地址
std::cout << "Vector 内部数据的地址 (Heap) : " << v.data() << std::endl;
// 4. 对比一个典型的栈变量
int stackVar = 100;
std::cout << "普通局部变量的地址 (Stack): " << &stackVar << std::endl;
// 5. 对比一个手动 new 出来的堆变量
int* heapVar = new int(100);
std::cout << "手动 new 变量的地址 (Heap) : " << heapVar << std::endl;
delete heapVar;
return 0;
}
cpp
sun@sun-Legion-Y9000P-IAH7H:~/03_My_learn/C++/vector$ g++ test.cpp -o test
sun@sun-Legion-Y9000P-IAH7H:~/03_My_learn/C++/vector$ ./test
输出:
cpp
Vector 对象本身的地址 (Stack): 0x7ffdf60fd890
Vector 内部数据的地址 (Heap) : 0x55bdb9f26eb0
普通局部变量的地址 (Stack): 0x7ffdf60fd884
手动 new 变量的地址 (Heap) : 0x55bdb9f272e0
可以看出,Vector 对象本身的地址 和普通局部变量的地址 距离很近,都在栈 上;Vector 内部数据的地址 和 手动 new 变量的地址距离很近,都在堆上。
这种栈上控制,堆上存储的设计是 C++ STL 的精髓:
1.栈(Stack)很小且固定 :栈的大小通常只有几 MB(例如 8MB)。如果你试图在栈上创建一个巨大的数组(比如 int arr[10000000]),程序会直接崩溃。
用ulimit -s查看栈内存的最大限制:
cpp
sun@sun-Legion-Y9000P-IAH7H:~/03_My_learn/C++/vector$ ulimit -s
8192
约为8M
如果运行下面的程序,会出现段错误。
int arr[10'000'000] 大约需要 10^7 * 4字节 ≈ 40MB。
cpp
#include <iostream>
#include <vector>
int main() {
int arr[10'000'000];
return 0;
}
cpp
sun@sun-Legion-Y9000P-IAH7H:~/03_My_learn/C++/vector$ g++ test.cpp -o test
sun@sun-Legion-Y9000P-IAH7H:~/03_My_learn/C++/vector$ ./test
段错误 (核心已转储)
改用vector:
cpp
#include <iostream>
#include <vector>
int main() {
// 在堆上申请 1000 万个 int
// v 本身(控制块)在栈上,只占 24 字节。
std::vector<int> v(10'000'000);
// 写入数据证明内存可用
v[0] = 100;
v[9999999] = 200;
return 0; // 退出时,v 析构,自动释放堆内存
}
40MB 的数据被 vector 安排到了堆内存区域。
2.堆(Heap)很大且动态 :堆的大小仅受物理内存限制(几个 GB 甚至 TB)。vector 把数据放在堆上,所以它可以容纳成千上万个元素,而且可以随时扩容。
3.自动管理 :虽然数据在堆上(正常需要手动 delete),但控制块 v 在栈上。当超出作用域时,栈上的 v 会自动销毁,v 的析构函数会被触发,进而自动去堆上把那块数据释放掉 。这也是RAII机制的体现。
vector的缺点是存在迭代器失效现象,补充一下vector迭代器失效的几种情况。
1.遍历删除元素,未更新迭代器
cpp
/*
* 迭代器失效情况1:遍历删除元素,未更新迭代器
在某些编译器(如 GCC)的特定版本中,vector的erase(iter)实现会将被删除元素之后的所有元素向前移动一位。
*/
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vec = {1};
for(auto iter = vec.begin(); iter != vec.end(); )
{
//if(*iter == 3)
cout << *iter << " ";
iter = vec.erase(iter);
if(iter!=vec.end())
cout << *iter << endl;
}
return 0;
}
2.插入新元素触发动态扩容机制,导致内存重新分配,原有的迭代器失效
cpp
/*
* 插入新元素触发动态扩容机制,导致内存重新分配,原有的迭代器失效
* 应该在插入操作之后重新获取迭代器
*/
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vec;
// 验证扩容倍数
//for(int i = 0; i <= 10; ++i){
//vec.emplace_back(i);
//cout << "vec的capacity:" << vec.capacity() << endl;
//}
vec.reserve(2);
vec.emplace_back(1);
vec.emplace_back(2);
auto it = vec.begin();
vec.emplace_back(3);
it = vec.begin();// 插入元素后应该重新获取迭代器
cout << *it << endl;
return 0;
}
3.删除某个元素导致后面元素整体前移
cpp
/*
* 删除某个元素导致后面元素整体前移
* 若当前迭代器之前的某个元素被删除,应该在删除后重新计算迭代器的位置
*
*/
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vec = {1,2,3,4,5,6,8,9};
auto iter = vec.begin() + 5;
cout << *iter << endl;
vec.erase(vec.begin() + 3);
cout << *iter << endl;
return 0;
}