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

【C语言】深入理解指针(四):回调函数与qsort函数的奥秘

前言

在C语言的学习中,指针一直是一个让人又爱又恨的话题。它强大而灵活,但也容易让人陷入困惑。今天,我们就来深入探讨指针的一个重要应用——回调函数,以及基于回调函数的经典函数qsort。

一、回调函数:隐藏在背后的英雄

先来看一个实际问题。在编写代码时,我们常常会遇到一些重复的逻辑,比如在实现一个简单的计算器程序时,输入输出的操作是重复的,只有具体的计算逻辑不同。传统的解决方法是写多个函数,每个函数都包含输入输出和计算逻辑,但这样会导致代码冗余。而回调函数的出现,就是为了解决这个问题。

1.1 回调函数是什么

回调函数本质上就是一个通过函数指针调用的函数。当你把一个函数的指针(地址)作为参数传递给另一个函数时,这个指针被用来调用其所指向的函数,那么被调用的函数就被称为回调函数。它的调用不是由函数的实现方直接调用,而是在特定的事件或条件发生时由另一方调用,用于对该事件或条件进行响应。

1.2 使用回调函数改造计算器程序

一个使用回调函数改造计算器程序的例子。改造前的代码中,每个计算操作(加、减、乘、除)都需要单独写输入输出和计算逻辑,代码冗长且重复。而使用回调函数后,我们把计算逻辑的函数地址作为参数传递给一个通用的计算函数calc,这样就避免了重复代码的编写。

void calc(int(*pf)(int, int))//函数calc调用函数指针来访问对应的函数
{
    int ret = 0;
    int x, y;
    printf("输⼊操作数:");
    scanf("%d %d", &x, &y);
    ret = pf(x, y);
    printf("ret = %d\n", ret);
}

在main函数中,我们只需要根据用户的选择调用calc函数,并将对应的计算函数(如add、sub、mul、div)的地址传递给它即可。

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

这种使用回调函数的方式,不仅让代码更加简洁,而且提高了代码的可维护性和可扩展性。当我们需要添加新的计算操作时,只需要定义一个新的计算函数,并在main函数中添加对应的分支即可,而不需要修改calc函数。
完整代码:

int add(int x, int y)
{
	return x + y;

}
int sub(int x, int y)
{
	return x - y;

}
int mul(int x, int y)
{
	return x * y;

}
int div(int x, int y)
{
	return x / y;
}

