深入理解指针(七)

1. 回调函数是什么?

回调函数就是⼀个通过函数指针调用的函数。 如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数 时,被调用的函数就是回调函数。

简而言之:把一个函数当作参数传给另一个函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

例如我们设计一个加减乘除的小程序,如图。

代码如下:

perl 复制代码
#define _CRT_SECURE_NO_WARNINGS
#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           2:sub\n");
        printf("  3:mul           4: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;
}

那现在我们要把这段代码改为含有回调函数的代码,那就需要增加一个函数,代码如下:

arduino 复制代码
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);
}

同时switch case也需要稍作修改,这里不再写出来。

2.qsort使用举例

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

下面这段代码核心思想是:排序算法由库实现,而比较规则由用户通过"回调函数"提供。

c 复制代码
#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数

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 两个元素谁大谁小。它的参数类型是 const void*,这是为了让 qsort 能适配任意类型的数据。在函数内部,把这两个指针强制转换成 int*,再解引用得到整数值,通过相减返回结果:正数表示第一个比第二个大,负数表示第一个比第二个小,0 表示相等。

接着在 main 函数中,调用 qsort() 进行排序,其中第一个参数是数组首地址,第二个参数是元素个数,第三个参数是每个元素的大小,第四个参数是刚刚写好的比较函数指针。qsort 在排序过程中会反复调用 int_cmp 来比较数组中的元素大小,从而完成排序。排序结束后,通过 for 循环依次打印数组内容,可以看到输出结果是从小到大的有序序列。

2.2 使用qsort排序结构数据

c 复制代码
#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 print(struct Stu s[], int sz)
{
    for (int i = 0; i < sz; i++)
    {
        printf("%s %d\n", s[i].name, s[i].age);
    }
    printf("--------\n");
}

// 按年龄排序
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);
    print(s, sz);
}

// 按名字排序
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);
    print(s, sz);
}

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

首先,struct Stu 定义了一个学生结构体,每个学生有两个成员:name 表示姓名,age 表示年龄。之后我们对学生数组进行排序,其实就是对这个结构体数组排序。

cmp_stu_by_age 是一个比较函数,用来按照年龄大小比较两个学生。参数是 const void*,这是因为 qsort 要支持任意类型数据。函数内部把它们强制转换成 struct Stu*,然后访问 age 成员,相减得到结果,返回值规则是:正数表示第一个大,负数表示第二个大,0 表示相等。

3. qsort函数的模拟实现

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

这段代码使用冒泡排序实现,同时体现了回调函数的机制。

首先定义了一个比较函数 int_cmp,它接收两个 void* 类型的指针,将其转换为 int* 后进行解引用,得到整数值并相减,从而确定两个元素的大小关系。这里的返回值约定是正数表示第一个元素大于第二个,负数表示第一个元素小于第二个,0 表示相等。接着定义了 _swap 函数,用于交换任意类型的两个元素。通过将指针转换为 char*,按字节逐个交换。

核心的排序逻辑在 bubble 函数中实现。它接收数组首地址、元素数量、元素大小以及一个函数指针 cmp 作为参数。内部采用双重循环完成冒泡排序,每次比较相邻两个元素时,调用 cmp 函数判断顺序,如果前一个元素大于后一个,就调用 _swap 交换它们的位置。这里的 cmp 函数就是回调函数,它由使用者提供。

main 函数中,定义了一个整型数组,并调用 bubble 进行排序,同时传入 int_cmp 作为回调函数。排序完成后通过循环打印数组元素,结果从小到大排列。

相关推荐
渔阳节度使12 小时前
SpringAI实时监控+观测性
后端·python·flask
Victor35613 小时前
MongoDB(42)如何使用$project阶段?
后端
Victor35613 小时前
MongoDB(43)什么是嵌入式文档?
后端
djarmy13 小时前
ubuntu20.04搭建openharmony6.0的master分支。构建编译环境报错解决记录
c语言·ubuntu
Darkdreams13 小时前
SpringBoot项目集成ONLYOFFICE
java·spring boot·后端
bropro13 小时前
【Spring Boot】Spring AOP中的环绕通知
spring boot·后端·spring
lhbian13 小时前
【Spring Cloud Alibaba】基于Spring Boot 3.x 搭建教程
java·spring boot·后端
IT_陈寒14 小时前
Redis性能提升3倍的5个冷门技巧,90%开发者都不知道!
前端·人工智能·后端
LucianaiB14 小时前
OpenClaw 安装后必看!你真的会科学养虾吗?第1天和第47天的Openclaw有什么区别?
后端
寻见90315 小时前
智能体开发_07Function Calling道法术器拆解,一文搞懂大模型如何“做事”
人工智能·后端·ai编程