指针与引用:C++ 的灵魂难关
这是什么:指针 = 记着变量住哪间房的门牌号;引用 = 变量的另一个名字,改它就是改本人。
为什么用:前面一路顺利,现在到了 C++ 的"劝退名场面"——指针。别慌:上一章的函数默认只收复印件,想让它真正改动外面的变量,就得靠指针和引用搭把手。
如何用:先记住"内存像一排带门牌号的房间"这个比喻,用 & 和 * 写一次取地址、解引用,再动手写个 int& 函数改外部变量。
内存与地址:先建立直觉
电脑内存像一排带门牌号的房间,每个房间住着一个数据。int age = 18; 就是把 18 住进某个房间;而指针,存的就是门牌号——"18 住在 1024 号房"。
& 取地址,* 解引用
#include <iostream>
int main() {
int age = 18;
int* p = &age; // p 是指针:存着 age 的门牌号
std::cout << age << std::endl; // 房间里的住户
std::cout << &age << std::endl; // 房间的门牌号
std::cout << *p << std::endl; // 顺着门牌号进屋,找到住户
return 0;
}
你会看到(地址一串十六进制数,每台电脑、每次运行都不同)
18
0x7ffee3b4a9dc
18
| 符号 | 名字 | 作用 |
|---|---|---|
&age |
取地址 | 问出"age 住在哪个门牌号" |
int* p |
声明指针 | p 这个变量专门用来存地址 |
*p |
解引用 | 顺着门牌号进屋,拿到里面的数据 |
& 和 * 是一对互逆操作:*(&age) 绕一圈还是 age 本尊。
空指针 nullptr
指针可以不指向任何有效地址,这时用 nullptr(C++11 起标准写法,替代老旧的 NULL):
int* p = nullptr; // 空指针,不指向任何对象
if (p != nullptr) {
std::cout << *p; // 只有非空才解引用,避免崩溃
}
绝不要对 nullptr 解引用(*nullptr),程序会直接崩溃(段错误)。
引用:变量的"别名"
还记得上一章"传值改不动原件"吗?把参数写成 int& n(引用传递),函数拿到的就不是复印件,而是同一个变量的别名:
#include <iostream>
void addTen(int& n) { // n 是外面那个数的别名
n += 10; // 改 n 就是改本人
}
int main() {
int a = 1;
addTen(a);
std::cout << a << std::endl;
return 0;
}
你会看到
11
引用出生就必须绑定,终生不换人:
int a = 1, b = 2;
int& r = a; // r 是 a 的别名
r = 100; // 改 r 就是改 a,a 变成 100
// r = &b; // 错误:引用不能重新绑定到 b(这行甚至无法编译,只作示意)
指针 vs 引用对比
指针 int* p |
引用 int& r |
|
|---|---|---|
| 本质 | 存门牌号的变量 | 别名,出生就必须绑定且终生不换人 |
| 可以为空 | 可以(nullptr,空指针) |
不行 |
| 能否改指别人 | 能(p = &other;) |
不能 |
| 用法 | 要 *p 解引用 |
当普通变量直接用 |
| 初始化 | 声明后可先不赋值 | 声明时必须初始化 |
一句话:指针是写着门牌号的纸条,引用是给房间起的第二个名字。
什么时候才需要指针
好消息:现代 C++ 里,引用、标准库容器和智能指针能覆盖绝大多数场景。裸指针主要出现在:底层开发、手工实现数据结构(比如链表)、和 C 语言代码打交道。零基础阶段的目标只有一个——见到 int*、&x、*p 不心慌。等学到 STL 和数据结构,你会回来和它们真正交朋友。
❓ 常见问题
Q1:指针和引用选哪个?
能用引用就优先用引用(更安全、语法更简洁)。需要"可以为空""可以改指别人""需要动态内存管理"时,才上指针。
Q2:为什么要学指针?现代 C++ 不都用智能指针了吗?
理解裸指针是看懂智能指针、看懂底层数据结构、看懂 C 语言代码的前提。零基础阶段混个脸熟即可,深入后再系统学。
Q3:*p 和 `p` 有什么区别?**
*p 是解引用一次,拿到 p 所指的数据;**p 是解引用两次,p 本身是个"指向指针的指针"(二级指针)。入门阶段用不到二级指针,先理解一级就够了。
⚠️ 新手翻车现场
翻车一:对空指针解引用
int* p = nullptr;
std::cout << *p; // 程序直接崩溃(段错误)
问题:nullptr 不指向任何有效内存,顺着门牌号进屋,房间根本不存在。
修正:解引用前先判空:if (p != nullptr) { std::cout << *p; }
翻车二:引用没有初始化
int& r; // 错误:引用声明时必须初始化
问题:引用本质是别名,必须出生就绑定到某个变量,不能单独存在。
修正:int a = 1; int& r = a;
翻车三:把指针当普通变量用,忘了 *
int age = 18;
int* p = &age;
std::cout << p; // 打印的是门牌号(地址),不是 18
问题:想打印房间里的住户(18),得用 *p 解引用。
修正:std::cout << *p;
🎯 什么时候用指针/引用
| 场景 | 推荐 | 原因 |
|---|---|---|
| 函数要修改外部变量 | 引用 int& |
语法简单、不能为空、更安全 |
| 需要可空或可重新指向 | 指针 int* |
支持 nullptr,支持 p = &other |
| 动态内存(new/delete) | 智能指针(后续章节) | 避免手动内存管理带来的泄漏 |
| 零基础入门 | 混个脸熟即可 | 不要在第一关卡住 |
📝 本章小结
&age取地址(门牌号),*p解引用(进屋拿数据)- 指针
int* p存地址,可以为空(nullptr),可以改指别人 - 引用
int& r是别名,必须初始化,终生不换人 - 不要对
nullptr解引用,程序会崩溃 - 记忆口诀:& 问门牌,* 顺着找;引用是别名,改它就是改本人。
动手改
- 声明一个
double x = 9.5;,打印&x(注意它的类型是double*) - 写
void swap(int& a, int& b)交换两个数,在main里验证真的换了 - 把
swap的参数改回int a, int b,再跑一次——为什么换不动了?亲手体会传值 vs 传引用 - 写一个函数
void setZero(int* p),用指针把外面的一个整数设为 0,验证有效
下一章:类与对象,终于要"造东西"了 🐸