📄 一句话概括
函数也有地址——函数名本身就是它的地址(和数组名一样,7.5)。所以可以声明一个指向函数的指针:int (*fp)(int, int) = add;,之后用 fp(3, 4) 就能调用它,效果和 add(3, 4) 完全一样。它的价值是——能把"要执行的代码"当成一个参数传来传去(回调)。
📝 本节和前面几节的关系
7.6.1 让"变量的地址"进了函数;这一节让"函数的地址"也变成一种可以传来传去的数据。它用的零件全是旧的:指针声明(7.4.1)、括号改变优先级(3.2)、返回类型与参数表(6.1.1)。注意那个括号——少了它就变成 7.6.3 的"返回指针的函数",是两回事。
一、函数名就是地址
int add(int a, int b) { return a + b; }
cout << add << endl; // 0x4015a0 ← 函数在代码区里也有地址
和数组名一样:add 这个名字代表函数的入口地址,所以可以直接赋给函数指针。
二、声明一个函数指针
int (*fp)(int, int); // fp 是一个指针,指向"收两个 int、返回 int"的函数
fp = add; // 让它指向 add(也可以写 = &add,两种都行)
逐块读法(从里往外):
| 片段 | 含义 |
|---|---|
fp |
指针的名字 |
*fp |
它是个指针 |
(*fp)(int, int) |
它指向一个函数,这个函数收两个 int |
int (*fp)(int, int) |
那个函数返回 int |
那对括号不能省(重点)
int (*fp)(int, int); // ① 函数指针:fp 指向一个函数
int *fp(int, int); // ② 函数声明:fp 是个函数,它返回 int*
一字之差,天壤之别。原因是 3.2 讲的优先级:() 的优先级高于 *。
- ② 里
fp(int, int)先结合,说明 fp 是函数,返回int*; - ① 里
(*fp)用括号强行让*先结合,说明 fp 是指针。
记忆法:看 * 挨着的是名字还是括号——(*fp)(...) 先"锁住"指针身份。
三、通过指针调用函数
int add(int a, int b) { return a + b; }
int (*fp)(int, int) = add;
cout << add(3, 4) << endl; // 7
cout << fp(3, 4) << endl; // 7 ← 像普通函数一样用
cout << (*fp)(3, 4) << endl; // 7 ← 也等价("解引用再调用")
两种调用写法等价:fp(3, 4) 和 (*fp)(3, 4) 都合法。前者更常见、更清爽。
四、它到底有什么用
| 用途 | 说明 | 生活比喻 |
|---|---|---|
| 回调 | 把"成功后要执行什么"当参数传进去 | 你留个电话,事情办完打给你 |
| 策略/规则 | 同一个排序函数,可以换不同的比较规则 | 同一台秤,可以按重量排也可以按价格排 |
| 函数表 | 一张表里存一排函数指针,按下标调用 | 遥控器:按 1 换台、按 2 调音量 |
| 避免长 switch | 用下标直接跳到对应函数 | 查目录,不用一页页翻 |
核心价值一句话:让"一段代码"变成可以传递、可以选择的"数据"。
五、动手 Demo:用函数指针选运算
#include <iostream>
using namespace std;
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
// 参数里收一个"函数指针"——这就是回调
int calc(int x, int y, int (*op)(int, int)) {
return op(x, y);
}
int main() {
int (*fp)(int, int) = add; // 指向 add
cout << "fp(3, 4) = " << fp(3, 4) << endl; // 7
cout << "(*fp)(3, 4) = " << (*fp)(3, 4) << endl; // 7
fp = mul; // 改指 mul
cout << "改指 mul 后 = " << fp(3, 4) << endl; // 12
cout << "---- 当参数传进去(回调)----" << endl;
cout << "3 + 4 = " << calc(3, 4, add) << endl; // 7
cout << "3 - 4 = " << calc(3, 4, sub) << endl; // -1
cout << "3 * 4 = " << calc(3, 4, mul) << endl; // 12
cout << "---- 函数指针数组(函数表)----" << endl;
int (*table[3])(int, int) = {add, sub, mul};
for (int i = 0; i < 3; i++)
cout << "table[" << i << "](6, 2) = " << table[i](6, 2) << endl; // 8 4 12
return 0;
}
看到什么算成功:7、7、12;然后是 7、-1、12;最后一行循环打印 8、4、12。
两处亮点:
calc(3, 4, add)——函数名add直接当参数传进去。"要算什么"成了参数,这就是回调的全部含义。int (*table[3])(int, int)——一个放着 3 个函数指针的数组(7.7 的"指针数组"在这里派上用场),用下标就能选函数,比一长串 if-else 干净得多。
30 秒实验:把 int (*fp)(int, int) = add; 改成 int *fp(int, int) = add;,重新编译——报错(第二个写法是"函数声明",不是变量声明)。亲手试一次,就永远不会忘记那对括号。
六、常见误区
- "函数指针的括号可以省"——不能。
int (*fp)(int)(函数指针)和int *fp(int)(返回指针的函数)完全不同。 - "必须写
&add才能取函数地址"——add和&add都可以。函数名本身就"退化成"函数地址。 - "
fp和*fp调用不一样"——一样。fp(3,4)和(*fp)(3,4)等价。 - "函数指针很少用,是屠龙之技"——标准库排序、事件驱动、状态机、插件机制,全都靠它。它是"把代码当数据"的第一块基石,后面学
std::function、lambda(更现代的写法)都以它为前提。 - "函数指针可以指向参数表不同的函数"——必须完全一致(返回类型 + 每个参数的类型)。
int (*fp)(int,int)不能指向double f(double)。
自测三问(点开对答案)
1. `int (*fp)(int, int);` 里的 `fp` 是什么?——一个**函数指针**,指向"收两个 int、返回 int"的函数。 2. `int *fp(int, int);` 和上一行有什么不同?——这是**函数声明**:`fp` 是个函数,返回 `int*`。 3. 怎么把函数当参数传给另一个函数?——形参写成函数指针(如 `int (*op)(int,int)`),调用时把函数名传进去(如 `calc(3,4,add)`)。- ☐ 7.6.3 从函数中返回指针(下一节)
相关笔记
- 传递地址 —— 地址可以进函数
- 从函数中返回指针 —— 一字之差的另一个写法
- 指针数组 —— 函数表用得到
- 函数的定义 —— 返回类型与参数表
- 🔷 C++基础 · 索引 —— 全课路线图