深入理解指针与回调函数:从基础到实践

引言

在C语言中,指针和回调函数是两个非常重要的概念。指针为我们提供了直接操作内存的能力,而回调函数则为我们提供了一种灵活的编程方式,使得我们可以将函数作为参数传递给其他函数,从而实现更加模块化和可复用的代码。本文将深入探讨指针和回调函数的概念,并通过实际的代码示例来展示它们的应用。

1. 回调函数是什么?

1.1 回调函数的定义

回调函数(Callback Function)是一种通过函数指针调用的函数。简单来说,回调函数是一个在特定事件或条件发生时被调用的函数。回调函数不是由该函数的实现方直接调用,而是通过函数指针传递给另一个函数,由该函数在适当的时机调用。

1.2 回调函数的作用

回调函数的主要作用是解耦代码。通过将函数作为参数传递,我们可以将代码的逻辑分离,使得代码更加模块化和可复用。例如,在一个计算器程序中,我们可以将加法、减法、乘法、除法等操作封装成不同的函数,然后通过回调函数的方式将这些函数传递给一个统一的处理函数,从而避免重复的代码。

1.3 回调函数的示例

让我们通过一个简单的例子来理解回调函数的使用。假设我们有一个计算器程序,用户可以选择不同的操作(加法、减法、乘法、除法),程序会根据用户的选择执行相应的操作。

1.3.1 使用回调函数改造前
cs 复制代码
#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:add \n");
        printf(" 2:sub \n");
        printf(" 3:mul \n");
        printf(" 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;
}

在这个代码中,我们使用了switch语句来根据用户的选择调用不同的函数。虽然这个代码可以正常工作,但是它存在一个问题:每次添加一个新的操作时,我们都需要修改switch语句,这会导致代码的冗余和不易维护。

1.3.2 使用回调函数改造后

为了简化代码,我们可以使用回调函数的方式将不同的操作函数作为参数传递给一个统一的处理函数。这样,当我们添加新的操作时,只需要添加新的函数,而不需要修改switch语句。

cs 复制代码
#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 calculate(int (*func)(int, int)) {
    int x, y;
    printf("输入操作数:");
    scanf("%d %d", &x, &y);
    int ret = func(x, y);
    printf("ret = %d\n", ret);
}

int main() {
    int input = 1;

    do {
        printf("**********\n");
        printf(" 1:add \n");
        printf(" 2:sub \n");
        printf(" 3:mul \n");
        printf(" 4:div \n");
        printf(" 0:exit \n");
        printf("**********\n");
        printf("请选择:");
        scanf("%d", &input);

        switch (input) {
            case 1:
                calculate(add);
                break;
            case 2:
                calculate(sub);
                break;
            case 3:
                calculate(mul);
                break;
            case 4:
                calculate(div);
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误\n");
                break;
        }
    } while (input);

    return 0;
}

在这个改造后的代码中,我们定义了一个calculate函数,它接受一个函数指针作为参数。这个函数指针指向一个接受两个int参数并返回int的函数。在main函数中,我们根据用户的选择将不同的操作函数传递给calculate函数,从而实现了代码的简化和模块化。

2. qsort使用举例

2.1 qsort函数简介

qsort是C标准库中的一个函数,用于对数组进行排序。它的原型如下:

cs 复制代码
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

base:指向要排序的数组的第一个元素的指针。

nmemb:数组中元素的个数。

size:数组中每个元素的大小(以字节为单位)。

compar:指向比较函数的指针。这个函数用于比较两个元素的大小。

2.2 使用qsort函数排序整型数据

让我们通过一个简单的例子来演示如何使用qsort函数对整型数组进行排序。

cs 复制代码
#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;
}

在这个例子中,我们定义了一个int_cmp函数,用于比较两个整型数的大小。然后我们使用qsort函数对数组arr进行排序。qsort函数会根据int_cmp函数的返回值来决定元素的顺序。

2.3 使用qsort排序结构数据

