🔷 第七章 数组、指针和引用
📄 一句话概括

函数也有地址——函数名本身就是它的地址(和数组名一样,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 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。

两处亮点:

30 秒实验:把 int (*fp)(int, int) = add; 改成 int *fp(int, int) = add;,重新编译——报错(第二个写法是"函数声明",不是变量声明)。亲手试一次,就永远不会忘记那对括号。

六、常见误区

  1. "函数指针的括号可以省"——不能。int (*fp)(int)(函数指针)和 int *fp(int)(返回指针的函数)完全不同。
  2. "必须写 &add 才能取函数地址"——add 和 &add 都可以。函数名本身就"退化成"函数地址。
  3. "fp 和 *fp 调用不一样"——一样。fp(3,4) 和 (*fp)(3,4) 等价。
  4. "函数指针很少用,是屠龙之技"——标准库排序、事件驱动、状态机、插件机制,全都靠它。它是"把代码当数据"的第一块基石,后面学 std::function、lambda(更现代的写法)都以它为前提。
  5. "函数指针可以指向参数表不同的函数"——必须完全一致(返回类型 + 每个参数的类型)。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)`)。

相关笔记