数据结构:静态数组(Static Array)和动态数组(Dynamic Array)

目录

静态数组(Static Arrays)

 动态数组(Dynamic Arrays)

为什么原始数组不能直接扩容?

为什么数组有“静态”和“动态”两种方式? 

最底层的动机:权衡效率 vs 灵活性


静态数组(Static Arrays)

静态数组是指在编译时或函数调用时就确定大小、由编译器自动分配和释放内存的数组。数组大小是确定不变的(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)       | ← 与静态数组无关
+-------------------+ ← 高地址

 动态数组(Dynamic Arrays)

动态数组是指在运行时使用关键字 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

最底层的动机:权衡效率 vs 灵活性

属性 静态数组(栈) 动态数组(堆)
 速度 极快(寄存器 + 栈帧) 稍慢(堆分配器)
 灵活性 固定大小 任意大小
 风险 栈空间有限 内存泄漏、碎片
控制 自动释放 手动控制生命周期

 这是系统设计对“资源受限环境”和“灵活程序逻辑”之间的一种平衡策略。

你可能感兴趣的:(数据结构,数据结构)