普通数组无法动态扩容,必须手动模拟vector行为:先分配新内存、再迁移元素(禁用memcpy)、最后清理旧资源;resize改变size并构造新元素,reserve仅预分配容量。

普通数组本身不能动态扩容——它在栈上声明时大小固定,在堆上用 new 分配后,内存地址和长度也完全固化。想“像 vector 一样动态扩容”,本质是**手动模拟 vector 的底层行为**:申请新内存、复制/移动旧数据、释放旧内存、更新指针和计数器。
为什么不能直接对普通数组调用 realloc 或 resize
因为 C++ 没有 realloc 对非 POD 类型的安全支持,而 std::vector 的扩容也不是靠 realloc 实现的(realloc 只适用于纯二进制可拷贝的类型,且不调用构造/析构函数)。对 std::string、自定义类等类型直接 memcpy 会跳过构造函数,导致未定义行为。
-
int arr[10]是栈数组,生命周期和大小都由作用域决定,无法扩展 -
int* p = new int[10]分配的是裸内存块,delete[] p后指针就失效了,没有“扩容接口” - 试图用
realloc(p, 20 * sizeof(int))虽然对int可能凑合,但一旦换成std::string*就立刻崩溃——它不会调用新对象的构造函数,也不会对旧对象调用析构函数
手写扩容逻辑:三步不可少
所有靠谱的手动扩容,都绕不开这三个动作,顺序不能错:
-
先分配新内存:用
new T[new_capacity](或allocator::allocate),确保新空间能正确构造对象 -
再迁移元素:对每个已有元素,用
std::construct_at(C++20)或 placement new + 拷贝/移动构造;非 POD 类型绝不能memcpy -
最后清理旧资源:对每个旧元素显式调用析构函数(
p[i].~T()),再delete[] old_ptr
漏掉析构 → 资源泄漏;跳过构造 → 对象未初始化;顺序颠倒(比如先 delete[] 再构造)→ 访问野指针。
立即学习“C++免费学习笔记(深入)”;
resize 和 reserve 必须分开实现
这是新手最容易混用、导致内存越界或重复构造的点:
-
reserve(n):只改容量(capacity),不碰现有元素,也不改变size;用于预分配,避免多次扩容 -
resize(n):改逻辑大小(size),若n > size,则对新增位置调用默认构造;若n ,则对超出部分逐个析构
比如你写 push_back,判断 size == capacity 后该调 reserve(2 * capacity),而不是 resize(2 * capacity)——后者会把所有旧元素析构再默认构造一遍,数据全丢。
真正难的不是扩容,是异常安全和移动语义
如果 T 的拷贝构造可能抛异常(比如分配失败、文件打开失败),那在迁移过程中出错,必须保证容器处于有效状态:已构造的新对象要析构,新分配的内存要释放,旧数据不能丢失。这需要 try/catch + RAII 封装。
同理,没实现移动构造函数,扩容时所有对象都走拷贝,性能断崖下跌;而移动构造没加 noexcept,std::vector 在某些标准库实现中会主动降级为拷贝——这些细节不处理,写出来的“动态数组”只在玩具场景下能跑。


















