Skip to content

函数指针与回调函数

在C语言中,函数指针是一种特殊的指针,它指向函数而不是数据。回调函数则是作为参数传递给其他函数的函数。这些概念在C语言中非常重要,特别是在事件处理、排序算法、状态机等场景中。在本章节中,我们将学习函数指针的定义、使用以及回调函数的应用。

1. 函数指针

1.1 什么是函数指针?

函数指针是指向函数的指针变量,它存储的是函数在内存中的地址。与普通指针类似,函数指针也有类型,其类型由函数的返回类型和参数列表决定。

1.2 函数指针的定义

定义函数指针就是创建一个指向特定类型函数的指针变量。函数指针定义的基本语法如下:

c
返回类型 (*指针变量名)(参数列表);

1.3 说明

  • 返回类型:函数指针指向的函数的返回类型
  • (*指针变量名):括号是必需的,用于确保*与指针变量名结合,而不是与返回类型结合
  • 参数列表:函数指针指向的函数的参数列表

1.4 示例

c
// 定义一个指向返回类型为int,参数为两个int的函数的指针
int (*add_ptr)(int, int);

// 定义一个指向返回类型为void,无参数的函数的指针
void (*print_ptr)(void);

// 定义一个指向返回类型为char*,参数为char*的函数的指针
char* (*string_ptr)(char*);

2. 函数指针的初始化和使用

2.1 初始化函数指针

初始化函数指针就是将函数的地址赋给函数指针变量。在C语言中,函数名本身就代表函数的地址,所以可以直接将函数名赋给函数指针。

2.2 示例

c
#include <stdio.h>

// 定义一个加法函数
int add(int a, int b) {
    return a + b;
}

// 定义一个打印函数
void print_hello() {
    printf("Hello, World!\n");
}

int main() {
    // 初始化函数指针
    int (*add_ptr)(int, int) = add;
    void (*print_ptr)(void) = print_hello;
    
    // 使用函数指针调用函数
    int sum = add_ptr(5, 3);
    printf("5 + 3 = %d\n", sum);  // 8
    
    print_ptr();  // 输出Hello, World!
    
    return 0;
}

2.3 函数指针的调用方式

函数指针有两种调用方式:

  1. 使用解引用运算符*(*add_ptr)(5, 3);
  2. 直接调用:add_ptr(5, 3);(更简洁,推荐使用)

3. 函数指针作为函数参数

函数指针可以作为参数传递给其他函数,这使得函数更加灵活,可以根据需要动态选择要调用的函数。

3.1 示例

c
#include <stdio.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;
}

int divide(int a, int b) {
    return b != 0 ? a / b : 0;
}

// 接受函数指针作为参数的函数
void calculate(int a, int b, int (*operation)(int, int)) {
    int result = operation(a, b);
    printf("结果: %d\n", result);
}

int main() {
    int x = 10, y = 5;
    
    printf("加法: ");
    calculate(x, y, add);  // 15
    
    printf("减法: ");
    calculate(x, y, subtract);  // 5
    
    printf("乘法: ");
    calculate(x, y, multiply);  // 50
    
    printf("除法: ");
    calculate(x, y, divide);  // 2
    
    return 0;
}

4. 回调函数

4.1 什么是回调函数?

回调函数是作为参数传递给其他函数的函数,在特定事件或条件发生时被调用。回调函数的本质是函数指针的应用。

4.2 回调函数的应用场景

回调函数常用于以下场景:

  • 事件处理:当特定事件发生时调用回调函数
  • 排序算法:如qsort函数使用回调函数定义比较规则
  • 状态机:在状态转换时调用回调函数
  • 异步操作:操作完成后调用回调函数通知结果

4.3 示例:自定义排序

c
#include <stdio.h>

// 比较函数类型定义
typedef int (*CompareFunc)(const void*, const void*);

