8.5 指向函数的指针

 

8.5 指向函数的指针

8.5.1 什么是函数的指针

在程序中定义一个函数时,编译器会将该函数的源代码转换为可执行代码,并分配一段存储空间。这段内存空间有一个起始地址,也称为函数的入口地址。每次调用函数时,都从该地址入口开始执行这段函数代码。函数名代表函数的起始地址,调用函数时,从函数名得到函数的起始地址,并执行函数代码。因此,函数名就是函数的指针,它代表函数的起始地址。

我们可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。例如:

int (*p)(int, int);

定义 p 是一个指向函数的指针变量,它可以指向函数类型为整型且有两个整型参数的函数。此时,指针变量 p 的类型用 int (*)(int, int) 表示。

8.5.2 用函数指针变量调用函数

如果想调用一个函数,除了可以通过函数名调用以外,还可以通过指向函数的指针变量来调用该函数。

先通过一个简单的例子来回顾一下函数的调用情况。

例 8.22 用函数求整数 ab 中的大者。

解题思路:定义一个函数 max,实现求两个整数中的大者。在主函数中调用 max 函数,除了可以通过函数名调用外,还可以通过指向函数的指针变量来实现。分别编程并作比较。

(1) 通过函数名调用函数

程序代码

#include <stdio.h>

int max(int, int); // 函数声明

int main() {
    int a, b, c;
    printf("Please enter a and b: ");
    scanf("%d %d", &a, &b);
    c = max(a, b); // 通过函数名调用 max 函数
    printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
    return 0;
}

int max(int x, int y) {
    int z;
    if (x > y) z = x;
    else z = y;
    return z;
}

运行结果

Please enter a and b: 45 87
a = 45
b = 87
max = 87

这个程序是很容易理解的。

(2) 通过指针变量调用它所指向的函数

将程序改写为:

程序代码

#include <stdio.h>

int max(int, int); // 函数声明

int main() {
    int (*p)(int, int); // 定义指向函数的指针变量 p
    int a, b, c;
    p = max; // 使 p 指向 max 函数
    printf("Please enter a and b: ");
    scanf("%d %d", &a, &b);
    c = (*p)(a, b); // 通过指针变量调用 max 函数
    printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
    return 0;
}

int max(int x, int y) {
    int z;
    if (x > y) z = x;
    else z = y;
    return z;
}

运行结果与程序 (1) 相同。

程序分析: 可以看到,程序 (1) 和 (2) 的 max 函数是相同的。不同的只是在 main 函数中调用 max 函数的方法。

程序 (2) 的第 4 行 int (*p)(int, int); 用来定义 p 是一个指向函数的指针变量,最前面的 int 表示这个函数的返回值是整型的。最后面的括号中有两个 int,表示这个函数有两个 int 型参数。注意 *p 两侧的括号不可省略,表示 p 先与 * 结合,是指针变量,然后再与后面的 () 结合,() 表示是函数,即该指针变量不是指向一般的变量,而是指向函数。如果写成 int *p(int, int);,由于 () 优先级高于 *,相当于 int *(p(int, int));,就成了声明一个返回值为指向整型变量的指针的 p 函数。

赋值语句 p = max 的作用是将函数 max 的入口地址赋给指针变量 p。和数组名代表数组首元素地址类似,函数名代表该函数的入口地址。这样,p 就是指向函数 max 的指针变量,此时 pmax 都指向函数的开头。调用 *p 就是调用 max 函数。请注意,p 是指向函数的指针变量,它只能指向函数的入口处而不可能指向函数中间的某一条指令处,因此不能用 *(p + 1) 来表示函数的下一条指令。

main 函数中有一个赋值语句:

c = (*p)(a, b);

它和

c = max(a, b);

等价。这就是用指针实现函数的调用。

以上用两种方法实现函数的调用,结果是一样的。

 

 

8.5.3 怎样定义和使用指向函数的指针变量

从例 8.22 中已经看到定义指向函数的指针变量的例子。定义指向函数的指针变量的一般形式为:

类型名 (*指针变量名)(函数参数表列)
判定指针变量是指向函数的指针变量
  1. 看变量名的前面是否有 * 号,如 *p。如果有,肯定是指针变量而不是普通变量。
  2. 看变量名的后面是否有圆括号,内有形参的类型。如果有,就是指向函数的指针变量,这对圆括号是函数的特征。由于优先级的关系,*指针变量名 要用圆括号括起来。
