# 9 指针与函数 ## 9.1 完整概念讲解 ### 指针做函数参数(传址调用) 函数参数传递有两种方式: 1. **传值调用**:传递变量的副本,函数内修改不影响原变量 2. **传址调用**:传递变量的地址,函数内通过指针可以修改原变量 ```c void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main() { int x = 10, y = 20; swap(&x, &y); // 传址调用 // x=20, y=10 } ``` ### 函数返回指针 - 可以返回指向静态变量、全局变量或动态分配内存的指针 - **绝对不要返回局部变量的地址**(函数结束后局部变量被销毁) ```c // 错误示例 int* wrong_func() { int local = 10; return &local; // 危险!局部变量地址 } // 正确示例1:返回全局变量指针 int global_var = 100; int* correct_func1() { return &global_var; } // 正确示例2:返回动态分配内存 int* correct_func2() { int *p = malloc(sizeof(int)); *p = 200; return p; // 调用者负责free } ``` ### 函数指针定义与使用 函数指针是指向函数的指针变量,可以用于动态调用函数。 ```c // 函数指针声明 int (*func_ptr)(int, int); // 赋值 func_ptr = add; // 调用 int result = func_ptr(3, 4); ``` ### 函数指针数组 ```c 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 (*ops[])(int, int) = {add, sub, mul}; ``` ### 回调模式 将函数作为参数传递给另一个函数,实现灵活的行为定制。 ### qsort使用 ```c #include int compare(const void *a, const void *b) { return (*(int*)a - *(int*)b); } int main() { int arr[] = {5, 2, 8, 1, 9}; qsort(arr, 5, sizeof(int), compare); } ``` ### 内核file_operations操作表模式 Linux内核中广泛使用函数指针数组实现面向对象的多态。 ## 9.2 核心API/语法 ### 函数指针语法 ```c // 基本语法 返回类型 (*函数指针名)(参数类型列表); // 示例 int (*add_ptr)(int, int); void (*print_ptr)(const char*); ``` ### 回调函数模式 ```c // 定义回调类型 typedef int (*compare_func)(const void*, const void*); // 使用回调 void sort(int *arr, int size, compare_func cmp) { // 使用cmp进行比较 } ``` ### qsort函数 ```c #include void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void*, const void*)); ``` ## 9.3 代码示例(完整可编译,附gcc命令) ### 示例1:传值调用 vs 传址调用 ```c #include // 传值调用:无效 void swap_by_value(int a, int b) { int temp = a; a = b; b = temp; printf("函数内: a=%d, b=%d\n", a, b); } // 传址调用:有效 void swap_by_address(int *a, int *b) { int temp = *a; *a = *b; *b = temp; printf("函数内: *a=%d, *b=%d\n", *a, *b); } int main() { int x = 10, y = 20; printf("调用前: x=%d, y=%d\n", x, y); swap_by_value(x, y); printf("传值调用后: x=%d, y=%d\n", x, y); swap_by_address(&x, &y); printf("传址调用后: x=%d, y=%d\n", x, y); return 0; } ``` 编译命令:`gcc -o pass_by_value_vs_address pass_by_value_vs_address.c` ### 示例2:函数返回指针 ```c #include #include // 危险:返回局部变量地址 int* dangerous_func() { int local_var = 100; return &local_var; // 未定义行为 } // 正确:返回全局变量指针 int global_var = 200; int* get_global_ptr() { return &global_var; } // 正确:返回静态变量指针 int* get_static_ptr() { static int static_var = 300; return &static_var; } // 正确:返回动态分配内存 int* get_dynamic_mem(int value) { int *p = (int*)malloc(sizeof(int)); if (p != NULL) { *p = value; } return p; } int main() { // 错误用法 // int *p1 = dangerous_func(); // printf("危险函数: %d\n", *p1); // 未定义行为 // 正确用法 int *p2 = get_global_ptr(); printf("全局变量: %d\n", *p2); int *p3 = get_static_ptr(); printf("静态变量: %d\n", *p3); int *p4 = get_dynamic_mem(400); if (p4 != NULL) { printf("动态内存: %d\n", *p4); free(p4); } return 0; } ``` 编译命令:`gcc -o return_pointer return_pointer.c` ### 示例3:函数指针基础 ```c #include 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 div_int(int a, int b) { return b != 0 ? a / b : 0; } int main() { // 函数指针声明与赋值 int (*operation)(int, int); operation = add; printf("3 + 4 = %d\n", operation(3, 4)); operation = sub; printf("3 - 4 = %d\n", operation(3, 4)); operation = mul; printf("3 * 4 = %d\n", operation(3, 4)); operation = div_int; printf("3 / 4 = %d\n", operation(3, 4)); // 函数指针数组 int (*ops[])(int, int) = {add, sub, mul, div_int}; char *op_names[] = {"+", "-", "*", "/"}; for (int i = 0; i < 4; i++) { printf("10 %s 5 = %d\n", op_names[i], ops[i](10, 5)); } return 0; } ``` 编译命令:`gcc -o function_pointer function_pointer.c` ### 示例4:回调模式与qsort ```c #include #include // 比较函数:升序 int compare_asc(const void *a, const void *b) { return (*(int*)a - *(int*)b); } // 比较函数:降序 int compare_desc(const void *a, const void *b) { return (*(int*)b - *(int*)a); } // 打印数组 void print_array(int *arr, int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n"); } // 通用排序函数 typedef int (*compare_func)(const void*, const void*); void sort_array(int *arr, int size, compare_func cmp) { qsort(arr, size, sizeof(int), cmp); } int main() { int arr1[] = {5, 2, 8, 1, 9, 3}; int arr2[] = {5, 2, 8, 1, 9, 3}; int size = sizeof(arr1) / sizeof(arr1[0]); printf("原始数组: "); print_array(arr1, size); sort_array(arr1, size, compare_asc); printf("升序排序: "); print_array(arr1, size); sort_array(arr2, size, compare_desc); printf("降序排序: "); print_array(arr2, size); return 0; } ``` 编译命令:`gcc -o callback_qsort callback_qsort.c` ### 示例5:内核file_operations模式 ```c #include #include #include // 模拟内核file_operations结构 struct file_operations { int (*open)(const char *path); int (*read)(int fd, void *buf, int count); int (*write)(int fd, const void *buf, int count); int (*close)(int fd); }; // 模拟设备实现 static int my_open(const char *path) { printf("打开设备: %s\n", path); return 0; } static int my_read(int fd, void *buf, int count) { printf("从设备读取 %d 字节\n", count); strcpy((char*)buf, "设备数据"); return strlen("设备数据"); } static int my_write(int fd, const void *buf, int count) { printf("向设备写入: %s (%d字节)\n", (const char*)buf, count); return count; } static int my_close(int fd) { printf("关闭设备\n"); return 0; } // 操作表 struct file_operations my_fops = { .open = my_open, .read = my_read, .write = my_write, .close = my_close }; // 使用操作表的通用函数 int device_operation(struct file_operations *fops, const char *path) { int fd = fops->open(path); char buf[100]; fops->read(fd, buf, sizeof(buf)); printf("读取到: %s\n", buf); fops->write(fd, "Hello Device", 12); fops->close(fd); return 0; } int main() { device_operation(&my_fops, "/dev/my_device"); return 0; } ``` 编译命令:`gcc -o kernel_pattern kernel_pattern.c` ## 9.4 注意事项与易错点 ### 返回局部变量地址 - 函数结束后,局部变量的内存被释放 - 返回的指针成为悬空指针,解引用是未定义行为 - 解决方案:使用动态内存、静态变量或全局变量 ### 函数指针类型不匹配 - 函数指针的返回类型和参数类型必须与目标函数匹配 - 类型不匹配可能导致未定义行为 ### 回调函数中的this指针 - C语言没有this指针,通常通过void*参数传递上下文 - 注意内存管理和类型转换 ### 函数指针数组越界 - 与普通数组一样,函数指针数组也需要边界检查 - 越界访问可能导致程序崩溃 ### 内存泄漏 - 动态分配的内存必须由调用者负责释放 - 函数返回指针时,文档应明确内存所有权 ### 递归函数的栈溢出 - 递归层次过深可能导致栈溢出 - 考虑使用迭代或尾递归优化 ## 9.5 面试要点(3-5个Q&A) **Q1: 传值调用和传址调用有什么区别?** A: 传值调用传递变量的副本,函数内修改不影响原变量;传址调用传递变量的地址,函数内通过指针可以修改原变量。传址调用效率更高(避免大结构体复制),且可以修改实参。 **Q2: 函数返回指针时需要注意什么?** A: 绝对不要返回局部变量的地址(函数结束后内存被释放)。应该返回全局变量、静态变量的指针,或动态分配的内存(调用者负责free)。 **Q3: 什么是函数指针?有什么用途?** A: 函数指针是指向函数的指针变量,可以用于动态调用函数。用途包括:回调模式、函数指针数组实现分发表、qsort等通用算法、模拟面向对象的多态。 **Q4: 如何实现类似qsort的回调机制?** A: 定义函数指针类型,将比较函数作为参数传递。例如:`typedef int (*compare_func)(const void*, const void*)`,然后将具体比较函数传入排序函数。 **Q5: 内核中的file_operations模式是什么?** A: 这是Linux内核中广泛使用的面向对象模式。通过结构体中的函数指针实现多态,不同设备驱动实现相同接口的函数。应用程序通过统一的系统调用接口操作不同设备。 ## 9.6 完整示例程序 ```c #include #include #include `),实际编译时需要修正。