C语言第13节:指针(3)

时间:2024-11-16 14:07:28

1. 回调函数

回调函数的基本思想是,将函数指针作为参数传递给另一个函数,并在需要时通过这个函数指针调用对应的函数。这种方式允许一个函数对执行的内容进行控制,而不需要知道具体的实现细节。

回调函数在以下场景中尤为有用:

  1. 事件驱动编程:例如处理按键事件或鼠标点击。
  2. 排序或过滤:可以将比较函数作为参数传递给排序函数。
  3. 异步操作:例如定时器到时后调用某个处理函数。

1.1 实现回调函数的步骤

实现回调函数主要有以下几个步骤:

  1. 定义一个回调函数:编写一个可以被回调的函数,这个函数的签名需要与回调机制所期望的签名一致。
  2. 定义函数指针:定义一个可以指向回调函数的函数指针。
  3. 调用函数并传递回调函数指针:在需要执行回调函数的地方,通过传递回调函数的地址来调用它。

1.2 回调函数实例

上一节我们讲解了转移表来改善我们的简易计算器,那我们能不能通过回调函数来实现呢?

1.2.1 改造前的代码

//使用回调函数改造前
#include <stdio.h>
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 a, int b)
{
    return a / b;
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    do
    {
        printf("*************************\n");
        printf(" 1:add2:sub \n");
        printf(" 3:mul4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
            case 1:
                printf("输入操作数:");
                scanf("%d %d", &x, &y);
                ret = add(x, y);
                printf("ret = %d\n", ret);
                break;
            case 2:
                printf("输入操作数:");
                scanf("%d %d", &x, &y);
                ret = sub(x, y);
                printf("ret = %d\n", ret);
                break;
            case 3:
                printf("输入操作数:");
                scanf("%d %d", &x, &y);
                ret = mul(x, y);
                printf("ret = %d\n", ret);
                break;
            case 4:
                printf("输入操作数:");
                scanf("%d %d", &x, &y);
                ret = div(x, y);
                printf("ret = %d\n", ret);
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误\n");
                break;
        }
    } while (input);
    return 0;
}

我们发现每一个case中的代码有很大一部分都是重复的,那么这一部分可以用回调函数来改造一下

1.2.2 改造后的代码

//使用回到函数改造后
#include <stdio.h>
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 a, int b)
{
    return a / b;
}
void calc(int(*pf)(int, int))
{
    int ret = 0;
    int x, y;
    printf("输入操作数:");
    scanf("%d %d", &x, &y);
    ret = pf(x, y);
    printf("ret = %d\n", ret);
}
int main()
{
    int input = 1;
    do
    {
        printf("*************************\n");
        printf(" 1:add2:sub \n");
        printf(" 3:mul4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
            case 1:
                calc(add);
                break;
            case 2:
                calc(sub);
                break;
            case 3:
                calc(mul);
                break;
            case 4:
                calc(div);
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误\n");
                break;
        }
    } while (input);
    return 0;
}

1.2.3 代码解析

这段代码的核心在于使用回调函数 calc 统一处理不同的运算操作,从而简化了 main 函数中的逻辑。

1.2.3.1 定义 calc 回调函数

calc 函数使用了函数指针 pf,可以接收任意符合 int (int, int) 签名的函数。calc 函数的作用是通用地处理不同的运算逻辑,而不直接关心运算的具体实现,这使得代码更加灵活。

void calc(int (*pf)(int, int)) {
    int ret = 0;
    int x, y;
    printf("输入操作数:");
    scanf("%d %d", &x, &y);
    ret = pf(x, y);  // 调用传入的回调函数
    printf("ret = %d\n", ret);
}
  • 输入操作数:提示用户输入两个操作数 xy
  • 调用回调函数:通过 pf(x, y) 调用传入的运算函数。
  • 输出结果:将结果 ret 输出给用户。
1.2.3.2 主函数 main

main 函数的作用是提供用户交互界面,根据用户的选择调用对应的运算函数。

int main() {
    int input = 1;
    do {
        printf("*************************\n");
        printf(" 1:add2:sub \n");
        printf(" 3:mul4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);

        switch (input) {
            case 1:
                calc(add);  // 将加法函数指针传递给 calc
                break;
            case 2:
                calc(sub);  // 将减法函数指针传递给 calc
                break;
            case 3:
                calc(mul);  // 将乘法函数指针传递给 calc
                break;
            case 4:
                calc(div);  // 将除法函数指针传递给 calc
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误\n");
                break;
        }
    } while (input);
    
    return 0;
}
  • 菜单显示:每次循环都会显示运算选项菜单,用户可以选择对应的运算。
  • 用户输入:获取用户的选择并存储在 input 中。
  • 调用 calc 函数:根据 input 的值,通过 calc 调用对应的运算函数。
  • 退出程序:当 input0 时,程序输出退出提示并结束循环。

1.3 回调函数的优点

  1. 代码复用:通过传递不同的回调函数,calc 函数可以实现不同的功能。
  2. 灵活性:可以在程序运行时动态地选择调用哪个函数。
  3. 模块化设计:使代码逻辑更加清晰、可维护。

1.4 回调函数的应用场景

  1. 排序算法:在 qsort 等函数中,用户可以自定义比较函数,实现各种排序逻辑。(下面会讲)
  2. 事件驱动的GUI程序:响应用户的点击、输入等操作。
  3. 异步任务处理:在异步操作完成时,通过回调函数通知调用者任务完成。

2. qsort 使用

qsort是C语言标准库<stdlib.h>中的一个通用排序函数,它使用快速排序算法来对数组进行排序。qsort的强大之处在于它可以用于各种数据类型的排序,包括基本数据类型和复杂的自定义数据结构。这里将探讨qsort的使用方法,并通过详细的实例演示如何对不同类型的数据进行排序。

2.1 qsort函数原型详解

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))

