在C++程序里,当我们需要处理数量在运行期才能确定的连续同类型元素时,就必须用到动态数组。和静态数组不同,动态数组的生命周期和大小都由程序员或标准库在运行时控制,内存通常来自堆区。掌握它的定义方式,是写出健壮C++代码的基础。

一、使用new和delete[]原始定义
最基础的定义动态数组的方式,是通过new[]运算符向堆申请一段连续内存,返回一个指向首元素的指针。使用完毕后必须用delete[]释放,否则会造成内存泄漏。这种方式不给任何边界检查,纯粹把内存管理责任交给开发者。
下面示范如何定义一个长度为n的int动态数组,并进行简单读写:
#include <iostream>
int main() {
int n = 5;
// 在堆上分配长度为n的int数组
int* arr = new int[n];
for (int i = 0; i < n; ++i) {
arr[i] = i * 10;
}
for (int i = 0; i < n; ++i) {
std::cout << arr[i] << " ";
}
// 必须手动释放,注意是delete[]而不是delete
delete[] arr;
return 0;
}
这种写法的优点是与C语言兼容、开销极小,适合极端关注性能或对内存布局有特殊要求的底层模块。缺点是容易忘记释放、发生异常时可能跳过delete[],而且数组大小变化要自己重新分配并拷贝,非常繁琐。
如果在函数中间抛出了异常,而释放语句在后面,那么这段堆内存就永远回收不了。因此实际工程中,除非写极其底层的库,否则不建议频繁使用裸new[]。
二、使用std::vector标准容器
C++标准库提供的std::vector是最推荐的动态数组定义方式。它在内部封装了堆内存管理,支持在运行期指定大小,也能在后续通过push_back等方法自动扩容。当vector离开作用域时,析构函数会自动释放内存,从根本上避免了泄漏。
我们可以用多种方法定义一个vector动态数组,例如指定大小并赋初值,或者先空着再添加元素:
#include <iostream>
#include <vector>
int main() {
// 定义长度为5、每个元素为0的int动态数组
std::vector<int> vec(5, 0);
vec[0] = 10;
vec.push_back(99); // 动态增长,不需要手动重分配
for (int i = 0; i < vec.size(); ++i) {
std::cout << vec[i] << " ";
}
// 不需要手动delete,析构时自动回收
return 0;
}
vector不仅管理内存,还提供了size()、capacity()、at()等成员函数。其中at()会做越界检查,抛出std::out_of_range异常,比直接用方括号更安全。对于大多数业务代码,vector在安全性与性能之间取得了很好的平衡。
需要注意的是,vector在扩容时会分配新内存并拷贝旧元素,如果提前用reserve()预留容量,就能减少拷贝开销。另外,在传递vector参数时,建议用引用或const引用,避免不必要的拷贝。
三、使用智能指针管理new[]
如果确实想保留new[]的显式语义,又希望获得自动释放能力,可以用std::unique_ptr或std::shared_ptr配合数组形式。C++11之后,unique_ptr<T[]>能正确调用delete[],比裸指针安全。
下面代码展示了用unique_ptr定义动态数组,并借助get()访问底层指针:
#include <iostream>
#include <memory>
int main() {
int n = 4;
// 用unique_ptr管理int动态数组
std::unique_ptr<int[]> pArr(new int[n]);
for (int i = 0; i < n; ++i) {
pArr[i] = i + 1;
}
for (int i = 0; i < n; ++i) {
std::cout << pArr[i] << " ";
}
// 离开作用域自动delete[]
return 0;
}
智能指针方式兼顾了“手动控制内存来源”和“自动释放”的优点,适合需要与旧接口交互、或必须在特定时机前保有数组所有权的场景。不过它不具备vector那样的丰富方法和自动扩容,使用起来更偏底层。
如果你的代码既要动态大小又要方便操作,仍然首选vector;只有在特殊约束下,才考虑智能指针加new[]的组合。三种方式的核心区别,其实就在于谁来管理那块堆内存的生命周期。
四、方式对比与选择建议
为了更直观,我们把三种定义方式在几个关键维度上做个比较:
| 定义方式 | 内存释放 | 扩容能力 | 安全性 | 适用场景 |
|---|---|---|---|---|
| new[]/delete[] | 手动 | 无,需自写 | 低 | 底层库、兼容C |
| std::vector | 自动 | 内置 | 高 | 绝大多数业务代码 |
| unique_ptr<T[]> | 自动 | 无 | 中 | 需显式堆数组且求安全 |
从工程角度看,优先使用vector可以显著降低缺陷率;当性能剖析证明vector的扩容或容器开销成为瓶颈,再考虑另两种方式。理解它们怎么定义,也就理解了C++里资源管理的不同哲学。
无论选哪种,都不要在同一个指针上重复释放,也不要把栈上数组的地址交给管理堆的释放逻辑。清晰的所有权边界,才是动态数组用得稳的关键。