# 19 函数指针与回调 ## 19.1 完整概念讲解 ### 函数指针 **函数指针(Function Pointer)**是存储函数地址的指针变量。通过函数指针可以间接调用函数,实现动态分发。 **函数指针的组成:** - 返回类型 - `(*指针名)`:指针名被括号包围 - 参数列表 ### 回调函数 **回调函数(Callback Function)**是通过函数指针传递给其他函数,在特定时机被调用的函数。 **应用场景:** - 排序算法(如`qsort`) - 事件处理 - 异步操作完成通知 - 钩子函数(Hook) ### 函数指针数组 **函数指针数组**是存储多个函数指针的数组,常用于实现跳转表(Jump Table)。 ### 内核操作表模式 Linux内核广泛使用函数指针实现多态,如`file_operations`结构体将文件操作映射到具体实现。 ## 19.2 核心API/语法 ```c // 函数指针定义 返回类型 (*指针名)(参数类型1, 参数类型2, ...); // 使用typedef简化 typedef 返回类型 (*函数指针类型名)(参数类型1, 参数类型2, ...); 函数指针类型名 指针变量; // 函数指针赋值和调用 指针变量 = 函数名; 指针变量(实参1, 实参2, ...); // 等同于 函数名(实参1, 实参2, ...); // 函数指针数组 返回类型 (*数组名[大小])(参数列表); ``` ## 19.3 代码示例(完整可编译,附gcc命令) ```c #include #include #include // 函数指针基础 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; } ``` **编译命令:** ```bash 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层通过函数指针支持不同文件系统,驱动框架支持多种硬件。