深入理解指针(七)

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 作为回调函数。排序完成后通过循环打印数组元素,结果从小到大排列。

相关推荐
ServBay1 小时前
彻底重绘Spring Boot性能版图,资源占用缩减80%
java·spring boot·后端
序安InToo2 小时前
第3课|第一个Ada程序:Hello World深入解析
后端·操作系统·嵌入式
Java编程爱好者2 小时前
为什么 Spring 和 IDEA 都不推荐使用 @Autowired 注解??
后端
Java编程爱好者3 小时前
Spring Boot + JPackage:构建独立安装包!
后端
CS生3 小时前
Rust环境准备
开发语言·后端·rust
神奇小汤圆3 小时前
效率拉爆:IntelliJ IDEA 中的这几款 AI 编程插件你都用过吗?
后端
神奇小汤圆3 小时前
都2026年了,还在用Xshell?试试这几款现代化的终端工具吧!
后端
understandme4 小时前
记录 redis 端口的坑,高并发请求常见的问题分类
后端