qsort函数不仅可以用于排序基本数据类型,还可以用于排序结构体数据。让我们通过一个例子来演示如何使用qsort函数对结构体数组进行排序。

cs 复制代码
#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);

    for (int i = 0; i < sz; i++) {
        printf("%s %d\n", s[i].name, s[i].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);

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

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

在这个例子中,我们定义了一个Stu结构体,包含学生的姓名和年龄。我们定义了两个比较函数cmp_stu_by_age和cmp_stu_by_name,分别用于按照年龄和姓名对学生进行排序。然后我们使用qsort函数对结构体数组进行排序,并输出排序后的结果。

3. qsort函数的模拟实现

3.1 冒泡排序简介

冒泡排序是一种简单的排序算法。它重复地遍历要排序的数组,比较相邻的两个元素,如果它们的顺序错误就交换它们。遍历数组的工作会重复进行,直到没有需要交换的元素为止。

3.2 使用回调函数模拟实现qsort

我们可以使用冒泡排序的思想来模拟实现qsort函数。为了支持不同类型的数组,我们需要使用void *指针和回调函数。

cs 复制代码
#include <stdio.h>

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

void _swap(void *p1, void *p2, int size) {
    int i = 0;
    for (i = 0; i < size; i++) {
        char tmp = *((char *)p1 + i);
        *((char *)p1 + i) = *((char *)p2 + i);
        *((char *)p2 + i) = tmp;
    }
}

void bubble(void *base, int count, int size, int (*cmp)(void *, void *)) {
    int i = 0;
    int j = 0;
    for (i = 0; i < count - 1; i++) {
        for (j = 0; j < count - i - 1; j++) {
            if (cmp((char *)base + j * size, (char *)base + (j + 1) * size) > 0) {
                _swap((char *)base + j * size, (char *)base + (j + 1) * size, size);
            }
        }
    }
}

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

    bubble(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;
}

在这个代码中,我们定义了一个bubble函数,它接受一个void *指针作为数组的基地址,数组的元素个数,每个元素的大小,以及一个比较函数。bubble函数使用冒泡排序算法对数组进行排序。为了支持不同类型的数组,我们使用void *指针和_swap函数来交换元素。

4. 总结

通过本文的学习,我们深入理解了指针和回调函数的概念,并通过实际的代码示例展示了它们的应用。回调函数为我们提供了一种灵活的编程方式,使得我们可以将函数作为参数传递给其他函数,从而实现更加模块化和可复用的代码。qsort函数是C标准库中的一个强大的排序函数,它通过回调函数的方式支持对不同类型的数据进行排序。我们还通过模拟实现qsort函数,进一步加深了对指针和回调函数的理解。

相关推荐
Jason_Orton13 分钟前
决策树(Decision Tree):机器学习中的经典算法
人工智能·算法·决策树·随机森林·机器学习
Kurbaneli17 分钟前
微软平台下 C 语言:编程世界的闪耀基石
c语言·单片机·microsoft
Nathan2024061622 分钟前
数据结构 - LinkedHashMap(二)
android·数据结构·面试
什么半岛铁盒23 分钟前
操作系统:计算机架构里的幕后指挥官
linux·c语言·centos·编辑器
筏.k27 分钟前
动态规划之 “完全背包“ ------P8646 [蓝桥杯 2017 省 AB] 包子凑数
算法·蓝桥杯·动态规划
星空露珠1 小时前
飞机大战lua迷你世界脚本
数据结构·游戏·lua
clownAdam1 小时前
通俗易懂的分类算法之支持向量机详解
算法·支持向量机·分类·数据挖掘
飞3001 小时前
淘天集团算法岗-计算机视觉(T-Star Lab)内推
人工智能·算法·计算机视觉·业界资讯
apcipot_rain1 小时前
【密码学——基础理论与应用】李子臣编著 第二章 古典密码 课后习题
算法·网络安全·密码学
练习&两年半2 小时前
C语言:51单片机 程序设计基础
c语言·开发语言·单片机·51单片机