参数说明:

  • base:指向要排序的数组的第一个元素的指针。它是一个void指针,可以接受任何类型的数组。
  • nitems:数组中元素的个数。通常使用sizeof(array) / sizeof(array[0])来计算。
  • size:数组中每个元素的大小,以字节为单位。通常使用sizeof(array[0])来获取。
  • compar:用于比较两个元素的函数指针。这个函数决定了排序的顺序和标准。

2.2 比较函数的设计

比较函数是qsort的核心,它决定了排序的方式。比较函数的原型如下:

int compare(const void *a, const void *b)

比较函数应该遵循以下规则:

  • 如果 *a < *b,返回负数(通常是-1)
  • 如果 *a == *b,返回0
  • 如果 *a > *b,返回正数(通常是1)

注意:比较函数接收的是void指针,需要在函数内部进行适当的类型转换。

2.3 整数排序详解

下面是一个使用qsort对整数数组进行排序的详细例子:

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

// 升序排序的比较函数
int compare_ints_asc(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}

// 降序排序的比较函数
int compare_ints_desc(const void* a, const void* b) {
    return (*(int*)b - *(int*)a);
}

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);

    // 升序排序
    qsort(arr, n, sizeof(int), compare_ints_asc);

    printf("升序排序后的数组:\n");
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    printf("\n");

    // 降序排序
    qsort(arr, n, sizeof(int), compare_ints_desc);

    printf("降序排序后的数组:\n");
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    printf("\n");

    return 0;
}

在这个例子中,我们定义了两个比较函数:一个用于升序排序,另一个用于降序排序。通过更改传递给qsort的比较函数,我们可以轻松地改变排序的顺序。

2.4 字符串排序详解

对字符串数组进行排序需要特别注意,因为我们处理的是指向字符串的指针数组。以下是一个详细的例子:

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

// 升序排序的比较函数
int compare_strings_asc(const void* a, const void* b) {
    return strcmp(*(const char**)a, *(const char**)b);
}

// 降序排序的比较函数
int compare_strings_desc(const void* a, const void* b) {
    return strcmp(*(const char**)b, *(const char**)a);
}

int main() {
    const char* arr[] = {"banana", "apple", "cherry", "date", "elderberry"};
    int n = sizeof(arr) / sizeof(arr[0]);

    // 升序排序
    qsort(arr, n, sizeof(const char*), compare_strings_asc);

    printf("升序排序后的字符串数组:\n");
    for (int i = 0; i < n; i++)
        printf("%s\n", arr[i]);

    // 降序排序
    qsort(arr, n, sizeof(const char*), compare_strings_desc);

    printf("\n降序排序后的字符串数组:\n");
    for (int i = 0; i < n; i++)
        printf("%s\n", arr[i]);

    return 0;
}

在这个例子中,我们使用strcmp函数来比较字符串。注意比较函数中的双重指针:*(const char**)a 用于获取实际的字符串指针。

2.5 结构体排序详解

对结构体数组进行排序是qsort最强大的应用之一。我们可以根据结构体的不同成员进行排序。以下是一个详细的例子:

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

typedef struct {
    char name[50];
    int age;
    float height;
} Person;

// 按年龄升序排序
int compare_age_asc(const void* a, const void* b) {
    Person* personA = (Person*)a;
    Person* personB = (Person*)b;
    return personA->age - personB->age;
}

// 按身高降序排序
int compare_height_desc(const void* a, const void* b) {
    Person* personA = (Person*)a;
    Person* personB = (Person*)b;
    if (personA->height < personB->height) return 1;
    if (personA->height > personB->height) return -1;
    return 0;
}

// 按姓名字母顺序排序
int compare_name(const void* a, const void* b) {
    Person* personA = (Person*)a;
    Person* personB = (Person*)b;
    return strcmp(personA->name, personB->name);
}

int main() {
    Person people[] = {
        {"Alice", 30, 165.5},
        {"Bob", 25, 180.0},
        {"Charlie", 35, 175.5},
        {"David", 28, 170.0},
        {"Eve", 22, 160.0}
    };
    int n = sizeof(people) / sizeof(people[0]);

    // 按年龄升序排序
    qsort(people, n, sizeof(Person), compare_age_asc);
    printf("按年龄升序排序:\n");
    for (int i = 0; i < n; i++)
        printf("%s: %d岁, %.1fcm\n", people[i].name, people[i].age, people[i].height);

    // 按身高降序排序
    qsort(people, n, sizeof(Person), compare_height_desc);
    printf("\n按身高降序排序:\n");
    for (int i = 0; i < n; i++)
        printf("%s: %d岁, %.1fcm\n", people[i].name, people[i].age, people[i].height);

    // 按姓名字母顺序排序
    qsort(people, n