C语言必学之指针——从入门到入坑——part4(chapter15)

前言

欢迎各位勇者披荆斩棘,来到我们指针的第四篇(如果没看过前三篇的记得从文章最底下进入我的专栏进行阅读哦),我们的前三篇是基础篇,我们在前三篇可以说把指针中各种各样的指针基本上都介绍的差不多了,回过头来,指针也就那么些东西,也希望大家都能有所收获

当然,指针的应用还没讲完,所以第四篇进入我们指针的应用篇

废话不多说,开始我们今天的内容~

这一讲我们说一下前面提到的函数指针的一个应用

因为可能讲到这,很多小伙伴可能还有疑惑,就是函数指针到底有什么用呢?

其实函数指针可以用来实现回调函数

什么是回调函数呢?我们来往下看

一.回调函数

在说这个回调函数之前,我们先来回忆一下上一篇博客我们写过的计算器

int main()
{
	int x, y;  //两个输入数
	int input = 1; //选择计算模式
	int ret = 0;//输出的结果
	do
	{
		menu();
		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 case语句的写法中有一个地方非常冗余,经常出现下面这种形式的代码

比如这个

    printf("输入操作数:");
	scanf("%d %d", &x, &y);
	ret = add(x, y);	
	printf("ret = %d\n", ret);
    break;

还有这个

printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;

我们发现,它们除了调用的函数不同,其他的形式都一模一样,显得有些冗余

那我们能不能把这个代码进行简化呢?

我们可以这样写

封装一个函数calc(),这个函数完成加减乘除

如果我们进入case1,我们就写calc(add),再给这个函数传一个add函数,如果这个函数得到add函数的地址的话,让它通过地址去调用不就可以了吗

其他的case2,3,4也同理

那我们接下来去实现一下这个calc函数

void calc(int(*pf)(int, int))
{
	int x, y;
	int ret = 0;

	printf("输入操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}

这样我们就可以通过pf去找到我们想要的add或者sub或者mul或者div去实现计算了

主程序代码如下


 


int main()
{
	int input = 1;
	do
	{
		menu();
		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);
}

下面是运行结果

通过这个例子,我们发现像add,sub,mul,div这几个函数我们是没有直接调用的,而是在选择了1234后把对应的函数通过函数指针传给了calc()

通过函数指针调用的这些函数就是回调函数,这就引出了什么是回调函数

再说一遍,回调函数就是一个通过函数指针调用的函数

下面是文字的解释:

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

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

二.qsort使用举例

好,理解了什么是回调函数,下面我们再讲一个函数指针的使用:qsort

它其实也是对回调函数的应用

qsort是一个库函数,它要包含的头文件是stdlib,所以在使用这个函数之前要加上#include<stdlib.h>

这个函数可以完成任意类型数据的排序

1.使用qsort函数排序整型数据

我们来回忆一下,我们曾经在指针part2中讲过冒泡排序

为了将一组整数排序成 升序,我们是当时是这样写的

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz-1; i++)
	{
		
		int j = 0;
		for (j = 0; j < sz-1-i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				flag = 0;//发生交换就说明,无序
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
		
	}
}
int main()
{
	int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
    int i = 0;
	bubble_sort(arr, sz);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

这个函数有个缺陷,就是它只能排序整形数据!!!

因为我们在定义void bubble_sort 这个函数的时候用的参数就是int arr[]

所以我们要用到今天要讲的qsort函数,它就能够实现任意数据的排序

我们接着往下看

因为我们对这个函数目前并不了解,我们可以打开cplusplus这个网站(这是我们在以前的博客都有分享过的)

在这个网站,我们搜索一这个函数,我们就可以看到关于这个函数的信息了,如下图

首先看到它的原型

我们发现它有四个参数

关于这四个参数,我们可以对照这个网站下面的内容进行理解

第一个参数是base,它指向了要排序的数组的第一个元素,它的类型是void*,这使得这个函数可以排序任意类型的数据

第二个参数是num,它表示base指向的数组中的元素个数(待排序的数组的元素个数)

第三个参数是size,它表示base指向的数组中元素的大小,单位是字节(也就是1个元素有几个字节)

第四个参数(稍稍难理解一点)是compar,它是一个函数指针,这个指针指向的函数是用来比较待排序数组中的2个元素的,这个函数指针的2个参数类型是void*,void*,也就是实际上是这2个参数指向的元素进行比较

我们再看这个网站上关于qsort后面的信息

我们发现,如果compar这个函数指针的2个参数指向的元素进行比较,结果是前面的小,返回值

<0,同理如果2个一样大,返回0,如果结果是前面的大,返回值>0

看到这,可能大家对qsort的第四个参数还是不太理解,为什么我们非用它不可呢?

其实,大家可以这样想

当我们需要比较的两个元素,它们不是整型,比如说两个结构体数据,它们是不能用><=去比较的

我们看下面这个结构体

/struct Stu //学⽣
//{
//	char name[20];//名字
//	int age;//年龄
//};

我们定义了学生的名字,年龄,那我们怎么去比较呢?

所以像这种复杂的对象,我们是不能直接用用><=去比较的

我们也就不能想冒泡排序一样直接比较,如下

arr[j] > arr[j + 1]

那我们想一下,我们之前用函数指针写的计算器,我们是不是把加减乘除各个函数抽离出来,放在calc()中

这里我们能不能也将 arr[j] > arr[j + 1] 抽离出来放在qsort中呢?

所以我们用到了qsort的第四个参数函数指针,由它来提供比较的方法,问题就引刃而解了

下面我们看一下这个qsort函数到底该怎么用

根据前面对qsort各个参数的理解,我们应该可以得到下面这句代码

来替代原来的bubble_sort(arr, sz);

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

这里的第四个参数是我定义的,所以也得自己去实现

对于,第四个参数我们可以这样写

int int_cmp(const void* p1, const void* p2)//int_cmp是用来比较p1和p2指向元素的大小的
{
	return (*(int*)p1 - *(int*)p2);
}

下面使用qsort函数排序整型数据的完整代码

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;
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), int_cmp);

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

运行一下

我们发现它确实能实现升序的功能

我们用qsort排序整型数据

接下来,我们将用qsort排序结构体数据

2.使用qsort排序结构体数据

先定义一个结构体

struct Stu //学⽣
{
	char name[20];//名字
	int age;//年龄
};

然后我们创建一个数组

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

​

我们接下来要思考的就是qsort的参数怎么写才能实现排序功能

比如我们想先按照年龄去比较

经过思考我们应该能写出这样的代码

//假设按照年龄来⽐较
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}


void test1()
{
	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);
}

