【C语言】指针深入讲解(下)

前言

今天我们来学习指针最后一个知识点回调函数,这个知识点也很重要,希望大家能坚持学习下去。
没学习之前指针知识内容的,可以点击这里进行学习。

回调函数

回调函数的概念

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

回调函数的使用

在指针深入讲解(中)篇中我们写的计算机的实现的代码中。
我们设计了实现功能的函数。

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

在没学习回调函数之前,主函数中有很多冗余的地方。
下面代码,scanf和printf函数大量重复出现。

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

我们观察功能实现的函数可以发现,函数的结构都是 int 函数名 (int,int) 的形式,这样就可以把函数的地址作为参数传给这样类型的函数指针,指针指向那个函数就调用那个函数,这就是使用的回调函数

用回调函数的形式,简化计算器实现的主函数代码。

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);
}
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(" 0:exit \n");
	printf("*************************\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;
}

这样代码就简化了很多,pf是指向函数的指针,当我们选择哪个功能时,会把实现该功能的函数地址传给pf指针,当条件满足,就会通过pf调用该函数。

qsort函数的使用和模拟实现

qsort函数的介绍

在这里插入图片描述

参数介绍

参数名含义
base指向要排序的数组的第一个对象的指针
num指向的数组中的元素数。是无符号整型,size_t
size数组中每个元素的大小(以字节为单位)。是无符号整型。size_t
compar指向比较两个元素的函数的指针。此函数被重复调用以比较两个元素。

最后一个参数很重要单独说明一下
在这里插入图片描述
compar是指向比较函数的指针,返回值为int型。
返回 <0说明p1指向的元素小于p2指向的元素。
返回 =0说明p1指向的元素等于p2指向的元素。
返回 >0说明p1指向的元素大于p2指向的元素。

qsort函数的使用

使⽤qsort函数排序整型数据

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

使⽤qsort排序结构数据

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;
}
//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的
//假设按照名字来⽐较
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);
}
//按照名字来排序
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);
}
int main()
{
 test2();
 test3();
 return 0;
}

qsort函数模拟实现

用回调函数,模拟实现qsort函数。
qsost底层采用的是快速排序的方法,在这里我们使用更简单的冒泡排序的排序算法来模拟实现qsort函数,快排会在学习数据结构时讲解。
我们要实现的qsort是可以针对任何数据进行排序,那想一下我们知道用户使用这个函数的时候是拿来排序什么数据吗?显然是不知道的,所以在内部实现时,我们需要更改什么呢?分析如下:

  1. 比较的方法
    由于不知道用户排序的数据类型,传过来的数组首元素地址我们必须使用void*指针接收,是不能进行解引用的,且数据类型是不能传参的,那我们该怎么找到相邻元素比较呢?
    于是我们在参数中添加了数组元素的大小(即宽度,一个元素占几个字节,这是用户可以传参的),这样就能找到相邻元素了
    通过首元素地址加减上一个数组元素占几个字节就可以找到相邻元素。
    公式为:元素地址=首元素地址+数组元素的宽度 * 数组元素下标
    在这里插入图片描述

注意:要把base强制类型转化为char型指针

接下来就是如何比较,由于我们不知道用户排序什么数据,所以没办法实现两个数据的比较,例如整数可以直接使用关系操作符,而字符串需要strcmp函数等等,于是我们把比较两个数据大小的函数交给用户去实现,所以在参数中使用了一个函数指针。

if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)

这里我们默认还是qsort的比较规则,用户实现compare函数时遵守:当第一个元素大于第二个元素时,就返回大于0的数字,此时我们交换,按这个规则排序出来为升序,反之为降序。

2.交换数据的方式
同样的是,我们不知道数据类型,但我们知道数据的大小,所以我们可以一个一个字节的交换

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

这样我们就完成了qsort函数的模拟实现。
源代码如下(排序整数):

#include<stdio.h>
void my_squrt(void* base, size_t num, size_t size, int (*compar)(void* p1, void* p2))
{
	int i = 0;
	int j = 1;
	for (i = 0; i < num - 1; i++)
	{
		for (j = 0; j < num - i-1; j++)
		{
		//判断大小
			if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
			//交换数据
				for (int k = 0; k < size; k++)
				{
					char tmp = *((char*)base + j * size + k);
					*((char*)base + j * size + k) = *((char*)base + (j + 1) * size + k);
					*((char*)base + (j + 1) * size + k) = tmp;
				}
			}
		}
	}
}
//整数的比较函数
int int_cmp(void* p1, void* p2)
{
	return *((int*)p2) - *((int*)p1);
}

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; \
		int sz = sizeof(arr) / sizeof(arr[0]);
	my_squrt(arr,sz,sizeof(arr[0]), int_cmp);
	int i = 0;
	//打印
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

总结:
qsout函数是典型的回调函数的例子,排序时我们不知道用户传进来的数据是什么类型,所以使用void *,把比较函数的实现方法交给用户实现,把实现函数通过函数指针传给qsout函数,在qsout函数内部比较时调用该函数。这就是回调函数。


感谢大家的观看, 大家可以在评论区留言,你们的支持就是我最大的动力。
评论 36
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值