指针进阶:qsort函数(回调函数)

Posted 再吃一个橘子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了指针进阶:qsort函数(回调函数)相关的知识,希望对你有一定的参考价值。

 回调函数

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

如上述代码1就是回调函数:

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
int Sub(int x, int y)//int (*)(int , int)类型
{
	return x - y;
}
int Mul(int x, int y)//int (*)(int , int)类型
{
	return x * y;
}
int Div(int x, int y)//int (*)(int , int)类型
{
	return x / y;
}
void menu()
{
	printf("*************************\\n");
	printf("****  1:add    2:sub ****\\n");
	printf("****  3:mul    4:div ****\\n");
	printf("****      0:exit     ****\\n");
	printf("*************************\\n");
}
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);
}
int main()
{
	int input = 1;
	do
	{
		menu();
		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;
}

9.1回调函数典型举例

冒泡排序

#include<stdio.h>
void bubble(int a[], int len)
{
	int i, j;
	for (i = 0; i < len - 1; i++)
	{
		for (j = 0; j < len - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				int tmp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = tmp;
			}
		}
	}
}
void print(int a[], int len)
{
	int i = 0;
	for (i = 0; i < len ; i++)
	{
		printf("%d ", a[i]);
	}
}
int main()
{
	int a[] = { 3,2,5,1,7,8,4,0,9,6 };
	int len = sizeof(a) / sizeof(a[0]);

	bubble(a, len);

	print(a, len);

	return 0;
}

但是我们发现。冒泡排序用void bubble(int a[], int len)把接受的数组类型“写死了”,这个是整型排序,那我想对浮点型排序呢?怎么调用这个bubble函数?

所以,我们需要一个接受任何类型的函数来实现排序  ——   qsort()函数。

先说用法:qsort()函数形式:

void qsort( void *base, size_t num, size_t width, int *  compare )(const void *elem1, const void *elem2 ) );

void qsort(void* base, 
	       size_t num,     //待排序的元素个数
	       size_t width,   //一个元素的大小(单位是字节)
	       int (*cmp)(const void* elem1, const void* elem2));   //cmp指向的是:排序时,用来比较2个元素的函数

解释

  1. 因为我们不知道传进来的是什么类型,所以用“万能型”类型void来接收,即:void *base,看代码1举例
  2. 传进来的数组,我们需要知道要排序的长度,所以 size_t num,既然不知道传进来的是什么类型,那么我们需要对这个传进来的数组,说明一个元素所占大小(字节),即: size_t width这样我们就相当于知道了数据类型。
  3. 需要指定要排序的是什么,比如:一个结构体我们如何排序?——>  需要指定结构体中的哪一个来进行排序,所以写了函数指针int *  compare )(const void *elem1, const void *elem2 ) )。

代码1

    int a = 110;
 	float b = 0.7f;

	//错误示范
	char* p1 = &a;  //会报错,应该是int*  
	char* p2 = &b;  //会报错,应该是float* 

	//正确示范
	void* p3 = &a;
	void* p4 = &b;

	//对比

	//void*无具体类型的指针
	//能够接收任意类型的地址
	//缺点:不能加减运算,不能解引用(因为不知道所占字节)

	//加减运算正确示范
	int* p5 = &a;
	p5++;
	p5 + 1;

	//缺点
	void* p6 = &a;
	p6++;//报错
	p6 + 1;//报错
	*p6;//报错

用qsort()函数来改写排序

#include<stdlib.h>
void print(int a[], int len)
{
	int i = 0;
	for (i = 0; i < len; i++)
	{
		printf("%d ", a[i]);
	}
}

