📄 一句话概括
指针和函数一结合,能干三件第六章干不了的事:传递地址(让函数能改调用方的原件,swap 终于换得动了)、函数指针(把"函数"本身当成数据传来传去)、返回指针(把地址当结果交回来)。
📝 本节和前面几节的关系
第六章 6.3.1 留了一个让人憋屈的伏笔:swap 怎么都换不动,结论是"想改原件,要等指针或引用"。这一节就是来收这个账的——7.6.1 会亲手把 swap 写对。另外,7.4.2 学的"*p 是左值,能改原件"是本节的机制基础;7.4.4 的"空指针判空"会在 7.6.3 变成"函数返回失败"的表示法。
一、三种玩法一览
| 玩法 | 小节 | 解决的问题 | 一句话 |
|---|---|---|---|
| 传递地址 | 7.6.1 | 让函数改原件 | 把门牌号交出去,对方才能进你家改东西 |
| 指向函数的指针 | 7.6.2 | 把"要执行的代码"当参数传 | 函数也有地址,也能被指针指着 |
| 从函数中返回指针 | 7.6.3 | 把地址当结果交回 | 能返回,但不能返回"已经消失的东西"的地址 |
二、先回到那个憋屈的 swap
6.3.1 里,这个函数换不动两个数:
void swapWrong(int a, int b) { // ❌ 传值:换的是复印件
int t = a; a = b; b = t;
}
原因回顾(6.2.2):调用时实参的值被复制给形参,函数里操作的是副本,函数一结束副本就销毁——原变量一个字节都没动。
这一节的解法:别给值,给地址。
void swapRight(int *a, int *b) { // ✅ 传地址:拿着门牌号去改原件
int t = *a; *a = *b; *b = t; // *a 就是原件本身(7.4.2)
}
调用时也要变:
swapRight(&x, &y); // 注意这个 & —— 把地址递进去
两处变化:函数定义里形参从 int 变成 int *(并在体内用 * 操作);调用时多了一个 &(把地址取出来递过去)。漏掉任何一处,都改不动。
三、动手 Demo:亲眼看出差别
#include <iostream>
using namespace std;
void swapWrong(int a, int b) { // 传值
int t = a; a = b; b = t;
}
void swapRight(int *a, int *b) { // 传地址
int t = *a; *a = *b; *b = t;
}
int main() {
int x = 1, y = 2;
swapWrong(x, y);
cout << "传值后: x = " << x << ", y = " << y << endl; // 1, 2(没变)
swapRight(&x, &y);
cout << "传地址后:x = " << x << ", y = " << y << endl; // 2, 1(变了!)
return 0;
}
看到什么算成功:传值后: x = 1, y = 2、传地址后:x = 2, y = 1。
同一件事,两种结果——差别只在"递进去的是复印件还是门牌号"。这个对比值得记一辈子:以后凡是"函数里改了外面的变量不生效",第一反应就该是"我是不是传了值?"
什么时候该传地址?只有一种情况:你确实想改调用方的变量。如果只是"读一读",传值更安全(天然隔离,回看 6.3.1 的好的一面)。
四、常见误区
- "函数传参默认就能改外面的变量"——不能。默认是传值(传副本)。想改原件必须传地址。
- "传地址很危险,能不用就不用"——它是必要的工具(
swap、改数组、改大对象)。危险的不是它,而是指针不判空、乱指(7.4.4)。 - "传值和传地址性能差很多"——对小类型(int、double)差别可以忽略;对大对象(大数组、大结构体)传地址能省掉整份拷贝,这时差别才明显。
自测三问(点开对答案)
1. 想让函数改调用方的变量,应该怎么传?——**传地址**:形参写指针,调用时加 `&`。 2. `swapWrong(x, y)` 和 `swapRight(&x, &y)` 结果为什么不同?——前者传的是副本(改不到原件),后者传的是地址(能改原件)。 3. 什么时候不必要传地址?——只想"读"参数、不想改的时候;传值更安全。- ☐ 7.6.1 传递地址(下一节)
相关笔记
- 传值调用 —— 伏笔的起点
- 指针运算符和取地址运算符 ——
*p是左值,能改原件 - 传递地址 —— 下一节:把 swap 写对
- 🔷 C++基础 · 索引 —— 全课路线图