学而时习之:C语言中的函数指针

C 语言中的函数指针

在 C 语言里,函数指针是一种存放函数入口地址的指针变量,它可以把函数当作参数传递,也能在运行时动态调用。这一特性在回调函数、事件驱动程序以及"多态"(同一函数或操作在不同上下文中表现出不同行为)等场景中尤为实用。

示例代码:

c 复制代码
#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main() {
    // 声明一个与 add() 签名一致的函数指针
    int (*fptr)(int, int);

    // 将 add() 的地址赋给指针
    fptr = &add;

    // 通过指针调用函数并打印结果
    printf("%d", fptr(10, 5));
    return 0;
}
复制代码
输出  
15 

解释:程序先定义函数 add(),随后将其地址赋给函数指针 fptr,最后通过该指针调用函数并打印两个整数的和。

函数指针的声明

函数指针必须按照它所指向函数的签名来声明。通用语法如下:

c 复制代码
return_type (*pointer_name)(parameter_types);
  • return_type:函数返回值的类型
  • parameter_types:函数参数的类型列表
  • pointer_name:函数指针的名字

注意*pointer_name 必须放在括号里,否则编译器会把它解释为返回 return_type * 的普通函数声明,而不是指针。

函数的类型由"返回类型"以及"参数的个数和类型"共同决定,因此声明函数指针时,必须保证它的签名与后续要指向的函数完全一致。

例如上面的代码中,函数指针声明为:

c 复制代码
int (*fptr)(int, int);

就与随后指向的 add() 函数签名完全匹配。

初始化

函数指针通过"把函数地址赋给它"来完成初始化:

c 复制代码
pointer_name = &function_name;

也可以省略取地址符,因为函数名在表达式里本身就相当于一个常量函数指针:

c 复制代码
pointer_name = function_name;

必须保证被赋值的函数签名(返回类型、参数列表)与指针声明时完全一致,否则编译器会报类型不匹配错误。

函数指针的特性

由于函数指针指向的是代码段中的指令地址,而非数据,因此它和普通指针相比有一些限制。主要特性如下:

  • 指向代码段中函数的入口地址
  • 要求函数签名(返回类型 + 参数列表)完全匹配
  • 同一指针可先后指向多个签名一致的函数
  • 不能进行自增、自减等指针算术运算
  • 可用来构造"函数指针数组",实现跳转表(jump-table)等高级用法

应用与示例

下面通过代码示例展示函数指针的几种常见用法:

1. 作为参数传递(回调函数)

函数指针最有价值的用途之一,就是把函数当作参数传给另一个函数,从而在运行时决定具体调用哪个函数。

c 复制代码
#include <stdio.h>

/* 普通加法 */
int add(int a, int b) {
    return a + b;
}

/* 普通减法 */
int subtract(int a, int b) {
    return a - b;
}

/* calc 接收一个函数指针 op,
   用它对 a、b 做运算并打印结果 */
void calc(int a, int b, int (*op)(int, int)) {
    printf("%d\n", op(a, b));
}

int main() {
    calc(10, 5, add);      // 传加法
    calc(10, 5, subtract); // 传减法
    return 0;
}
复制代码
输出  
15  
5 

解释:calc 函数通过第三个参数(函数指针)动态选择要执行的操作。把 addsubtract 传进去,就能在运行时完成不同的计算。

用结构体"模拟"成员函数

C 语言允许在结构体里放数据,却不能直接写函数;但可以把函数指针当作"成员",达到类似类成员函数的效果。

c 复制代码
#include <stdio.h>

/* 结构体里放函数指针,当成"成员函数" */
typedef struct Rect {
    int w, h;
    void (*set)(struct Rect*, int, int);
    int  (*area)(struct Rect*);
    void (*show)(struct Rect*);
} Rect;

/* 计算面积 */
int area(Rect* r) {
    return r->w * r->h;
}

/* 打印尺寸 */
void show(Rect* r) {
    printf("Rectangle's Width: %d, Height: %d\n", r->w, r->h);
}

/* 设置宽高 */
void set(Rect* r, int w, int h) {
    r->w = w;
    r->h = h;
}

/* "构造函数":把函数指针装进去 */
void constructRect(Rect* r) {
    r->w = 0;
    r->h = 0;
    r->set  = set;
    r->area = area;
    r->show = show;
}

int main() {
    Rect r;
    constructRect(&r);  // 初始化

    r.set(&r, 10, 5);   // 调用"成员函数"
    r.show(&r);
    printf("Rectangle Area: %d\n", r.area(&r));
    return 0;
}
yaml 复制代码
输出  
Rectangle's Width: 10, Height: 5  
Rectangle Area: 50

函数指针数组

你还可以把函数指针放进数组里,实现一组函数的动态调用。

c 复制代码
#include <stdio.h>

/* 四个运算函数 */
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 divd(int a, int b) { return b ? a / b : -1; }

int main() {
    /* 声明并初始化函数指针数组 */
    int (*farr[])(int, int) = {add, sub, mul, divd};
    int x = 10, y = 5;

    /* 通过数组下标动态调用不同函数 */
    printf("Sum: %d\n",        farr[0](x, y));
    printf("Difference: %d\n", farr[1](x, y));
    printf("Product: %d\n",    farr[2](x, y));
    printf("Divide: %d\n",     farr[3](x, y));

    return 0;
}
makefile 复制代码
输出  
Sum: 15  
Difference: 5  
Product: 50  
Divide: 2
相关推荐
unspn13 小时前
选择语句if
c
煤球王子16 小时前
学而时习之:C语音中的指针
c
冷凝雨2 天前
FreeRTOS源码学习(一)内存管理heap_1、heap_3
嵌入式·c·freertos·内存管理·源码分析
小志biubiu3 天前
linux_缓冲区及简单libc库【Ubuntu】
linux·运维·服务器·c语言·学习·ubuntu·c
Dragon_D.4 天前
排序算法大全——插入排序
算法·排序算法·c·学习方法
iriczhao8 天前
【u-boot】u-boot的分区支持
c·u-boot·bootloader·引导加载
煤球王子9 天前
学而时习之:C语言中的"悬空指针"、"空类型指针"、"野指针"
c
煤球王子9 天前
学而时习之:C语言中的内存管理
c
。。。90413 天前
mit6s081 lab8 locks
操作系统·c