百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

C语言进阶教程:函数指针与回调函数

yund56 2025-05-24 22:08 25 浏览

函数指针是C语言中一个强大且灵活的特性,它允许我们将函数像数据一样进行传递和操作。回调函数则是函数指针的一种典型应用场景,它允许一个底层函数在特定事件发生时调用一个由上层代码提供的函数。

1. 函数指针 (Function Pointers)

定义

函数指针是一个指向函数的指针变量。正如普通指针变量可以存储变量的内存地址一样,函数指针存储的是函数的入口地址(即函数代码在内存中的起始位置)。

声明语法

声明一个函数指针需要指定它所指向的函数的返回类型和参数列表。

返回类型 (*指针变量名)(参数类型1, 参数类型2, ...);

关键点

  • 指针变量名 外面的括号 (* ... ) 是必需的。如果没有这个括号,例如 返回类型 *指针变量名(参数列表);,这会被编译器解释为一个返回 返回类型* 指针的普通函数声明。

示例:

 // 声明一个指向 "返回int,接受两个int参数" 的函数的指针 fp
 int (*fp)(int, int);
 
 // 声明一个指向 "无返回值,接受一个char*参数" 的函数的指针 print_func
 void (*print_func)(char*);
 
 // 声明一个指向 "无返回值,无参数" 的函数的指针 action_func
 void (*action_func)(void);

初始化与赋值

可以将一个符合签名的函数名(即函数地址)赋值给函数指针。

 #include <stdio.h>
 
 // 目标函数
 int add(int a, int b) {
     return a + b;
 }
 
 int subtract(int a, int b) {
     return a - b;
 }
 
 void greet(char *name) {
     printf("Hello, %s!\n", name);
 }
 
 int main() {
     int (*operation_ptr)(int, int); // 声明函数指针
     void (*display_ptr)(char*);
 
     // 初始化/赋值
     operation_ptr = add; // 将 add 函数的地址赋给 operation_ptr
                          // 也可以写成 operation_ptr = &add; &是可选的
 
     // 通过函数指针调用函数
     int result = operation_ptr(5, 3); // 等价于 add(5, 3)
                                      // 也可以写成 (*operation_ptr)(5, 3); *是可选的
     printf("Result of add: %d\n", result); // 输出: Result of add: 8
 
     operation_ptr = subtract;
     result = operation_ptr(10, 4);
     printf("Result of subtract: %d\n", result); // 输出: Result of subtract: 6
 
     display_ptr = greet;
     display_ptr("Alice"); // 输出: Hello, Alice!
 
     return 0;
 }

注意

  • 在给函数指针赋值时,函数名本身就代表函数的地址,所以 & 运算符是可选的。
  • 在通过函数指针调用函数时,直接使用 指针名(参数) 的形式即可,* 运算符也是可选的((*指针名)(参数))。现代C编译器通常都支持这两种形式。

函数指针作为函数参数

函数指针最常见的用途之一是作为另一个函数的参数。这允许我们将一个函数的行为传递给另一个函数,使其更加通用和灵活。

 #include <stdio.h>
 
 // 目标操作函数
 int add(int x, int y) { return x + y; }
 int multiply(int x, int y) { return x * y; }
 
 // 接受函数指针作为参数的函数
 // 'op' 是一个函数指针,指向一个接受两个int并返回int的函数
 int calculate(int a, int b, int (*op)(int, int)) {
     return op(a, b);
 }
 
 int main() {
     int num1 = 10, num2 = 5;
 
     int sum = calculate(num1, num2, add);      // 传递 add 函数
     int product = calculate(num1, num2, multiply); // 传递 multiply 函数
 
     printf("Sum: %d\n", sum);         // 输出: Sum: 15
     printf("Product: %d\n", product); // 输出: Product: 50
 
     return 0;
 }

函数指针作为函数返回值

虽然不那么常见,但函数也可以返回一个函数指针。

 #include <stdio.h>
 
 int add(int a, int b) { return a + b; }
 int subtract(int a, int b) { return a - b; }
 
 // 声明一个返回函数指针的函数
 // get_operation 返回一个指向 "接受两个int参数,返回int" 的函数的指针
 int (*get_operation(char opcode))(int, int) {
     switch (opcode) {
         case '+':
             return add;
         case '-':
             return subtract;
         default:
             return NULL; // 或返回一个默认操作
     }
 }
 
 int main() {
     int (*chosen_op)(int, int);
 
     chosen_op = get_operation('+');
     if (chosen_op != NULL) {
         printf("10 + 5 = %d\n", chosen_op(10, 5)); // 输出: 10 + 5 = 15
     }
 
     chosen_op = get_operation('-');
     if (chosen_op != NULL) {
         printf("10 - 3 = %d\n", chosen_op(10, 3)); // 输出: 10 - 3 = 7
     }
     return 0;
 }