说明:
  1. 定义指向函数的指针变量,并不意味着这个指针变量可以指向任何函数,它只能指向在定义时指定类型的函数。如 int (*p)(int, int); 表示指针变量 p 可以指向返回值为整型且有两个整型参数的函数。在程序中可以将符合该类型的函数的地址赋给它,它就指向那个函数。在一个程序中,一个指针变量可以先后指向同类型的不同函数。
  2. 如果要用指针调用函数,必须先使指针变量指向该函数。如:
    p = max;
    
    这将 max 函数的入口地址赋给指针变量 p
  3. 在给函数指针变量赋值时,只需给出函数名而不必给出参数。例如:
    p = max(a, b);
    
    因为是将函数入口地址赋给 p,而不涉及实参与形参的结合问题。如果写成:
    c = (*p)(a, b);
    
    就错了。p = max(a, b) 的作用是将调用 max 函数所得到的函数值赋给 p,而不是将函数入口地址赋给 p
  4. 用函数指针变量调用函数时,只需将 (*p) 代替函数名即可。在 (*p) 之后的括号中根据需要写上实参。例如:
    c = (*p)(a, b);
    
    表示“调用由 p 指向的函数,实参为 ab。得到的函数值赋给 c”。
  5. 对指向函数的指针变量不能进行算术运算,如 p + np++p-- 等运算是无意义的。
  6. 用函数名调用函数,只能调用指定的一个函数,而通过指针变量调用函数比较灵活,可以根据不同情况先后调用不同的函数。见例 8.23。

例 8.23 输入两个整数,然后让用户选择 1 或 2。选 1 时调用 max 函数,输出二者中的大数;选 2 时调用 min 函数,输出二者中的小数。

解题思路:这是一个示意性的简单例子,说明怎样使用指向函数的指针变量。定义两个函数 maxmin,分别用来求大数和小数。在主函数中根据用户输入的数字是 1 或 2,使指针变量指向 max 函数或 min 函数。

程序代码

#include <stdio.h>

int max(int, int); // 函数声明
int min(int, int); // 函数声明

int main() {
    int (*p)(int, int); // 定义指向函数的指针变量
    int a, b, c, n;
    printf("Please enter a and b: ");
    scanf("%d %d", &a, &b);
    printf("Please choose 1 or 2: ");
    scanf("%d", &n);

    if (n == 1) p = max; // 如输入 1,使 p 指向 max 函数
    else if (n == 2) p = min; // 如输入 2,使 p 指向 min 函数

    c = (*p)(a, b); // 调用 p 指向的函数
    printf("a = %d, b = %d\n", a, b);
    if (n == 1) printf("max = %d\n", c);
    else printf("min = %d\n", c);

    return 0;
}

int max(int x, int y) {
    int z;
    if (x > y) z = x;
    else z = y;
    return z;
}

int min(int x, int y) {
    int z;
    if (x < y) z = x;
    else z = y;
    return z;
}

运行结果

  1. 输入 ab 的值为 34 和 89,选择模式 1:
    Please enter a and b: 34 89
    Please choose 1 or 2: 1
    a = 34, b = 89
    max = 89
    

  2. 输入 ab 的值为 34 和 89,选择模式 2:
    Please enter a and b: 34 89
    Please choose 1 or 2: 2
    a = 34, b = 89
    min = 34
    

程序分析: 在程序中,调用函数的语句是 c = (*p)(a, b);。从这个语句本身看不出是调用哪一个函数,在程序执行过程中由用户选择输入一个数字,程序根据输入的数字决定指针变量 p 指向哪一个函数,然后调用相应的函数。

这个例子虽然简单,但具有很大的实用价值。在许多应用程序中,常用菜单提示用户输入一个数字,然后根据输入的不同值调用不同的函数,实现不同的功能。通过使用指针变量,可以使程序更简洁和专业。 

 

 

8.5.4 用指向函数的指针作函数参数

指向函数的指针变量的一个重要用途是把函数的入口地址作为参数传递到其他函数。指向函数的指针可以作为函数参数,把函数的入口地址传递给形参,这样就能够在被调用的函数中使用实参函数。其原理如下:

假设有一个函数 fun,它有两个形参 x1x2,定义 x1x2 为指向函数的指针变量。在调用函数 fun 时,实参为两个函数名 f1f2,给形参传递的是函数 f1f2 的入口地址。这样在函数 fun 中就可以调用 f1f2 函数了。例如:

void fun(int (*x1)(int), int (*x2)(int, int)) {
    int a, b, i = 3, j = 5;
    a = (*x1)(i); // 调用 f1 函数,i 是实参
    b = (*x2)(i, j); // 调用 f2 函数,i 和 j 是实参
}