我们通过结构体成员的间接访问结构体指针->成员名 来比较年龄

完整的代码如下

int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}



void test1()
{
	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);
}

int main()
{
	test1();
	return 0;
}

我们调试一下看一下结果

我们发现编译器确实按照年龄进行了升序排序

那我们在来比较一下名字,也就是字符串的比较

对于字符串的比较,我们需要引入一个新的库函数,strcmp,它要包含的头文件是string,所以在使用这个函数之前要加上#include<string.h>

所以比较的时候我们不用

((struct Stu*)e1)->name - ((struct Stu*)e2)->name  //err

而是使用

strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);

剩下的同理,我们直接附上完整代码

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

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

我们调试一下看一下结果

我们发现确实按照名字进行升序排序了

通过这2个例子 ,我们明白了qsort确实可以排序任意类型的数据

最后说一点:

对于qsort函数,今天我们学习了它的使用,并用它优化了原来的冒泡排序只能比较整型数据的局限性,其实,qsort的底层逻辑是快速排序,我们将在下一篇博客进行讲解,用已经学过的知识模拟实现qsort!

结语

好了,今天的分享就到这里了

最后,希望大家点个赞或者关注吧(感谢感谢)

让我们在接下来的时间里一起成长,一起进步吧!

11d7d69969784ba2a62ccf12606261ed.jpg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不吃肉的Humble

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值