目录
静态数组(Static Arrays)
动态数组(Dynamic Arrays)
为什么原始数组不能直接扩容?
为什么数组有“静态”和“动态”两种方式?
最底层的动机:权衡效率 vs 灵活性
静态数组是指在编译时或函数调用时就确定大小、由编译器自动分配和释放内存的数组。数组大小是确定不变的(static)。
它存储在:
栈区(stack)(局部数组,如 int A[5];
)
或者 静态/全局区(如 static int A[5];
或全局数组)
int A[5]; // 局部静态数组(分配在栈)
static int B[5]; // 静态数组(分配在全局/静态区)
特点:
特性 | 描述 |
---|---|
分配位置 | 栈(默认局部数组)或数据段(static / 全局) |
生命周期 | 栈数组:离开作用域自动释放;静态数组:程序运行期间一直存在 |
效率 | 高效、连续内存、无碎片 |
大小 | 必须是编译期已知的固定大小(或 const 常量) |
安全性 | 比动态数组更易管理(不需要手动释放) |
内存结构图
+-------------------+ ← 低地址(地址编号小)
| 代码区(Text) | ← 程序指令
+-------------------+
| 静态/全局区(Data/BSS) |
| static int B[5]; | ← 静态数组(如 static 或全局)
+-------------------+
| 栈区(Stack) | ← 函数调用相关
| int A[5]; | ← 静态局部数组
| |
| ↓ 向下增长 |
+-------------------+
| |
| |
| |
| |
| ↑ |
| 堆区(Heap) | ← 与静态数组无关
+-------------------+ ← 高地址
动态数组是指在运行时使用关键字 new
(或 malloc
)在堆区(heap)中分配的数组,大小可以在程序执行时确定,故为动态(dynamic)。
你必须手动释放内存,否则会内存泄漏。
int* A = new int[5]; // 动态数组(C++)
delete[] A; // 手动释放内存
// 或者 C风格
int* B = (int*)malloc(5 * sizeof(int));
free(B);
特点:
特性 | 描述 |
---|---|
分配位置 | 堆区(heap),运行时分配 |
生命周期 | 直到程序手动释放内存 |
大小 | 可在运行时动态确定 |
效率 | 分配稍慢,可能产生内存碎片 |
管理 | 程序员负责内存释放(风险较高) |
内存结构图
+-------------------+ ← 低地址(地址编号小)
| 代码区(Text) | ← 程序指令
+-------------------+
| 静态/全局区(Data/BSS) |
+-------------------+
| 栈区(Stack) | ← 局部变量/函数调用
| int* A; | ← 存储指向堆区的指针
| |
| ↓ 向下增长 |
+-------------------+
| |
| ↑ 堆区(Heap) |
| [int][int][int]… | ← 实际数组内容在堆中
| new int[5] |
+-------------------+ ← 高地址(地址编号大)
❗ 原因一:数组大小固定、连续内存
C++ 中的原始数组(无论是静态还是动态)都是:
一段连续分配的内存块
大小在创建时就固定,编译器/操作系统已经为它划好内存边界
一旦数组被声明为:
int A[5] = {1,2,3,4,5};
你就不能这样写:
A[5] = 6; // ❌ 错误:越界访问,非法写入
❗ 原因二:数组后面可能已经被其他数据占用
内存不是一片空白,数组后面的内存可能被:
函数栈帧
其他变量
操作系统保留
你无法保证“数组后面有空位置”可扩展。即使有,操作系统也不会让你直接用。
内存图示:为什么不能扩容原数组
+------------+------------+------------+------------+------------+------------+
| A[0] = 1 | A[1] = 2 | A[2] = 3 | A[3] = 4 | A[4] = 5 | ??? |
+------------+------------+------------+------------+------------+------------+
↑
数组声明时分配的最后一个合法位置
← 后面空间不归你,不能用!
✅ 正确的方式是:创建一个更大的新数组,并拷贝旧数据
这种方法适用于动态数组,步骤如下:
示例(使用 new[]
和 delete[]
):
int* A = new int[5]; // 原数组
for (int i = 0; i < 5; i++) A[i] = i + 1; // 初始化
int* B = new int[10]; // 新的更大数组
for (int i = 0; i < 5; i++) B[i] = A[i]; // 拷贝旧数据
delete[] A; // 释放旧数组
A = B; // 指向新数组
现在 A
就成了一个“逻辑扩容”的数组,拥有了 10 个元素的空间。
现代操作系统将程序内存划分为多个区域:
+-------------------+ ← 低地址
| 代码区(只读) |
+-------------------+
| 静态/全局变量区 |
+-------------------+
| 栈区(stack) | ← 静态数组
| ↓ 向下增长 |
+-------------------+
| ↑ 向上增长 |
| 堆区(heap) | ← 动态数组
+-------------------+ ← 高地址
我们从“问题场景”来逆推设计动机:
问题一:我要一个数组,但我不知道它有多大 → 需要动态数组
静态数组限制:
int n;
cin >> n;
int A[n]; // 错误(除非支持 VLA 扩展)
动态数组解决:
int* A = new int[n];
动态数组允许在运行时才决定大小
→ 支持“用户输入”、“文件内容”、“图结构”等数据的存储需求
问题二:我只需要一个小数组,放到堆里效率太低 → 需要静态数组
动态分配开销:
new
/delete
比栈变量慢得多(涉及堆管理)
频繁堆分配容易导致碎片、缓存失效
静态数组解决:
int small[16]; // 栈中快速分配,无需释放
小规模、生命周期短的数据,栈(静态数组)效率更高,性能更好
问题三:我不想自己管理内存 → 静态数组有自动释放优势
void func() {
int A[100]; // 自动分配、自动释放,省心
}
静态数组适合生命周期明确、短暂的局部数据
问题四:我要的数据量太大,栈装不下 → 只能用堆(动态数组)
大多数操作系统的栈空间默认大小是 1~8 MB(很小)。
int A[10000000]; // ❌ 栈溢出(stack overflow)
堆空间则可能有 几 GB
int* A = new int[10000000]; // ✅ 正常
大型数据结构必须放在堆上
→ 需要动态数组(或者容器类如 std::vector
)
属性 | 静态数组(栈) | 动态数组(堆) |
---|---|---|
速度 | 极快(寄存器 + 栈帧) | 稍慢(堆分配器) |
灵活性 | 固定大小 | 任意大小 |
风险 | 栈空间有限 | 内存泄漏、碎片 |
控制 | 自动释放 | 手动控制生命周期 |
这是系统设计对“资源受限环境”和“灵活程序逻辑”之间的一种平衡策略。