fun 函数中声明形参 x1x2 为指向函数的指针变量,x1 指向的函数有一个整型形参,x2 指向的函数有两个整型形参。ij 是调用 f1f2 函数时所要求的实参。函数 fun 的形参 x1x2(指针变量)在函数 fun 未被调用时并不占内存单元,也不指向任何函数。在主函数调用 fun 函数时,把实参函数 f1f2 的入口地址传给形参指针变量 x1x2,使 x1x2 指向函数 f1f2

为什么使用指向函数的指针变量

有人可能会问,既然在 fun 函数中要调用 f1f2 函数,为什么不直接调用 f1f2 而要用函数指针变量呢?的确,如果只是用到 f1f2 函数,可以在 fun 函数中直接调用 f1f2。但是,如果在每次调用 fun 函数时,要调用的函数不是固定的,这次调用 f1f2,下次要调用 f3f4,再下一次要调用 f5f6,用指针变量就比较方便了。只要在每次调用 fun 函数时给出不同的函数名作为实参即可,而 fun 函数不必做任何修改。这种方法符合结构化程序设计方法的原则,是程序设计中常使用的。

下面通过一个简单的例子来说明这种方法的应用。

例 8.24 有两个整数 ab,由用户输入 1、2 或 3。如输入 1,程序就给出 ab 中的大者;输入 2,就给出 ab 中的小者;输入 3,则求 ab 之和。

解题思路:与例 8.23 相似,但现在用一个函数 fun 来实现以上功能。

程序代码

#include <stdio.h>

int fun(int x, int y, int (*p)(int, int)); // fun 函数声明
int max(int, int); // max 函数声明
int min(int, int); // min 函数声明
int add(int, int); // add 函数声明

int main() {
    int a = 34, b = -21, n;
    printf("Please choose 1, 2 or 3: ");
    scanf("%d", &n); // 输入 1、2 或 3 之一

    if (n == 1) fun(a, b, max); // 输入 1 时调用 max 函数
    else if (n == 2) fun(a, b, min); // 输入 2 时调用 min 函数
    else if (n == 3) fun(a, b, add); // 输入 3 时调用 add 函数

    return 0;
}

int fun(int x, int y, int (*p)(int, int)) { // 定义 fun 函数
    int result;
    result = (*p)(x, y); // 调用 p 指向的函数
    printf("%d\n", result); // 输出结果
    return result;
}

int max(int x, int y) { // 定义 max 函数
    int z;
    if (x > y) z = x;
    else z = y;
    printf("max = ");
    return z; // 返回值是两数中的大者
}

int min(int x, int y) { // 定义 min 函数
    int z;
    if (x < y) z = x;
    else z = y;
    printf("min = ");
    return z; // 返回值是两数中的小者
}

int add(int x, int y) { // 定义 add 函数
    printf("sum = ");
    return x + y; // 返回值是两数之和
}

运行结果

  1. 选择 1,调用 max 函数:
    Please choose 1, 2 or 3: 1
    max = 34
    

  2. 选择 2,调用 min 函数:
    Please choose 1, 2 or 3: 2
    min = -21
    

  3. 选择 3,调用 add 函数:
    Please choose 1, 2 or 3: 3
    sum = 13
    

程序分析: 在定义 fun 函数时,在函数首部用 int (*p)(int, int) 声明形参 p 是指向函数的指针,该函数是整型函数,有两个整型形参。maxminadd 是已定义的 3 个函数,分别用来实现求大数、求小数和求和的功能。

当输入 1 时 (n = 1),调用 fun 函数,除了将 ab 作为实参,将两个整数传给 fun 函数的形参 xy 外,还将函数名 max 作为实参,将其入口地址传送给 fun 函数中的形参 pp 是指向函数的指针变量)。这时,fun 函数中的 (*p)(x, y) 相当于 max(x, y),调用 max(x, y) 就输出 ab 中的大者。

同理,当输入 2 时 (n = 2),调用 fun 函数时,以函数名 min 作实参,此时 fun 函数的形参 p 指向函数 min。调用 min(x, y) 就输出 ab 中的小者。

当输入 3 时 (n = 3),调用 fun 函数时,以函数名 add 作实参,fun 函数中的 (*p)(x, y) 相当于 add(x, y),调用 add(x, y) 就输出 ab 之和。

这个例子的思路与例 8.23 相似,但具体做法不同。在例 8.23 中定义了一个指向函数的指针变量 p,根据不同情况,使 p 指向不同的函数,然后通过该指针变量调用不同的函数。本例程序没有定义指针变量,而是根据不同情况,将不同的函数名作为调用 fun 函数的实参,把函数入口地址传送给 fun 函数中的形参(该形参是指向函数的指针变量),调用 fun 函数就分别执行不同的函数。