// 排序函数,接受回调函数作为参数
void sort(int arr[], int size, CompareFunc compare) {
    for (int i = 0; i < size - 1; i++) {
        for (int j = 0; j < size - i - 1; j++) {
            // 使用回调函数比较元素
            if (compare(&arr[j], &arr[j+1]) > 0) {
                // 交换元素
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

// 升序比较函数
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");
}

int main() {
    int arr[] = {5, 2, 8, 1, 9};
    int size = sizeof(arr) / sizeof(arr[0]);
    
    printf("原始数组: ");
    print_array(arr, size);
    
    // 升序排序
    sort(arr, size, compare_asc);
    printf("升序排序: ");
    print_array(arr, size);
    
    // 降序排序
    sort(arr, size, compare_desc);
    printf("降序排序: ");
    print_array(arr, size);
    
    return 0;
}

5. 函数指针数组

函数指针数组是存储多个函数指针的数组,用于根据索引选择不同的函数。

5.1 函数指针数组的定义

c
返回类型 (*数组名[数组大小])(参数列表);

5.2 示例

c
#include <stdio.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;
}

int divide(int a, int b) {
    return b != 0 ? a / b : 0;
}

int main() {
    // 定义函数指针数组
    int (*operations[])(int, int) = {add, subtract, multiply, divide};
    int size = sizeof(operations) / sizeof(operations[0]);
    
    int x = 10, y = 5;
    
    // 使用函数指针数组调用函数
    for (int i = 0; i < size; i++) {
        int result = operations[i](x, y);
        switch (i) {
            case 0:
                printf("加法: %d\n", result);
                break;
            case 1:
                printf("减法: %d\n", result);
                break;
            case 2:
                printf("乘法: %d\n", result);
                break;
            case 3:
                printf("除法: %d\n", result);
                break;
        }
    }
    
    return 0;
}

6. typedef与函数指针

使用typedef可以为函数指针类型创建别名,使代码更加简洁易读。

6.1 语法

c
typedef 返回类型 (*类型别名)(参数列表);

6.2 示例

c
#include <stdio.h>

// 为函数指针类型创建别名
typedef int (*MathOperation)(int, int);
typedef void (*PrintFunction)(void);

// 数学运算函数
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

// 打印函数
void print_hello() {
    printf("Hello, World!\n");
}

int main() {
    // 使用类型别名声明函数指针
    MathOperation op1 = add;
    MathOperation op2 = subtract;
    PrintFunction printer = print_hello;
    
    // 使用函数指针
    printf("5 + 3 = %d\n", op1(5, 3));  // 8
    printf("5 - 3 = %d\n", op2(5, 3));  // 2
    printer();  // 输出Hello, World!
    
    return 0;
}

7. 回调函数的高级应用

7.1 带参数的回调函数

回调函数可以接受额外的参数,这使得回调函数更加灵活。

c
#include <stdio.h>

// 回调函数类型
typedef void (*CallbackFunction)(int, void*);

// 执行回调函数
void execute_callback(int value, CallbackFunction callback, void* user_data) {
    callback(value, user_data);
}

// 回调函数1
void callback1(int value, void* user_data) {
    int* extra = (int*)user_data;
    printf("回调函数1: 值 = %d, 额外数据 = %d\n", value, *extra);
}

// 回调函数2
void callback2(int value, void* user_data) {
    char* message = (char*)user_data;
    printf("回调函数2: 值 = %d, 消息 = %s\n", value, message);
}

int main() {
    int extra_data = 42;
    char message[] = "Hello from callback!";
    
    // 执行回调函数1
    execute_callback(10, callback1, &extra_data);
    
    // 执行回调函数2
    execute_callback(20, callback2, message);
    
    return 0;
}

7.2 回调函数与结构体

回调函数可以与结构体结合使用,实现更复杂的功能。

c
#include <stdio.h>

// 回调函数类型
typedef void (*EventCallback)(void*);

// 事件结构体
typedef struct {
    int event_id;
    EventCallback callback;
    void* user_data;
} Event;

// 处理事件
void process_event(Event* event) {
    printf("处理事件 ID: %d\n", event->event_id);
    event->callback(event->user_data);
}

// 事件回调函数1
void on_button_click(void* user_data) {
    printf("按钮被点击! 用户数据: %s\n", (char*)user_data);
}

// 事件回调函数2
void on_timer_expire(void* user_data) {
    printf("定时器到期! 用户数据: %d\n", *(int*)user_data);
}

int main() {
    // 创建事件1
    char button_message[] = "登录按钮";
    Event button_event = {
        .event_id = 1,
        .callback = on_button_click,
        .user_data = button_message
    };
    
    // 创建事件2
    int timer_value = 5000;
    Event timer_event = {
        .event_id = 2,
        .callback = on_timer_expire,
        .user_data = &timer_value
    };
    
    // 处理事件
    process_event(&button_event);
    process_event(&timer_event);
    
    return 0;
}

8. 函数指针的注意事项

  1. 类型匹配:函数指针的类型必须与它指向的函数的类型完全匹配,包括返回类型和参数列表
  2. 括号的使用:在定义函数指针时,(*指针变量名)的括号是必需的,否则会被解析为返回指针的函数
  3. 函数名:函数名本身就是函数的地址,不需要使用&运算符
  4. typedef的使用:使用typedef为函数指针类型创建别名,可以使代码更加简洁易读
  5. 回调函数的生命周期:确保回调函数在被调用时仍然有效,避免使用已释放的函数

9. 示例:综合运用

让我们看一个综合运用函数指针和回调函数的例子:

c
#include <stdio.h>

// 学生结构体
typedef struct {
    char name[50];
    int score;
} Student;

// 比较函数类型
typedef int (*CompareStudent)(const void*, const void*);

// 打印函数类型
typedef void (*PrintStudent)(const void*);

// 处理学生数组的函数
void process_students(void* students, int count, size_t size,
                     CompareStudent compare,
                     PrintStudent print) {
    // 冒泡排序
    for (int i = 0; i < count - 1; i++) {
        for (int j = 0; j < count - i - 1; j++) {
            void* student1 = (char*)students + j * size;
            void* student2 = (char*)students + (j + 1) * size;
            if (compare(student1, student2) > 0) {
                // 交换学生
                char temp[size];
                memcpy(temp, student1, size);
                memcpy(student1, student2, size);
                memcpy(student2, temp, size);
            }
        }
    }
    
    // 打印学生
    for (int i = 0; i < count; i++) {
        void* student = (char*)students + i * size;
        print(student);
    }
}

// 按分数升序比较
int compare_score_asc(const void* a, const void* b) {
    return ((Student*)a)->score - ((Student*)b)->score;
}

// 按分数降序比较
int compare_score_desc(const void* a, const void* b) {
    return ((Student*)b)->score - ((Student*)a)->score;
}

// 打印学生信息
void print_student(const void* student) {
    Student* s = (Student*)student;
    printf("姓名: %s, 分数: %d\n", s->name, s->score);
}

int main() {
    // 创建学生数组
    Student students[] = {
        {"张三", 85},
        {"李四", 92},
        {"王五", 78},
        {"赵六", 95},
        {"钱七", 88}
    };
    
    int count = sizeof(students) / sizeof(students[0]);
    size_t size = sizeof(Student);
    
    printf("按分数升序排序:\n");
    process_students(students, count, size, compare_score_asc, print_student);
    
    printf("\n按分数降序排序:\n");
    process_students(students, count, size, compare_score_desc, print_student);
    
    return 0;
}

10. 小结

函数指针和回调函数是C语言中强大的特性,它们允许我们:

  1. 动态选择函数:在运行时根据需要选择不同的函数执行
  2. 实现回调机制:将函数作为参数传递,实现事件驱动编程
  3. 提高代码复用性:通过回调函数,可以编写更通用的代码
  4. 实现多态:在C语言中模拟面向对象编程的多态特性

函数指针的语法可能一开始看起来有点复杂,但一旦掌握了基本概念,就会发现它们是非常强大和灵活的工具。回调函数则是函数指针的重要应用,在许多库和框架中都有广泛的使用。

通过合理使用函数指针和回调函数,可以编写出更加模块化、可扩展和灵活的C程序。