两个符号,一对搭档:& 是"取地址"(问门牌号),* 是"解引用"(照着地址上门取值)。int *p = &a; 让 p 记住了 a 的地址;*p 就回到 a 那户人家。最关键的一点:*p 是左值——既能读也能写,写 *p = 99; 就等于写 a = 99;。这就是"通过指针改原件"的完整原理,也是 6.3.1 那个换不动的 swap 的解药。
7.4.1 学会了"声明指针、填上门牌号"。这一节学"照着门牌号办事"——是本节把指针从"名词"变成了"动词"。它直接回收两笔老账:3.4 讲过的"左值 = 有地址、能赋值"(*p 正是一个左值),以及 6.3.1 埋下的"swap 换不动"(因为传的是复印件;有了 *p 就能改原件了)。
一、两个运算符,各有各的活
| 运算符 | 名字 | 干什么 | 例子 | 读作 |
|---|---|---|---|---|
& |
取地址运算符 | 问一个变量"你住哪儿" | &a |
"a 的地址" |
* |
指针运算符(解引用) | 顺着地址找上门,取出那户人家的东西 | *p |
"p 指向的那个值" |
它们是一对反操作:
int a = 10;
int *p = &a; // p = a 的地址
cout << *p; // 10 ← 又回到了 a 的值
*&a 就等于 a:先取地址、再解引用,绕了一圈回到原地。
二、* 的三重身份(别混)
同一个字符 *,在不同位置干三件完全不同的事:
int x = 3 * 4; // ① 乘法运算符:3 乘 4
int a = 10;
int *p = &a; // ② 声明里的星号:p 是"指向 int 的指针"
cout << *p << endl; // ③ 解引用:取出 p 指向的值(10)
| 出现位置 | 身份 | 怎么认 |
|---|---|---|
两个值中间:3 * 4 |
乘法 | 左右都是"值" |
类型后面、名字前面:int *p |
指针类型标记 | 出现在声明语句里 |
指针变量前面:*p |
解引用 | 后面跟的是"已经声明过的指针" |
认的技巧:看 * 后面跟的是"新名字"还是"已存在的变量"。跟在 int 后面引入新名字 → 是声明;跟在一个现成的指针变量前面 → 是解引用。
三、*p 是左值:能读、也能写
这是本节最值钱的一条。3.4 说过:左值 = 有明确地址的盒子,能出现在 = 左边。*p 指的就是 a 那块内存,当然有地址,所以它是左值:
int a = 10;
int *p = &a;
cout << *p; // 读:10
*p = 99; // 写!等价于 a = 99;
cout << a; // 99 ← a 真的被改了
注意这里有多反直觉:明明写的是 *p = 99;,改的却是 a。因为 *p 和 a 指的是同一块内存——两张指路牌,通到同一个房间。
p ──────┐
▼
┌──────┐
a ───► │ 99 │ `a` 和 `*p` 是同一扇门的两个叫法
└──────┘
这条就是"改原件"的完整机制:把地址交出去,对方就能用 * 改你家东西。6.3.1 的 swap 之所以失败,是因为传的是"复印件";只要传地址,就能换动——7.6.1 会亲手把这个 swap 写对。
四、实例06:输出指针对应的数值
#include <iostream>
using namespace std;
int main() {
int a = 10;
int *p = &a;
cout << "a = " << a << endl; // 10
cout << "*p = " << *p << endl; // 10 ← 和 a 一样
*p = 99; // 通过指针改原件
cout << "改完之后:" << endl;
cout << "a = " << a << endl; // 99
cout << "*p = " << *p << endl; // 99
a = 7; // 反过来直接改 a
cout << "*p = " << *p << endl; // 7 ← 指针立刻"看到"新值
return 0;
}
看到什么算成功:10、10、99、99、7。
这五行输出把"同一块内存"钉死了:
| 操作 | 结果 | 说明 |
|---|---|---|
读 a / 读 *p |
都是 10 | 两条路通到同一个房间 |
写 *p = 99 |
a 变 99 |
通过指针改原件 |
写 a = 7 |
*p 变 7 |
反过来也成立 |
30 秒实验:在 int *p = &a; 之前加一句 int b = 10;,再打印 &a 和 &b——两个地址不一样,说明"值相同 ≠ 同一户人家"。这正是 6.3.1 里 swap 换不动的原因:函数里拿到的是新盒子(副本),不是原盒子的地址。
五、常见误区
- "
*p = 99;是给 p 赋值"——不是。改的是 p 指向的那个变量。p = 某个地址;才是改 p 自己。 - "
*p和p可以随便换着写"——差得远。p是地址,*p是地址里的值。混用最典型的后果:cout << p打印一串0x...,看着像乱码,其实是地址。 - "把
&和&&搞混"——&a是取地址(单个 &),a && b是 3.1.3 的逻辑与。虽然写法像,语义完全无关;还有按位与a & b也是单 &,靠前后是不是变量名来分辨。 - "指针不初始化也能解引用"——
int *p; cout << *p;会去读一块随机内存,可能直接崩溃,也可能读到垃圾值。这就是"野指针",7.4.4 给治疗方案。 - "
*p只能读不能写"——能写。它是左值(3.4)。这是指针最有价值的特性,也是 7.6.1 / 7.8.2 两种"改原件"手段的基础。
自测三问(点开对答案)
1. `&` 和 `*` 各干什么?——`&` 取地址;`*` 解引用(顺着地址取值)。 2. `int a = 5; int *p = &a; *p = 8;` 之后 `a` 是几?——`8`。`*p` 和 `a` 是同一块内存。 3. `int *p = &a;` 和 `*p = 99;` 里的两个 `*` 是同一种用法吗?——**不是**。前者是"声明里的指针类型标记",后者是"解引用"。- ☐ 7.4.3 指针运算(下一节)
相关笔记
- 变量与指针 —— 指针怎么声明
- 判断左值与右值 ——
*p为什么是左值 - 传值调用 ——
swap换不动的伏笔 - 传递地址 —— 用本节原理写对
swap - 🔷 C++基础 · 索引 —— 全课路线图