本章目标:指针概念、new/delete、引用与指针的区别、 智能指针(unique_ptr / shared_ptr)、RAII。 现代 C++ 的核心思想:**永远别手写 new/delete**。
int x = 10;
int* p = &x; // & 取 x 的地址,p 指向 x
*p = 20; // * 解引用:通过 p 修改 x
std::println("{}", x); // 20
形象理解:变量是"装了 10 的盒子",&x 是盒子的门牌号, p 是一个写着门牌号的小纸条,*p 是"按照纸条找到盒子"。
空指针:不指向任何东西
int* p = nullptr; // 现代写法(C++11 起,替代 NULL)
if (p != nullptr) { /* 安全使用 */ }
// 解引用空指针 = 直接崩溃(segfault)
1) 函数间共享大对象(但引用已经能搞定,见 5.3) 2) 动态分配:运行时长出来的对象(需要 new,见 8.3) 3) 多态:指向父类的指针实际操作子类对象(第九章) 4) 与 C 库/系统接口对接(旧 API 全是裸指针)
现代 C++ 的结论:业务代码里 90% 的指针应该用引用或 智能指针替代。裸指针(*)只出现在"观察别人数据"的场合。
int* p = new int(42); // 堆上分配一个 int,值 42
delete p; // 释放!忘了 = 内存泄漏
p = nullptr; // 顺手置空,防止悬空
int* arr = new int[10]; // 堆上分配数组
delete[] arr; // 数组用 delete[],别搞混
三大经典 bug(这就是手写 new 的下场):
C++ 官方 1994 年就想消灭这些,于是发明了……
#include <memory>
// unique_ptr:独占所有权(最常用)
std::unique_ptr<int> u = std::make_unique<int>(42);
// 用 *u 访问。u 离开作用域时,自动 delete!
// shared_ptr:共享所有权(引用计数)
std::shared_ptr<int> s1 = std::make_shared<int>(10);
auto s2 = s1; // 两个智能指针共享同一个 int
// 最后一个 shared_ptr 销毁时才真正 delete
// weak_ptr:旁观者,不增加引用计数(打破循环引用)
std::weak_ptr<int> w = s1;
if (auto sp = w.lock()) { // 安全地"借用"一下
std::println("{}", *sp);
}
人话总结:
为什么不直接传裸指针给 shared_ptr?因为 new 泄漏在 "构造失败/异常"时会漏,make_shared 一次搞定还更高效。
RAII = Resource Acquisition Is Initialization (资源获取即初始化)。翻译成人话:
"把资源(内存、文件、锁、连接)的获取放在对象构造时, 释放放在对象析构时。对象死了,资源自动还回去。"
{
std::ofstream f("a.txt"); // 构造 = 打开文件
f << "hello"; // 使用
} // 离开作用域,f 析构 = 自动关闭文件(即使有异常)
智能指针就是 RAII 的代表。你写的类也应该遵循 RAII: 构造函数拿资源,析构函数还资源。这样:
这是 C++ 区别于 C 的最伟大之处。记住这句话: **new 对应 delete,但 RAII 让你永远不用记得配对。**
| 引用 & | 指针 * | |
|---|---|---|
| 可空 | 不能(必须绑定) | 可以 nullptr |
| 可换绑 | 不能(终身绑定) | 可以改指向 |
| 解引用 | 自动 | 要 * |
| 用途 | 参数、返回值 | 可选对象、容器节点 |
铁律:能用引用就用引用;必须"没有东西"(可空)才用指针。 现代代码里裸指针只用来"指向但不拥有"(叫非拥有指针)。
int* p;
{
int local = 5;
p = &local;
} // local 已销毁
// 现在 *p 是"悬空"的——地址还在,但东西没了。
// 可能打印 5,可能乱码,可能崩溃——都是"未定义行为"。
预防:
int* const p:p 本身不能改(常指针)
练习题
(这题不会也要背答案:unique_ptr<vector<int>>)
(use_count() 可以看)。