//比较函数
int cmp(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

int main()
{
	int a[] = { 1,4,2,3,6,8,0,9,5,7 };
	int len = sizeof(a) / sizeof(a[0]);

	qsort(a, len, sizeof(a[0]), cmp);

	print(a, len);
	return 0;
}

解释

  1. qsort(a, len, sizeof(a[0]), cmp);前面三个参数按要求写即可,最后一个是比较函数,用来指定比较的对象,需要自己来编写这个函数。
  2. int cmp(const void* e1, const void* e2)来定义返回值类型int(  稍会说明为什么是int类型 ),还有参数const void* e1, const void* e2。
  3. return *(int*)e1 - *(int*)e2;显得较为复杂,其实e1,e2指的是地址,我们想要通过解引用的方式来获取该地址的数据,但是void*类型的缺点是不能解引用,所以我们强制转换成int*类型再去解引用。为什么是减号➖呢??见下表(    ps.参考MSDN关于函数qsort的解释  )

解释解释的解释

(1)返回值是int类型是因为我们返回值对应的是和整数0比较大小

(2) 参考MSDN关于函数qsort的解释

 参考MSDN关于函数qsort的解释

 当然,想要实现降序排列即转换cmp返回值:

//比较函数
int cmp(const void* e1, const void* e2)
{
	return *(int*)e2 - *(int*)e1;
}

可见,在函数qsort()函数中,使用了比较函数cmp,非常非常典型的回调函数函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数

那么如何用qsort函数来实现结构体的排序呢?(就不打印了哈,知道如何用即可

#include<stdlib.h>
#include<string.h>

struct Stu
{
	char name[20];
	int age;
};

//比较名字
int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);//字符串比较大小,即:判断字符串的长短,用strcmp函数,详情见strcmp函数应用
}

//比较年龄
int cmp_by_age(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
void test1()
{
	struct Stu a[3] = { {"路飞",3},{"索隆",6},{"山治",8} };
	int len = sizeof(a) / sizeof(a[0]);

	qsort(a, len, sizeof(a[0]), cmp_by_name);

}

void test2()
{
	struct Stu a[3] = { {"路飞",3},{"索隆",6},{"山治",8} };
	int len = sizeof(a) / sizeof(a[0]);

	qsort(a, len, sizeof(a[0]), cmp_by_age);

}
int main()
{
	//按名字排序
	test1();

	//按年龄排序
	test2();

	return 0;

}

strcmp函数用法:

那么,我们可以   使用回调函数,实现一个

9.2通用的冒泡排序

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//打印
void print(int a[], int len)
{
	int i = 0;
	for (i = 0; i < len; i++)
	{
		printf("%d ", a[i]);
	}
}

//比较函数
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

//交换
void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

//通用的冒泡排序
void BubbleSort(void* base, size_t num, size_t width, int (*cmp)(const void* e1, const void* e2))
{
	int i, j;
	for (i = 0; i < num - 1; i++)
	{
		for (j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			} 
		}
	}
}

//主函数
int main()
{
	int a[] = { 3,2,5,1,7,8,4,0,9,6 };
	int len = sizeof(a) / sizeof(a[0]);

	BubbleSort(a, len, sizeof(a[0]), cmp_int);

	print(a, len);

	return 0;
}

解释

  1. void BubbleSort(void* base, size_t num, size_t width, int (*cmp)(const void* e1, const void* e2))作为通用的冒泡排序
  2. if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)之中,用回调函数cmp来通过其返回值判断前后数据大小(稍会要在上面写下cmp函数),那我们需要把什么参数传到cmp函数来判断前后数据大小呢?——>  肯定是前面一个数据的内容,和后面一个数据的内容比较,用因为是void*类型,不能解引用所以应该用强制类型转换,那么用什么类型转换??这是个通用的冒泡排序,是不可以随便用一个数据类型强转的呀!——>  用char*类型比较合适,因为char*只一次占一个字节大小(更细),所以(char*)base,通过和数据类型的所占字节长度width观察得到下一位数据,所以写出上述表达式。
  3. void Swap(char* buf1, char* buf2, int width)交换函数,(  假设我们想要冒泡完成升序。。如果前一位大于后一位,则调用Swap函数实现前后交换  ),当然需要传入前后两个数据的地址,当然,还必须得有数据类型所占字节数(  方便移动到下一个数据的位置上去  ),具体见下图实现过程:

 

以上是关于指针进阶:qsort函数(回调函数)的主要内容,如果未能解决你的问题,请参考以下文章

C*指针进阶

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针

指针的这些知识你知道吗?C语言超硬核指针进阶版3w+字详解+指针笔试题画图+文字详细讲解