11. 函数指针

函数指针是指向函数的指针。通过函数指针也可以调用它指向的函数。函数指针是 C 语言实现 C++ 中多态的基础。

通过 C 语言编译器编译后的函数会成为 CPU 能够识别的指令集,这些指令集在程序时也会保存在内存的一段连续的地址空间中,这段地址空间通常是代码段中,且大多数系统为了安全起见会让代码段只读(不可修改)。

经过编译器编译后,函数名其实最终会变成代码段中某段代码的起始地址,这个地址通常是常量,不可改变。

在 C 语言中可以定义一个指针,指向代码段中的某个函数的起始地址,然后通过这个指针来调用它指向的函数。这种做法在 Linux 内核和驱动程序中非常常见。

在 C 语言中,每个函数也有自己的类型,如函数 int fx(char c, double d) {return 0;} 的类型为 int(char, double) ,指向这种类型函数的指针类型为 int(*)(char, double)

那么如何来声明一个 指向函数的指针呢?

函数指针的定义语法:

返回类型 (*指针名)(形参类型1, 形参类型2, ...) = 初始值;

说明:

  1. 指针名必须是标识符。
  2. 函数如果没有返回类型需要写成 void 类型
  3. (形参类型1, 形参类型2, ...) 为形参类型,如果没有形参,则可以为 () 或写成 (void)
  4. 指针如果不赋值初始值,则 = 初始值 可以省略不写。
  5. = 初始值 是为指针赋初始值,初始值必须是同类型的指针,或者为 NULL

那么如何为指针赋值呢?接下来我们说一下函数名的返回值和函数名取地址运算符(&)。

在 C 语言中,函数名作为表达式时,它的返回值一个函数指针。对函数名取地址的表达式的返回值也同样是一个函数指针。如函数 void fy(double a, double b){} 的函数名 fy 的返回值是函数的起始地址,返回类型是 void(*)(double, double)。对 fy 取地址 &fy 和 fy 取值是一样的,即返回值是函数的起始地址,返回类型同样是 void(*)(double, double)

使用函数指针调用函数的语法如下:

函数指针(实际调用参数1, 实际调用参数1, ... )
// 或
(*函数指针)(实际调用参数1, 实际调用参数1, ... )

上述直接使用指针调用函数指针解引用后调用函数的写法调用结果是一样的。且函数调用同样是一个表达式。

示例:

定义一个函数指针,分别指向不同的两个函数,然后使用函数指针调用这两个函数。

// filename: function_pointer.c
#include <stdio.h>

int myadd(int x, int y) {
    printf("%d + %d = %d\n", x, y, x + y);
    return x + y;
}

int mymul(int x, int y) {
    printf("%d * %d = %d\n", x, y, x * y);
    return x * y;
}

int main(int argc, char * argv[]) {
    int value;
    // 声明一个指向 int(int, int) 类型函数的函数指针 pfun
    int (*pfun)(int, int) = NULL;  // 初始值为零值

    pfun = myadd;  // 让 pfun 指向 myadd  函数
    pfun(1, 2);  // 使用指针调用 myadd 函数
    (*pfun)(3, 4);  // 使用指针解引用后调用 myadd 函数

    pfun = mymul;
    pfun(5, 6);
    (*pfun)(7, 8);

    pfun = &myadd;  // 函数取地址也同样是函数的地址。
    value = pfun(9, 10);  // 将函数调用的返回值赋值给 value
    printf("pfun(9, 10) 的返回值是 %d\n", value);

    return 0;
}

运行结果如下:

weimingze@mzstudio:~$ gcc -o function_pointer function_pointer.c
weimingze@mzstudio:~$ ./function_pointer
1 + 2 = 3
3 + 4 = 7
5 * 6 = 30
7 * 8 = 56
9 + 10 = 19
pfun(9, 10) 的返回值是 19

实验:

尝试定义一个函数指针的数组,数组内含有三个函数指针。让这 3 个指针指向相同参数和返回值的三个不同的函数。然后使用循环遍历这个数组中的指针,然后用指针调用不同的函数。

提示:

函数指针类型的数组的定义语法如下:

返回类型 (*数组名[元素个数])(形参类型1, 形参类型2, ...) = {初始值1, 初始值2, ...};