语法 int (*get_operation(char opcode))(int, int) 可能看起来复杂。可以使用 typedef 来简化:

 #include <stdio.h>
 
 // 使用 typedef 简化函数指针类型声明
 typedef int (*binary_operation_func_ptr)(int, int);
 
 int add(int a, int b) { return a + b; }
 int subtract(int a, int b) { return a - b; }
 
 // 现在返回类型更清晰
 binary_operation_func_ptr get_operation_typedef(char opcode) {
     switch (opcode) {
         case '+': return add;
         case '-': return subtract;
         default:  return NULL;
     }
 }
 
 int main() {
     binary_operation_func_ptr op_func;
 
     op_func = get_operation_typedef('+');
     if (op_func) {
         printf("7 + 3 = %d\n", op_func(7, 3));
     }
     return 0;
 }

函数指针数组

可以创建函数指针数组,用于根据索引选择并调用不同的函数。

 #include <stdio.h>
 
 void say_hello() { printf("Hello!\n"); }
 void say_goodbye() { printf("Goodbye!\n"); }
 void say_thanks() { printf("Thanks!\n"); }
 
 int main() {
     // 声明并初始化一个函数指针数组
     void (*greetings[])(void) = {say_hello, say_goodbye, say_thanks};
 
     greetings[0](); // 调用 say_hello()
     greetings[1](); // 调用 say_goodbye()
     greetings[2](); // 调用 say_thanks()
 
     int choice;
     printf("Enter choice (0-Hello, 1-Goodbye, 2-Thanks): ");
     scanf("%d", &choice);
 
     if (choice >= 0 && choice < 3) {
         greetings[choice]();
     } else {
         printf("Invalid choice.\n");
     }
 
     return 0;
 }

2. 回调函数 (Callback Functions)

定义

回调函数是一种通过函数指针调用的函数。其核心思想是:

  1. 上层代码(调用者)定义一个函数(回调函数)。
  2. 上层代码将这个回调函数的地址(即函数指针)传递给一个底层代码(被调用者或库函数)。
  3. 底层代码在执行过程中,当某个特定事件发生或某个条件满足时,会使用接收到的函数指针来“回调”上层代码提供的那个函数。

这是一种事件驱动或异步编程的常见模式,允许库或底层模块在不直接了解上层具体实现的情况下,通知上层或请求上层执行特定操作。

为什么使用回调函数?

  • 通用性与可扩展性:库函数可以设计得更通用,因为它不需要知道将来会被如何使用。用户可以通过提供不同的回调函数来定制库的行为。
  • 解耦:上层代码和底层代码之间的耦合度降低。底层代码只依赖于回调函数的接口(签名),而不依赖于其具体实现。
  • 事件处理:在GUI编程、异步I/O、中断处理等场景中广泛使用,用于响应用户输入、数据到达、定时器超时等事件。

示例:排序中的比较函数

C标准库中的 qsort() 函数就是一个典型的使用回调函数的例子。qsort() 用于对数组进行排序,但它本身不知道如何比较数组中元素的“大小”,因为元素可以是任何类型(int, float, struct 等)。因此,qsort() 要求调用者提供一个比较函数作为回调。

 #include <stdio.h>
 #include <stdlib.h> // For qsort
 
 // 回调函数:用于比较两个整数
 // qsort 要求比较函数返回:
 //   < 0 如果 a < b
 //   = 0 如果 a == b
 //   > 0 如果 a > b
 int compare_integers(const void *a, const void *b) {
     int int_a = *(const int*)a;
     int int_b = *(const int*)b;
 
     if (int_a < int_b) return -1;
     if (int_a > int_b) return 1;
     return 0;
     // 或者更简洁: return (int_a > int_b) - (int_a < int_b);
     // 或者: return int_a - int_b; (对于整数,这通常有效,但要注意溢出)
 }
 
 // 回调函数:用于比较两个浮点数 (降序)
 int compare_floats_desc(const void *a, const void *b) {
     float float_a = *(const float*)a;
     float float_b = *(const float*)b;
 
     if (float_a < float_b) return 1;  // 注意这里是1,实现降序
     if (float_a > float_b) return -1; // 注意这里是-1
     return 0;
 }
 
 void print_array_int(int arr[], int size) {
     for (int i = 0; i < size; i++) {
         printf("%d ", arr[i]);
     }
     printf("\n");
 }
 
 void print_array_float(float arr[], int size) {
     for (int i = 0; i < size; i++) {
         printf("%.2f ", arr[i]);
     }
     printf("\n");
 }
 
 int main() {
     int numbers[] = {50, 20, 80, 10, 60, 30, 70, 40, 90, 0};
     int n_numbers = sizeof(numbers) / sizeof(numbers[0]);
 
     printf("Original integer array: ");
     print_array_int(numbers, n_numbers);
 
     // 使用 qsort 和回调函数 compare_integers 进行排序
     qsort(numbers, n_numbers, sizeof(int), compare_integers);
 
     printf("Sorted integer array (ascending): ");
     print_array_int(numbers, n_numbers);
 
     float values[] = {3.14, 1.0, 2.71, 0.5, 5.0};
     int n_values = sizeof(values) / sizeof(values[0]);
 
     printf("Original float array: ");
     print_array_float(values, n_values);
 
     qsort(values, n_values, sizeof(float), compare_floats_desc);
 
     printf("Sorted float array (descending): ");
     print_array_float(values, n_values);
 
     return 0;
 }

