GESP 6级

new / delete 动态内存

6级 · 指针/STL/复杂度
💡 为什么重要

普通变量在程序运行前就确定了大小(编译时分配),但有时我们运行时才知道需要多少空间(比如让用户输入数组大小)。动态内存分配让我们在程序运行时向系统"申请"内存。链表、树等动态数据结构都依赖 new/delete。GESP 6级考试经常考查 new/delete 的正确用法和内存泄漏问题。

📖 前置知识
  • 指针基础(指针声明、& 取地址、* 解引用)
  • 数组的基本使用
  • 变量的作用域概念
🔍 栈内存 vs 堆内存

计算机给程序分配的内存分为两个主要区域:

  • 栈(Stack):自动管理。你声明的普通变量(如 int a = 5;)都在栈上。函数结束时自动释放。速度快,但空间有限(通常几MB)。
  • 堆(Heap):手动管理。用 new 申请的内存都在堆上。必须手动用 delete 释放。空间大,但需要自己管。

打个比方:栈像是图书馆的阅读座位(用完自动回收),堆像是你租的储物间(需要你自己打扫归还)。

📐 核心语法
int *p = new int; // 在堆上申请1个int的空间
int *p = new int(10); // 申请并初始化为10
int *arr = new int[n]; // 申请n个int的数组

delete p; // 释放单个变量
delete[] arr; // 释放数组(注意[])

★ new 返回的是指针(地址)
★ 用完必须 delete,否则内存泄漏
★ delete后指针变为悬空指针,建议置为nullptr
💻 代码详解
📝 示例1:new/delete 基本用法
#include <iostream>
using namespace std;

int main() {
    // 1. 动态分配单个变量
    int *p = new int;     // 在堆上申请1个int大小的空间
                          // new返回这块空间的地址,赋给p
    *p = 42;              // 通过指针给这块空间赋值
    cout << *p << endl;  // 输出42
    delete p;             // 释放p指向的堆内存
                          // ★ 用完一定要delete!

    // 2. 动态分配并初始化
    int *q = new int(100);  // 申请1个int,并初始化为100
    cout << *q << endl;    // 输出100
    delete q;               // 释放

    // 3. 动态分配数组
    int n;
    cout << "输入数组大小: ";
    cin >> n;              // 运行时才知道大小!

    int *arr = new int[n];  // 动态申请n个int的数组
    for (int i = 0; i < n; i++) {
        arr[i] = i * 10;    // 和普通数组一样用下标访问
    }
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";  // 0 10 20 ...
    }
    cout << endl;

    delete[] arr;           // 释放数组,注意delete后面的[]
                            // ★ 数组用 delete[],不是 delete!

    return 0;
}
📝 示例2:动态二维数组
#include <iostream>
using namespace std;

int main() {
    int n, m;
    cout << "输入行数和列数: ";
    cin >> n >> m;

    // 动态创建 n 行 m 列的二维数组
    int **grid = new int*[n];   // 申请n个指针(n行)
    for (int i = 0; i < n; i++) {
        grid[i] = new int[m](); // 每行申请m个int,()表示初始化为0
    }

    // 使用:和普通二维数组一样
    grid[0][0] = 1;
    grid[1][2] = 5;

    // 打印
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cout << grid[i][j] << " ";
        }
        cout << endl;
    }

    // 释放:先释放每行,再释放指针数组
    for (int i = 0; i < n; i++) {
        delete[] grid[i];   // 先释放每一行
    }
    delete[] grid;          // 再释放行指针数组
    // ★ 顺序不能反!先内后外

    return 0;
}
📝 示例3:内存泄漏演示
#include <iostream>
using namespace std;

int main() {
    // ★ 反面教材:内存泄漏
    int *p = new int(10);   // 申请了一块堆内存

    // 如果这里直接让p指向别处...
    p = new int(20);         // p现在指向新的堆内存
    // 之前的那块内存(存着10的)就没有指针指向它了
    // 程序再也无法释放它 → 这就是"内存泄漏"!

    delete p;                // 只能释放后面的20那块
                             // 前面的10那块永远泄漏了

    // ★ 正确做法:
    int *q = new int(10);
    delete q;                // 先释放
    q = new int(20);         // 再申请新的
    delete q;                // 用完再释放

    return 0;
}
⚠️ 易错点
  • 忘记 delete:程序结束前不释放,堆内存泄漏。小程序影响不大,但大程序会越来越占内存
  • delete 和 delete[] 混用:用 new[] 申请的数组必须用 delete[] 释放,用 new 申请的单变量用 delete。混用是未定义行为,可能崩溃
  • 释放后继续使用:delete p 后,p 变成悬空指针(指向已释放的内存)。再 *p 读写都是危险的。释放后建议 p = nullptr;
  • 重复释放:对同一个指针 delete 两次,第二次释放的内存可能已被别人使用,导致崩溃
  • 用 delete 释放栈内存:只有 new 申请的内存才能 delete,栈上的变量不能 delete
  • 返回局部变量的地址:函数返回局部变量的指针是危险的,因为函数结束后局部变量就不存在了
🎯 练习建议
  • 入门:用 new 动态创建一个 int,赋值后打印,最后 delete
  • 进阶:让用户输入 n,动态创建数组,输入 n 个数,求最大值,最后 delete[]
  • 挑战:动态创建一个 n×m 的二维数组,实现矩阵转置(行变列,列变行)
  • 思考:如果用 new 创建的数组要扩容怎么办?(提示:需要创建新数组、复制数据、释放旧数组)
  • 尝试故意制造内存泄漏,用任务管理器观察程序内存占用变化