指针是 C 的招牌,引用是 C++ 对指针的"安全升级"。两者都能间接访问变量,但语义大不相同:指针是"存地址的变量",引用是"变量的别名"。理解它们的区别,是写出正确 C++ 的分水岭。
1. 引用是什么
引用必须在声明时初始化,之后永远绑定同一个对象,不能"改绑"。它本质上是编译器帮你隐藏了取址和解引用:
int x = 10;
int& r = x; // r 是 x 的别名
r = 20; // x 变成 20
int y = 99;
r = y; // 注意:这是把 y 的值赋给 x,不是让 r 改绑 y!
2. 引用 vs 指针:一张表看懂
| 对比项 | 引用 | 指针 |
|---|---|---|
| 初始化 | 必须立即绑定对象 | 可以不初始化(野指针危险) |
| 改绑 | 不允许 | 可指向别处 |
| 空值 | 不存在"空引用" | 可为 nullptr |
| 运算 | 无指针算术 | 支持 ++、+n 等 |
| 解引用 | 直接当变量用 | 需 *p 或 -> |
| 适用 | 函数参数/返回值、遍历 | 动态内存、容器、可选对象 |
3. const 引用:只读 + 省拷贝
传大对象时,值传递会拷贝整个对象;传引用只传地址。加 const 承诺不修改,还能接受临时量:
void print(const string& s) {
// 只读访问,不拷贝,s 很大也不怕
cout << s << endl;
}
print("hello"); // 临时字符串也能绑定 const 引用
口诀:参数优先 const 引用;需要修改才用普通引用;内置小类型(如 int)直接值传递即可。
4. 引用作返回值
返回引用可以避免拷贝,也能实现链式调用(cout << a << b 就是这么干的)。但绝不能返回局部变量的引用——函数结束局部变量就销毁了:
int& bad() {
int local = 42;
return local; // 悬垂引用!UB
}
vector<int> v = {1, 2, 3};
int& good = v[0]; // 引用容器里的元素,合法
good = 100; // v[0] 变成 100
5. 指针仍不可替代的场景
- 动态分配:
new出来的对象必须用指针(或智能指针)持有。 - 可空语义:对象可能"不存在"时,
nullptr是明确的哨兵。 - 指针数组 / 指针算术:遍历 C 数组、实现数据结构时。
- 多态:通过基类指针调用虚函数(引用也可以,但指针更常见)。
6. 现代 C++ 的实践建议
void process(Widget& w); // 必须修改 → 引用
void dump(const Widget& w); // 只读 → const 引用
Widget* find(int id); // 可能找不到 → 返回指针(nullptr)
unique_ptr<Widget> make(); // 拥有所有权 → 智能指针
Widget w; Widget* p = &w; // 需要数组/多态 → 原始指针
💡 一句话记忆:"能用引用就别用指针,能用 const 引用就别用普通引用"。把"传参用引用、拥有用智能指针、可选用裸指针"当成默认习惯,你的代码会安全一大截。