在这个例子中:

  • qsort() 是底层库函数。
  • compare_integerscompare_floats_desc 是上层代码提供的回调函数。
  • qsort() 在排序过程中,会反复调用我们提供的比较函数来确定元素的相对顺序。

示例:自定义事件处理器

假设我们正在编写一个库,它会监控某个事件,并在事件发生时通知用户。

 #include <stdio.h>
 #include <unistd.h> // For sleep (on POSIX systems)
 
 // 定义回调函数的类型
 typedef void (*event_handler_t)(int event_code, const char *message);
 
 // 库函数:注册事件处理器并模拟事件发生
 void register_and_monitor_event(event_handler_t handler) {
     printf("Library: Monitoring for events...\n");
     
     // 模拟一些工作
     sleep(2); // 暂停2秒
 
     // 模拟事件1发生
     printf("Library: Event 101 occurred! Calling handler.\n");
     if (handler != NULL) {
         handler(101, "Critical system alert!"); // 调用回调函数
     }
 
     sleep(1);
 
     // 模拟事件2发生
     printf("Library: Event 202 occurred! Calling handler.\n");
     if (handler != NULL) {
         handler(202, "User logged in."); // 再次调用回调函数
     }
     printf("Library: Monitoring finished.\n");
 }
 
 // 上层代码:定义具体的回调函数
 void my_event_logger(int code, const char *msg) {
     printf("--- User Callback Logger ---\n");
     printf("Event Code: %d\nMessage: %s\n", code, msg);
     printf("--- End Log ---\n\n");
 }
 
 void my_simple_notifier(int code, const char *msg) {
     printf("*** User Notification: Event %d - %s ***\n\n", code, msg);
 }
 
 int main() {
     printf("Main: Registering my_event_logger as callback.\n");
     register_and_monitor_event(my_event_logger);
 
     printf("\nMain: Registering my_simple_notifier as callback.\n");
     register_and_monitor_event(my_simple_notifier);
 
     printf("Main: Registering NULL as callback (no action).\n");
     register_and_monitor_event(NULL);
 
     return 0;
 }

在这个例子中:

  • register_and_monitor_event 是库函数,它接受一个 event_handler_t 类型的函数指针。
  • my_event_loggermy_simple_notifier 是用户定义的、符合 event_handler_t 签名的回调函数。
  • 库函数在“事件发生”时,调用用户注册的回调函数,将事件信息传递给它。

3. typedef与函数指针

typedef 可以极大地简化函数指针的声明和使用,提高代码的可读性。

// 不使用 typedef
void process_data(int data, void (*error_handler)(int, const char*));

// 使用 typedef
typedef void (*ErrorHandlerFunc)(int, const char*);
void process_data_typedef(int data, ErrorHandlerFunc handler);

第二种方式显然更易读。

总结

  • 函数指针是存储函数地址的变量,允许像操作数据一样操作函数。
  • 它们可以被赋值、传递给函数、从函数返回,以及存储在数组中。
  • 回调函数是通过函数指针调用的函数,是实现代码解耦、事件驱动和库函数定制化的强大机制。
  • 使用 typedef 可以使函数指针的声明和使用更加清晰。

理解并熟练运用函数指针和回调函数是C语言进阶的重要一步,它们为编写灵活、可扩展和模块化的程序提供了有力的工具。然而,也需要注意函数指针的类型匹配,以及在回调中避免引入不必要的复杂性或循环依赖。