void calc(int(*pf)(int, int))
{
	int ret = 0;
	int x = 0;
	int y = 0;
	printf("请输入操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}
int main()
{
	int x = 0;
	int y = 0;
	int input = 1;
	int ret = 0;
	int(*p[5])(int x, int y) = { 0,add,sub,mul,div };
	do
	{
		printf("*********************\n");
		printf("1、 add        2、sub \n");
		printf("3、 mul        4、div \n");
		printf("0、 eixt              \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;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、qsort函数:排序界的瑞士军刀

qsort函数是C语言标准库中的一个非常强大的排序函数,它使用了快速排序算法,可以对任意类型的数据进行排序。它的强大之处在于,它允许用户自定义比较函数,通过回调函数的方式,来决定数据的排序规则。

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

首先,我们需要定义一个比较函数int_cmp,这个函数接收两个const void类型的参数,分别指向要比较的两个元素。在比较函数中,我们**需要将这两个参数强制转换为int类型,然后比较它们所指向的整数值**。

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

然后,在main函数中,我们调用qsort函数,并将数组、数组的大小、每个元素的大小以及比较函数作为参数传递给它。

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 int_cmp(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);//转换为int* 类型再进行解引用操作
}
int main()
{
	int arr[] = { 1,3,5,7,9,2,4,6,8,10 };
	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;
}

在这里插入图片描述

2.2 使用qsort函数排序结构体数组

qsort函数不仅可以对基本数据类型进行排序,还可以对结构体数组进行排序。我们定义一个Stu结构体,包含学生的姓名和年龄。然后,我们定义了两个比较函数cmp_stu_by_age和cmp_stu_by_name,分别按照年龄和姓名对学生进行排序。

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

在test2和test3函数中,我们分别调用qsort函数,并将结构体数组、数组的大小、每个元素的大小以及对应的比较函数作为参数传递给它。

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

通过这种方式,我们可以非常灵活地对结构体数组进行排序,而不需要自己编写复杂的排序算法。

三、模拟实现qsort函数:深入理解回调函数与void*

在课件的最后,我们看到了一个模拟实现qsort函数的例子。这个例子不仅让我们更加深入地理解了qsort函数的工作原理,还让我们学习了如何使用回调函数和void*指针。

3.1 void*指针的作用

在C语言中,void指针是一种特殊的指针,它可以指向任意类型的数据。当我们不确定要操作的数据类型时,可以使用void指针。在模拟实现qsort函数时,我们使用void*指针来指向数组的首地址,这样就可以对任意类型的数据进行操作。

3.2 模拟实现qsort函数

在模拟实现qsort函数时,我们使用了冒泡排序算法。首先,我们定义了一个比较函数int_cmp,这个函数与前面的例子中的比较函数相同。然后,我们定义了一个_swap函数,用于交换两个元素的值。在_swap函数中,我们使用了void指针来指向要交换的两个元素,并通过强制类型转换将它们转换为char指针,然后逐字节地交换它们的值。

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

接下来,我们定义了bubble函数,这个函数实现了冒泡排序算法。在bubble函数中,我们使用了void指针来指向数组的首地址,并通过强制类型转换将它转换为char指针,然后逐个比较数组中的元素,并根据比较函数的结果交换它们的值。

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

最后,在main函数中,我们调用bubble函数,并将整型数组、数组的大小、每个元素的大小以及比较函数作为参数传递给它。

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

通过模拟实现qsort函数,我们不仅加深了对回调函数和void*指针的理解,还学习了如何使用这些知识来解决实际问题。

四、总结

今天,我们深入探讨了指针的一个重要应用——回调函数,以及基于回调函数的经典函数qsort。我们看到了回调函数如何帮助我们简化代码,提高代码的可维护性和可扩展性。我们也看到了qsort函数的强大功能,它可以对任意类型的数据进行排序,并且允许用户自定义排序规则。最后,我们通过模拟实现qsort函数,深入理解了回调函数和void*指针的作用。

希望这篇文章能帮助你更好地理解指针和回调函数,让你在C语言的学习之路上更进一步。如果你对这篇文章感兴趣,欢迎点赞、收藏和评论,你的支持是我继续创作的动力。

http://www.dtcms.com/a/107432.html

相关文章:

  • Mysql MIC高可用集群搭建
  • python的一些使用姿势
  • 【HTML】分享一个自己写的3*3拼图小游戏
  • Go语言从零构建SQL数据库(4)-解析器
  • 人工智能之数学基础:矩阵分解之LU分解
  • Stable Diffusion win10 Cpu安装日志
  • 国产三维CAD「皇冠CAD」在汽车零部件领域建模教程:刹车片
  • React-Diffing算法和key的作用
  • 【AI论文】什么、如何、何处以及效果如何?大语言模型测试时缩放技术调研
  • Python扩展知识详解:map函数
  • Pinia持久化插件pinia-plugin-persistedstate
  • Ubuntu安装psql
  • 推导Bias² + Variance + σ²_ε
  • Java项目之基于ssm的校园驿站管理系统(源码+文档)
  • GO语言学习(16)Gin后端框架
  • Linux服务器环境下如何优化环境变量
  • DeepSeek 开源的 3FS 如何?
  • 【Pandas】pandas DataFrame info
  • 高速电路中的PCB及其完整性设计
  • AI的未来演进
  • ubuntu git cola gui
  • DecodeEX 功能揭秘:什么是“复制交易”?
  • Windows 10 如何设置右击鼠标快速进行“关机”
  • Java安全 - CC1链
  • 基于Spring Boot的社区互助平台的设计与实现(LW+源码+讲解)
  • 如何批量将带有GPS信息的照片导入奥维地图:完整指南
  • 园门打印机配置教程
  • 网络安全与防护策略
  • 基于Spring Boot的实验室课程管理系统的设计与实现(LW+源码+讲解)
  • LeetCode-547. 省份数量