① 程序设计基础 — 指针进阶课
& 取地址,* 解引用,引用是别名,new / delete 分配释放。new 了不 delete 会出事。
new 出来的在堆上,要自己 delete。return 出去。你写的 C++ 程序运行时,操作系统给了它一块内存。习惯上分成四块:
| 区域 | 放什么 | 谁管理 | 什么时候消失 |
|---|---|---|---|
| 代码区 | 编译后的机器指令 | 系统 | 程序结束 |
| 全局区 | 全局变量、static 变量 | 系统 | 程序结束 |
| 栈 stack | 局部变量、函数参数 | 系统自动 | 函数返回后 |
| 堆 heap | new 出来的对象 | 程序员手动 | delete 之后 |
你之前写的 int a = 10; 大多数情况在栈上;写 int* p = new int; 时,p 这个指针本身在栈上,*p 那块内容在堆上。
void f() {
int x = 10; // x 在栈上
} // 函数结束,x 自动消失
int main() {
f();
// 这里已经没有 x 了,别想再用 x
return 0;
}
栈的好处:不用管清理,系统自动回收。坏处:生命周期太短,函数一返回就没了。
int* bad() {
int x = 10;
return &x; // x 在栈上,函数返回后 x 就死了
} // 返回的地址指向已死的内存 → 悬空指针
int main() {
int* p = bad();
cout << *p; // 未定义行为:可能碰巧对,可能乱码,可能崩
}
结论:局部变量的地址不要 return 出去。想让数据活得比函数久,就要用堆。
int* good() {
int* p = new int(10); // 在堆上创建 int,初值 10
return p; // 返回堆地址:合法!因为堆内存还活着
}
int main() {
int* p = good();
cout << *p; // 10
delete p; // 不用了,手动释放
p = nullptr; // 释放后清空指针,防止误用
return 0;
}
堆的好处:生命周期自己控制。坏处:忘了 delete 就会内存泄漏 —— 程序一直占着那块内存不还。
int n;
cin >> n;
int* arr = new int[n]; // 大小运行时才定,所以叫“动态”
for (int i = 0; i < n; i++) {
arr[i] = i * i;
}
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
delete[] arr; // 数组必须用 delete[],不能写 delete
arr = nullptr;
vector<int> v; 内部就是帮你自动 new[] / delete[]。所以能用 vector 就少自己写 new[]。这节课学这些,是为了理解 vector 底层到底发生了什么。
int* p = new int(5);
delete p; // 内存还回去了
// 但 p 仍然保存着原来的地址
cout << *p; // 悬空指针:访问已释放的内存,危险!
p = nullptr; // 正确做法:立刻把 p 清空
把指针想象成“门禁卡”:delete 之后卡作废了,但你还攥着;再刷就可能出问题。最稳妥的是作废后直接扔掉 —— 赋成 nullptr。
int arr[] = {10, 20, 30};
int* p = arr; // p 指向 arr[0]
cout << *p; // 10
p++; // 不是地址加 1 字节,而是跳到下一个 int
cout << *p; // 20
指针 +1 会自动跳过“一个元素的大小”。int* 就跳 4 字节,double* 就跳 8 字节。这正是 arr[i] 和 *(arr + i) 等价的原因。
| 场景 | 选择 | 原因 |
|---|---|---|
| 函数想修改传入变量 | 引用 int& | 语法简单,不能为空 |
| 数据要活过函数结束 | 堆指针 new | 栈内存活不到函数外 |
| 可能是“没有对象” | 指针 nullptr | 引用不能为空 |
| 要遍历数组 | 指针或下标 | 两者等价,优先下标更安全 |
| 传大对象但不想改 | const 引用 | 不拷贝,也不误改 |
delete 之后指针还留着旧地址:再解引用是未定义行为。释放完立刻 p = nullptr;。new[] 只能配 delete[]:写成 delete arr 或者 free(arr) 都不对。delete 就是内存泄漏:程序不报错、不崩溃,只是悄悄占着内存不还,最难查。new 到堆上,或者干脆用 vector / string 托管。+1 不是地址加 1:它按指向类型的大小跳,别拿它当字节偏移用。new int(10) 创建的对象存放在哪里?return 一个局部变量的地址,会怎样?int* arr = new int[8]; 用完应该怎样释放?int* p = arr; 之后 p++ 表示什么?int* makeSquares(int n),在堆上创建长度 n 的数组,让 arr[i] = i * i,并返回数组地址。delete[] 并把指针置为 nullptr。
int* f() { int x = 5; return &x; }int main() { int* p = f(); cout << *p; }cout << &x; 看地址也行。new 和 delete 的形态对得上吗(new[] 对 delete[])。-fsanitize=address 跑一遍,它会直接指出哪一行访问了已释放的内存。new 出来的数据,活得久,但要你 delete。new 返回堆上的地址。new[] 配 delete[];delete 之后立刻 p = nullptr。+1 按元素大小移动,所以 arr[i] 就是 *(arr + i)。new 出来的指针成员呢?IntArray b = a; 会复制指针还是复制数据?析构时会不会删两次?这一课讲的就是深拷贝、浅拷贝,和著名的“三法则”。