相关推荐

SM小分队Girls on Top,女神战队少了f(x)?

这次由SM娱乐公司在冬季即将开演的smtown里,将公司的所有女团成员集结成了一个小分队project。第一位这是全面ACE的大姐成员权宝儿(BoA),出道二十年,在日本单人销量过千万,韩国国内200...

韩国女团 aespa 首场 VR 演唱会或暗示 Quest 3 将于 10 月推出

AmazeVR宣布将在十月份举办一场现场VR音乐会,观众将佩戴MetaQuest3进行体验。韩国女团aespa于2020年11月出道,此后在日本推出了三张金唱片,在韩国推出了...

韩网热议!女团aespa成员Giselle在长腿爱豆中真的是legend

身高163的Giselle,长腿傲人,身材比例绝了...

假唱而被骂爆的女团:IVE、NewJeans、aespa上榜

在韩国,其实K-pop偶像并不被认为是真正的歌手,因为偶像们必须兼备舞蹈能力、也经常透过对嘴来完成舞台。由于科技的日渐发达,也有许多网友会利用消音软体来验证K-pop偶像到底有没有开麦唱歌,导致假唱这...

新女团Aespa登时尚大片 四个少女四种style

来源:环球网

韩国女团aespa新歌MV曝光 画面梦幻造型超美

12月20日,韩国女团aespa翻唱曲《DreamsComeTrue》MV公开,视频中,她们的造型超美!WINTER背后长出一双梦幻般的翅膀。柳智敏笑容甜美。宁艺卓皮肤白皙。GISELLE五官精致...

女网友向拳头维权,自称是萨勒芬妮的原型?某韩国女团抄袭KDA

女英雄萨勒芬妮(Seraphine)是拳头在2020年推出的第五位新英雄,在还没有正式上线时就备受lsp玩家的关注,因为她实在是太可爱了。和其他新英雄不同的是,萨勒芬妮在没上线时就被拳头当成虚拟偶像来...

人气TOP女团是?INS粉丝数见分晓;TWICE成员为何在演唱会落泪?

现在的人气TOP女团是?INS粉丝数见分晓!现在爱豆和粉丝之间的交流方法变得多种多样,但是Instagram依然是主要的交流手段。很多粉丝根据粉丝数评价偶像的人气,拥有数百、数千万粉丝的组合作为全球偶...

韩国女团MVaespa Drama MV_韩国女团穿超短裙子跳舞

WelcometoDrama.Pleasefollow4ruleswhilewatchingtheDrama.·1)Lookbackimmediatelywhenyoufe...

aespa师妹团今年将出道! SM职员亲口曝「新女团风格、人数」

记者刘宛欣/综合报导南韩造星工厂SM娱乐曾打造出东方神起、SUPERJUNIOR、少女时代、SHINee、EXO等传奇团体,近年推出的aespa、RIIZE更是双双成为新生代一线团体,深受大众与粉丝...

南韩最活跃的女团aespa,新专辑《Girls》即将发布,盘点昔日经典

女团aespa歌曲盘点,新专辑《Girls》即将发布,期待大火。明天也就是2022年的7月8号,aespa新专辑《Girls》即将发行。这是继首张专辑《Savage》之后,时隔19个月的第二张专辑,这...

章泽天女团aespa出席戛纳晚宴 宋康昊携新片亮相

搜狐娱乐讯(山今/文玄反影/图科明/视频)法国时间5月23日晚,女团aespa、宋康昊、章泽天等明星亮相戛纳晚宴。章泽天身姿优越。章泽天肩颈线优越。章泽天双臂纤细。章泽天仪态端正。女团aespa亮...

Aespa舞台暴露身高比例,宁艺卓脸大,柳智敏有“TOP”相

作为SM公司最新女团aespa,初舞台《BlackMamba》公开,在初舞台里,看得出来SM公司是下了大功夫的,虽然之前SM公司新出的女团都有很长的先导片,但是aespa显然是有“特殊待遇”。运用了...

AESPA女团成员柳智敏karina大美女

真队内速度最快最火达成队内首个且唯一两百万点赞五代男女团中输断层第一(图转自微博)...

对来学校演出的女团成员语言性骚扰?韩国这所男高的学生恶心透了

哕了……本月4日,景福男子高中相关人士称已经找到了在SNS中上传对aespa成员进行性骚扰文章的学生,并开始着手调查。2日,SM娱乐创始人李秀满的母校——景福高中迎来了建校101周年庆典活动。当天,S...