栈内存与堆内存
本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 12 课,共 104 课。
C++ 程序使用两个主要的内存区域:栈和堆。了解对象存储在哪里,对于编写高效、无 bug 的代码至关重要。
栈是速度快、自动管理的内存。在函数内声明的变量存储在这里,超出作用域时会自动销毁。
void example() {
int x = 10; // 栈上分配
Player player; // 栈上分配,自动销毁
} // x 和 player 在这里被销毁堆是容量更大但速度更慢的内存,需要你手动管理。堆上的对象会一直存在,直到你明确删除它们。
void example() {
Player* player = new Player(); // 堆内存分配
// player 会一直存在,直到被删除
delete player; // 需要手动清理
}主要区别:
| 栈 | 堆 |
|---|---|
| 分配速度快 | 分配速度较慢 |
| 大小有限 | 容量大 |
| 自动清理 | 需要手动清理 |
| 固定大小的对象 | 动态大小的对象 |
对于较小且生命周期较短的对象,使用栈分配。如果对象需要在其作用域结束后继续存在,或者其大小在运行时才确定,则使用堆分配。
忘记释放堆内存会导致内存泄漏,这是 C++ 程序中常见的错误来源。
挑战
简单我们来创建一个简单的 Counter 类,跟踪对象的创建和销毁,从而了解栈内存和堆内存的区别。
你需要创建两个文件来组织代码:
Counter.h:定义一个Counter类,其中包含一个私有name属性(字符串)。添加一个接收名称并输出"Creating: <name>"的构造函数,以及一个输出"Destroying: <name>"的析构函数。添加一个getName()方法,用于返回计数器的名称。main.cpp:演示栈分配和堆分配。读取一个输入名称,然后:- 使用该名称创建一个在栈上分配的
Counter - 创建一个在堆上分配、名称为
"HeapCounter"的Counter - 按照
"Stack: <name>"和"Heap: <name>"的格式输出两个计数器的名称 - 删除在堆上分配的计数器,以防止内存泄漏
- 使用该名称创建一个在栈上分配的
观察构造函数和析构函数的消息如何根据创建和删除各个对象的时机,以不同顺序出现。当 main() 结束时,栈上的对象会自动销毁,而堆上的对象必须显式删除。
在 main.cpp 中使用 #include "Counter.h" 引入头文件。
亲手试试
#include <iostream>
#include <string>
#include "Counter.h"
using namespace std;
int main() {
// 从输入中读取名称
string inputName;
cin >> inputName;
// TODO:使用 inputName 创建一个在栈上分配的 Counter 对象
// TODO:创建一个在堆上分配的 Counter 对象,名称为 "HeapCounter"
// TODO:按 "Stack: <name>" 格式输出栈上 Counter 对象的名称
// TODO:按 "Heap: <name>" 格式输出堆上 Counter 对象的名称
// TODO:删除在堆上分配的 Counter 对象,防止内存泄漏
return 0;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器