在C++中,我们平时定义的int a;或者MyClass obj;,它们的内存都是由编译器在“栈”上自动管理的,函数结束,内存就自动释放,省心又安全。但如果我们需要在程序运行时,根据实际需求动态地创建对象,就需要请出今天的主角——new和delete。它们赋予了我们手动控制内存的强大能力,但稍有不慎,就会引发“内存泄漏”这个恐怖的bug!

一、堆(Heap)与栈(Stack):C++内存的两大“地盘”

为了理解new,我们必须先知道C++程序内存的两个主要区域:

  • 栈 (Stack): 像一个干净整洁的书桌,编译器会自动管理。函数里的局部变量都放在这里。它的优点是速度超快全自动管理,你不用操心。缺点是空间有限,不能存放超大的数据。

  • 堆 (Heap): 像一个巨大但需要自己整理的仓库。它的空间非常大,我们可以在程序运行时自由申请和释放。new出来的对象就存放在这里。优点是空间大、灵活。缺点是你必须手动管理,借了就得还,否则仓库就乱了。

二、new 与 delete:借用与归还

1. new:从堆仓库申请一块内存

当你使用new时,你其实在向操作系统说:“嘿,请在堆上给我一块能放下XX类型的内存,并把这块内存的地址告诉我。”

#include <iostream>

class Player {
public:
    Player() { std::cout << "Player created on the Heap!" << std::endl; }
    ~Player() { std::cout << "Player destroyed!" << std::endl; }
};

int main() {
    // 申请一个int大小的内存,并用指针p_int指向它
    int* p_int = new int(100); 

    // 申请一个Player对象大小的内存,并用指针p_player指向它
    Player* p_player = new Player();

    std::cout << "Value of int: " << *p_int << std::endl;

    // ... 程序继续执行

    // 如果忘记归还,麻烦就来了!
    return 0;
}

2. delete:将内存归还给堆仓库

delete是你“还东西”的动作。它告诉操作系统:“这个地址上的内存我用完了,你收回去吧。”

// 正确的做法:有借有还!
    delete p_int;
    delete p_player;

    // 清理完后,最好将指针置为空,防止成为“野指针”
    p_int = nullptr;
    p_player = nullptr;

三、内存泄漏:有借无还的“灾难”

什么是内存泄漏?

当你用new从堆上申请了内存,但用完之后忘记用delete归还,这块内存就“丢失”了。你的程序再也无法访问它,操作系统也无法将它分配给别人。它就像一个被遗忘在仓库角落的包裹,占着地方,却无人认领。

如果这种情况在程序中反复发生(比如在一个循环里),程序占用的内存就会像滚雪球一样越来越大,最终可能导致程序变慢、卡顿,甚至整个系统崩溃!

四、别忘了数组:new[] 与 delete[] 必须配对!

当你需要动态创建一个数组时,要使用new[]。相应地,释放时也必须使用delete[]。

// 申请一个包含10个Player对象的数组
Player* player_array = new Player[10];

// 释放时,必须使用 delete[]
delete[] player_array; 
player_array = nullptr;

千万记住:new配delete,new[]配delete[]。如果你用delete去释放一个用new[]创建的数组,只会释放第一个元素的内存,剩下的9个都会泄漏,造成灾难!

总结

手动管理内存是C++强大之处,也是其复杂性的来源。养成良好的习惯是避免内存泄漏的关键:每一次new,都要在脑海里问自己:“我将来会在哪里delete它?”。当然,现代C++为了解决这个难题,引入了“智能指针”这种神器,它能自动管理内存,我们将在未来的教程中深入探讨。但理解new和delete的底层原理,是你成为C++高手的必经之路!💡

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