通过本例可以清楚地看到,不论调用 maxminaddfun 函数都没有改变,只是改变了实参函数名而已。在 fun 函数中输出 result,由于在不同情况下调用了不同的函数,因此 result 的值是不同的。这就增加了函数使用的灵活性。

可以编写一个通用的函数来实现各种专用的功能。需要注意的是,对作为实参的函数(如 maxminadd),应在主调函数中用函数原型作函数声明。例如

main 函数中的第 3 行到第 6 行的函数声明是不可少的。这样可以确保编译器知道这些函数的存在及其参数和返回值类型。

通过指向函数的指针变量,可以编写出更为复杂和通用的程序。例如,编写一个求定积分的通用函数,用它分别求以下 5 个函数的定积分:

  • ∫(1+𝑥) 𝑑𝑥∫(1+x)dx
  • ∫(2𝑥+3) 𝑑𝑥∫(2x+3)dx
  • ∫(𝑒𝑥+1) 𝑑𝑥∫(ex+1)dx
  • ∫(1+𝑥2) 𝑑𝑥∫(1+x2)dx
  • ∫(𝑥3) 𝑑𝑥∫(x3)dx

可以看出,每次需要求定积分的函数是不一样的。可以编写一个求定积分的通用函数 integral,它有 3 个形参:下限 a、上限 b 以及指向函数的指针变量 funintegral 函数的原型可写为:

float integral(float a, float b, float (*fun)(float));

然后分别定义 5 个函数 f1f2f3f4f5,代表上面 5 个函数(1 + x, 2x + 3, e^x + 1, 1 + x^2, x^3)。然后先后调用 integral 函数 5 次,每次调用时把 ab 以及一个函数名(f1f2f3f4f5 之一)作为实参,即把上限、下限以及有关函数的入口地址传送给形参 fun。分别执行 integral 函数,可以求出不同函数的定积分。

请读者根据以上思路,编写出完整的程序。这里给出一个简要的示例:

程序代码

#include <stdio.h>
#include <math.h>

float integral(float a, float b, float (*fun)(float));

float f1(float x) { return 1 + x; }
float f2(float x) { return 2 * x + 3; }
float f3(float x) { return exp(x) + 1; }
float f4(float x) { return 1 + x * x; }
float f5(float x) { return x * x * x; }

int main() {
    float a = 0, b = 1;

    printf("Integral of f1 from %.2f to %.2f: %.4f\n", a, b, integral(a, b, f1));
    printf("Integral of f2 from %.2f to %.2f: %.4f\n", a, b, integral(a, b, f2));
    printf("Integral of f3 from %.2f to %.2f: %.4f\n", a, b, integral(a, b, f3));
    printf("Integral of f4 from %.2f to %.2f: %.4f\n", a, b, integral(a, b, f4));
    printf("Integral of f5 from %.2f to %.2f: %.4f\n", a, b, integral(a, b, f5));

    return 0;
}

float integral(float a, float b, float (*fun)(float)) {
    int n = 1000; // 分割成 1000 个小区间
    float h = (b - a) / n;
    float sum = 0.5 * (fun(a) + fun(b));
    for (int i = 1; i < n; i++) {
        sum += fun(a + i * h);
    }
    return sum * h;
}

程序说明

  1. 定义了 integral 函数,用于求定积分。
  2. 定义了 5 个函数 f1f2f3f4f5,分别代表不同的函数。
  3. main 函数中,分别调用 integral 函数,求出不同函数的定积分,并打印结果。

运行结果

Integral of f1 from 0.00 to 1.00: 1.5000
Integral of f2 from 0.00 to 1.00: 4.0000
Integral of f3 from 0.00 to 1.00: 2.7183
Integral of f4 from 0.00 to 1.00: 1.3333
Integral of f5 from 0.00 to 1.00: 0.2500

通过这个示例,可以看出指向函数的指针作为函数参数的灵活性和实用性。它使得程序可以处理不同的函数,而不需要修改 integral 函数的实现。

总结

指向函数的指针作为函数参数,是 C 语言实际应用中的一个比较深入的部分。本节只作初步介绍,使读者对此有一定的了解,为以后进一步学习和应用打下基础。通过灵活使用指向函数的指针变量,可以编写出更加通用和简洁的程序,提高程序的复用性和可维护性。

 

 

 

 

 

 

 

 

 

 

  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏驰和徐策

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值