当前位置: 首页 > article >正文

深入理解回调函数:指针世界的终极挑战(完)

目录:

  • 前言
  • 一、 回调函数的概念
  • 二、 回调函数的使用
  • 三、 qsort 函数的使用和模拟实现

前言

在前几章中,我们已经深入探讨了指针的各种应用,包括字符指针、数组指针、函数指针等。今天,我们将迎来指针学习的最后一个重要知识点——回调函数。回调函数在实际编程中应用广泛,理解它将帮助你更好地掌握指针的精髓。希望你能坚持学习下去,掌握这一重要概念。

一、 回调函数的概念

回调函数是一种通过函数指针调用的函数。如果你将函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

二、 回调函数的使用

在之前的计算器实现代码中,我们设计了实现加、减、乘、除功能的函数:

#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;
}

在没有学习回调函数之前,主函数中存在大量的冗余代码,特别是 scanfprintf 函数的重复使用。

int main() {
    int x, y;
    int input = 1;
    int ret = 0;

    do {
        printf("*************************\n");
        printf(" 1:add 2:sub \n");
        printf(" 3:mul 4:div \n");
        printf(" 0:exit \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;
}

通过观察这些功能函数的结构,我们发现它们的签名都是 int 函数名(int, int) 的形式。因此,我们可以将函数的地址作为参数传递给一个函数指针,指针指向哪个函数就调用哪个函数,这就是回调函数的应用。

使用回调函数简化计算器实现的主函数代码:

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:add 2:sub \n");
        printf(" 3:mul 4:div \n");
        printf(" 0:exit \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;
}

通过这种方式,代码变得更加简洁。pf 是一个指向函数的指针,当我们选择某个功能时,会将实现该功能的函数地址传递给 pf,当条件满足时,就会通过 pf 调用该函数。

三、 qsort 函数的使用和模拟实现

qsort 函数 是 C 标准库中用于排序的函数,它使用快速排序算法。qsort 函数的原型如下:

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

参数介绍:

  • base:指向要排序的数组的第一个对象的指针。
  • num:数组中的元素数,是无符号整型。
  • size:数组中每个元素的大小(以字节为单位),是无符号整型。
  • compar:指向比较两个元素的函数的指针。此函数被重复调用以比较两个元素。

compar 函数 的返回值为 int 类型,其规则如下:

  • 返回 <0:表示 p1 指向的元素小于 p2 指向的元素。
  • 返回 =0:表示 p1 指向的元素等于 p2 指向的元素。
  • 返回 >0:表示 p1 指向的元素大于 p2 指向的元素。

qsort 函数的使用:

排序整型数据:

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

int int_cmp(const void *p1, const void *p2) {
    return (*(int *)p1 - *(int *)p2);
}

int main() {
    int arr[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};
    int i = 0;

    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);

    for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

排序结构数据:

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

struct Stu {
    char name[20];
    int age;
};
//按年龄排序
int cmp_stu_by_age(const void *e1, const void *e2) {
    return ((struct Stu *)e1)->age - ((struct Stu *)e2)->age;
}
//按名字排序
int cmp_stu_by_name(const void *e1, const void *e2) {
    return strcmp(((struct Stu *)e1)->name, ((struct Stu *)e2)->name);
}

void test2() {
    struct Stu s[] = {{"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15}};
    int sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}

void test3() {
    struct Stu s[] = {{"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15}};
    int sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}

int main() {
    test2();
    test3();
    return 0;
}

qsort 函数的模拟实现:

为了更好地理解 qsort 函数的工作原理,我们可以使用回调函数来模拟实现 qsort 函数。这里我们使用冒泡排序算法来模拟 qsort 函数的功能。

#include <stdio.h>

void my_qsort(void *base, size_t num, size_t size, int (*compar)(void *, void *)) {
    int i, j;
    char *arr = (char *)base;

    for (i = 0; i < num - 1; i++) {
        for (j = 0; j < num - i - 1; j++) {
            if (compar(arr + j * size, arr + (j + 1) * size) > 0) {
                for (int k = 0; k < size; k++) {
                    char tmp = arr[j * size + k];
                    arr[j * size + k] = arr[(j + 1) * size + k];
                    arr[(j + 1) * size + k] = tmp;
                }
            }
        }
    }
}

int int_cmp(void *p1, void *p2) {
    return *(int *)p1 - *(int *)p2;
}

int main() {
    int arr[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};
    int sz = sizeof(arr) / sizeof(arr[0]);

    my_qsort(arr, sz, sizeof(arr[0]), int_cmp);

    for (int i = 0; i < sz; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

总结:

qsort 函数是典型的回调函数的例子。通过回调函数,我们可以将比较和交换的逻辑交给用户自定义,从而实现对任意数据类型的排序。理解回调函数的工作原理,将帮助你更好地掌握指针的高级应用,提升编程能力。希望你能通过本章的学习,掌握回调函数的精髓,并在实际编程中灵活运用。


http://www.kler.cn/a/331568.html

相关文章:

  • JAVA开发时获取用户信息失败,分析后端日志信息
  • Flask内存马学习
  • 小红书关键词搜索采集 | AI改写 | 无水印下载 | 多维表格 | 采集同步飞书
  • mfc140u.dll是什么文件?如何解决mfc140u.dll丢失的相关问题
  • 排序算法(3)——归并排序、计数排序
  • 记一次mysql故障排除和硬盘扩容
  • CSS内边距
  • ES索引备份
  • 关于建表字段是否该使用 `NOT NULL` 的问题,你怎么看?
  • ubuntu命令行连接wifi
  • Hive数仓操作(十二)
  • C++ 语言特性13 - 强枚举类型
  • IP 数据包分包组包
  • mit6824-01-MapReduce详解
  • 解决 TypeError: Expected state_dict to be dict-like, got <class ‘*‘>.
  • 在 Ubuntu 下通过 Docker 部署 NAS 服务器
  • 损失函数篇 | YOLOv5 引入Unified-IoU 高质量目标检测IoU损失
  • Vue3项目开发——新闻发布管理系统(九)(完结篇)
  • 项目-坦克大战学习-资源冲突解决
  • 算法 | 鹈鹕算法POA-Transformer-LSTM多变量回归预测
  • redis 5的安装及启动(window)
  • csapp_计算机系统通览
  • 数据校验的总结
  • 《开源大模型食用指南》,一杯奶茶速通大模型!新增Examples最佳实践!
  • 【pytorch】pytorch入门5:最大池化层(Pooling layers )
  • SSY20241002提高组T4题解__纯数论