19. 函数指针与回调.md 6.2 KB

19 函数指针与回调

19.1 完整概念讲解

函数指针

函数指针(Function Pointer)是存储函数地址的指针变量。通过函数指针可以间接调用函数,实现动态分发。

函数指针的组成:

  • 返回类型
  • (*指针名):指针名被括号包围
  • 参数列表

回调函数

回调函数(Callback Function)是通过函数指针传递给其他函数,在特定时机被调用的函数。

应用场景:

  • 排序算法(如qsort
  • 事件处理
  • 异步操作完成通知
  • 钩子函数(Hook)

函数指针数组

函数指针数组是存储多个函数指针的数组,常用于实现跳转表(Jump Table)。

内核操作表模式

Linux内核广泛使用函数指针实现多态,如file_operations结构体将文件操作映射到具体实现。

19.2 核心API/语法

// 函数指针定义
返回类型 (*指针名)(参数类型1, 参数类型2, ...);

// 使用typedef简化
typedef 返回类型 (*函数指针类型名)(参数类型1, 参数类型2, ...);
函数指针类型名 指针变量;

// 函数指针赋值和调用
指针变量 = 函数名;
指针变量(实参1, 实参2, ...);  // 等同于 函数名(实参1, 实参2, ...);

// 函数指针数组
返回类型 (*数组名[大小])(参数列表);

19.3 代码示例(完整可编译,附gcc命令)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 函数指针基础
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }

// 回调函数示例:qsort的比较函数
int compare_int(const void *a, const void *b) {
    return (*(int *)a - *(int *)b);
}

int compare_float(const void *a, const void *b) {
    float diff = *(float *)a - *(float *)b;
    return (diff > 0) ? 1 : (diff < 0) ? -1 : 0;
}

// 回调函数示例:遍历数组并应用函数
void apply_to_array(int arr[], int size, int (*func)(int)) {
    for (int i = 0; i < size; i++) {
        arr[i] = func(arr[i]);
    }
}

int square(int x) { return x * x; }
int negate(int x) { return -x; }

// 函数指针数组(跳转表)
int calculator(int a, int b, int op) {
    // 函数指针数组
    int (*operations[])(int, int) = {add, subtract, multiply};

    if (op >= 0 && op <= 2) {
        return operations[op](a, b);
    }
    return 0;
}

// 模拟内核file_operations
struct file_operations {
    int (*open)(const char *path);
    int (*read)(int fd, void *buf, int size);
    int (*write)(int fd, const void *buf, int size);
    int (*close)(int fd);
};

int my_open(const char *path) {
    printf("Opening file: %s\n", path);
    return 0;
}

int my_read(int fd, void *buf, int size) {
    printf("Reading %d bytes from fd %d\n", size, fd);
    return size;
}

int my_write(int fd, const void *buf, int size) {
    printf("Writing %d bytes to fd %d\n", size, fd);
    return size;
}

int my_close(int fd) {
    printf("Closing fd %d\n", fd);
    return 0;
}

int main() {
    // 1. 函数指针基础
    printf("=== 函数指针基础 ===\n");
    int (*operation)(int, int);  // 定义函数指针

    operation = add;
    printf("add(10, 5) = %d\n", operation(10, 5));

    operation = subtract;
    printf("subtract(10, 5) = %d\n", operation(10, 5));

    operation = multiply;
    printf("multiply(10, 5) = %d\n", operation(10, 5));

    // 2. qsort使用
    printf("\n=== qsort回调 ===\n");
    int nums[] = {5, 2, 8, 1, 9, 3};
    int n = sizeof(nums) / sizeof(nums[0]);

    qsort(nums, n, sizeof(int), compare_int);

    printf("Sorted: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", nums[i]);
    }
    printf("\n");

    // 3. apply_to_array回调
    printf("\n=== apply_to_array ===\n");
    int arr[] = {1, 2, 3, 4, 5};
    int size = 5;

    printf("Original: ");
    for (int i = 0; i < size; i++) printf("%d ", arr[i]);
    printf("\n");

    apply_to_array(arr, size, square);
    printf("Squared:  ");
    for (int i = 0; i < size; i++) printf("%d ", arr[i]);
    printf("\n");

    apply_to_array(arr, size, negate);
    printf("Negated:  ");
    for (int i = 0; i < size; i++) printf("%d ", arr[i]);
    printf("\n");

    // 4. 函数指针数组(跳转表)
    printf("\n=== 跳转表 ===\n");
    printf("10 + 5 = %d\n", calculator(10, 5, 0));
    printf("10 - 5 = %d\n", calculator(10, 5, 1));
    printf("10 * 5 = %d\n", calculator(10, 5, 2));

    // 5. 模拟内核操作表
    printf("\n=== 内核操作表模式 ===\n");
    struct file_operations fops = {
        .open = my_open,
        .read = my_read,
        .write = my_write,
        .close = my_close
    };

    fops.open("/dev/test");
    fops.read(0, NULL, 100);
    fops.write(0, "data", 4);
    fops.close(0);

    return 0;
}

编译命令:

gcc -Wall -Wextra -o function_pointer function_pointer.c
./function_pointer

19.4 注意事项与易错点

  1. 函数指针类型必须匹配:返回类型、参数类型必须与目标函数一致
  2. NULL指针检查:使用函数指针前应检查是否为NULL
  3. 回调函数的约定:回调函数的参数和返回值需符合调用者的约定
  4. 函数指针数组越界:访问超出数组范围的索引是未定义行为
  5. volatile/const修饰:某些场景需要对函数指针使用特殊修饰符

19.5 面试要点(3-5个Q&A)

Q1: 函数指针和函数名有什么区别? A: 函数名在表达式中会衰减为函数指针,两者可互换。但函数名是常量,不能赋值;函数指针是变量,可以修改。

Q2: 回调函数有什么优点? A: 解耦调用者和被调用者,实现多态,支持异步编程,便于扩展和测试。

Q3: 如何声明返回函数指针的函数? A: 使用int (*func(int))(int, int)typedef简化:typedef int (*fp)(int, int); fp func(int);

Q4: qsort的比较函数为什么用void指针? A: qsort是通用排序函数,不知道元素类型。使用void指针接受任意类型,由比较函数负责类型转换和比较。

Q5: 内核中为什么大量使用函数指针? A: 实现多态和抽象,将接口与实现分离。如VFS层通过函数指针支持不同文件系统,驱动框架支持多种硬件。