【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看

作者:小树苗渴望变成参天大树
作者宣言:认真写好每一篇博客
作者gitee:link
【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第1张图片如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

qsort

  • 前言
  • ✨一、什么是回调函数?
  • 二、qsort函数
    • 2.1冒泡排序的思想
    • 2.2qsort函数原型
    • 2.3qsort函数的整型实例
    • 2.4qsort函数的结构体类型实例
      • 2.4.1对结构体年龄进行排序
      • 2.4.2对结构体姓名进行排序
    • 2.5qsort函数的浮点型实例
  • 三、模拟实现qsort函数
    • 3.1比较函数的写法
    • ❤️‍3.2交换函数的写法
    • 3.3完整模拟的代码
  • 四、总结


前言

各位友友们,一日不见,如隔三秋,我们又见面了,我又可以给你们分享新的知识了,今天我们讲的小的知识点是回调函数,大的知识点是怎么模拟是实现qsort函数,并且里面的细节。话不多说,我们开始进入正文。

✨一、什么是回调函数?

我们先来看一下它的概念:

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

很显然你们还是不太明白,好我们用例子来解释一下。

我们在上一篇博客种写过计算器这个代码,我们第一种是使用case语句写的
【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第2张图片
我们看到每个case语句里面有许多重复的语句,那我们能不能把这些相同的代码包装成一个函数呢??答案是可以的。我们写一个cacl()函数

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

我们这样来包装函数,每个case语句来调用这个函数,但是这样的话就写死了,每个case只能实现加法功能,那我们怎么修改呢??这个时候就要使用回调函数,我们把每个功能的函数传给cacl函数不就行了,用函数指针接收就可以了.

void cacl(int(*p)(int,int))
{
	 printf( "输入操作数:" );
     scanf( "%d %d", &x, &y);
     ret = p(x, y);
     printf( "ret = %d\n", ret);
}
case 1:
	cacl(add);
	break;

这样是不是就可以是实现对应的功能了,不会出现写死的情况。而且add的参数是由cacl内部去自动实现传参的,和接下来的qsort函数有关系,我们先不急。那回调函数和qsort函数又什么联系呢??qsort函数又是干什么呢?跟随我的脚步来看。

二、qsort函数

2.1冒泡排序的思想

qsort是一个库函数,用来实现任意数组的排序,我们先来看看我们比较熟悉的一种排序算法–冒泡排序
【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第3张图片
【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第4张图片

有没有发现一个问题,我们这个冒泡排序只能实现整型的排序,把想要排的类型给写死了,我们不能排字符型,结构体类型,浮点型等等。所以有的牛人就写了一个可以排任意类型数的一个库函数叫做qsort,让我们具体来看看这个函数是怎么使用的


2.2qsort函数原型

【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第5张图片
【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第6张图片
我们介绍了qsort里面每一个参数代表着什么,在补充一个小的知识点我们在函数传参的时候,有一个万能的接收类型,那就是空类型,这个类型可以接收任何类型的参数,但是有一个缺点,不能直接进行解引用或者++操作,需要先转换成其他类型才可以操作
【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第7张图片


我们知道了前三个参数具体是什么了,那第四个参数呢??他是一个函数指针,指向一个函数,该函数是一个比较函数,这个函数是需要我们使用者自己写的,因为不管是什么排序,都少不了元素的比较,但是每个元素的类型牛人一开始是不知道的,所以把涉及元素的比较想包装成一个函数让使用者自己写,qsort里面的算法思想牛人已经写死,因为和类型的不同没有关系。那我们这个比较函数我们自己应该怎么去写呢??我们来看看这个函数的原型

【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第8张图片
我们看到两个参数是指针,所以qsort内部传给这个函数应该是两个比较元素的地址,我先把代码写出来,以整型为例来分析:

int cmp_int(const void* e1, const void* e2)//整形排序
{
	return  *(int*)e1 - *(int*)e2;
}

一.我们把两个待比较元素的地址传过来,用void*类型接收,我们想要解引用必须强制类型转换我们需要的类型,这个需要的类型我们使用者肯定发是知道的所以先 (int * )e1进行强制类型转换, 在 *(int * )e1进行解引用得到数据,相减就能比较出谁大谁小。

【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第9张图片

二.我们来思考,如果返回大于0的数据,说明e1要大于e2,我们来回想刚才介绍的冒泡排序,如果前一个数大于后一个数就交换,那我们是不是就可以说如果我们判断的结果(即为比较函数返回来的结果)大于0,进行交换的话就是排升序,则反之。我们可以先把我们刚才写的冒泡排序当成qsort库函数,假设qsort内部就是冒泡排序的思想实现的,只是把比较两个元素的大小,单独写成了一个函数,根据回调函数的特点,在qsort函数里面调用这个比较函数。

2.3qsort函数的整型实例

我们具体来看看怎么使用:

int int_cmp(const void * p1, const void * p2)
{
  return (*( int *)p1 - *(int *) p2);
}
void test1()
{
	 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");
}
int main()
{
   test1();
    return 0;
}

【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第10张图片
我们来看结果是怎么样的通过这个例子,我们再来讲结构体数组和浮点型来进行一下排序,

2.4qsort函数的结构体类型实例

struct Stu
{
	char name[20];
	int age;
};
void test2()
{
	struct Stu s[3] = { {"zhangsan",20},{"lisi",10},{"wangwu",30} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_age);//对年龄
	qsort(s, sz, sizeof(s[0]), cmp_name);//对名字
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", s[i]);
	}
	}

我们看到对于结构体我们这个结构体类型有两个元素,所以我们可以对名字进行排序,也可以对年龄进行排序

2.4.1对结构体年龄进行排序

我们需要自己写比较函数

int cmp_age(const void* e1, const void* e2)//对年龄的比较
{
	return  ((struct Stu*)e1)->age -((struct Stu*)e2)->age;//数字的比较
}

->这个就是访问到结构体里面的元素,也可以写成这样
return ((struct Stu)e1).age- ((struct Stu)e2).age;
我们来看运行结果:应该是10,20,30
【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第11张图片
我们可以看到按照年龄的升序进行排序的。

2.4.2对结构体姓名进行排序

这个比较函数还是我们自己来写

int cmp_name(const void* e1, const void* e2)//对名字的比较
{
	return  strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);//字符串的比较
}

这里我们注意这是字符串比较,我们必须使用strcmp进行比较,那strcmp实际是按照对应位进行比较,第一次遇到不同的就比较对应的字符,来表示字符串的大小。这里牛人设计的非常巧妙,把strcmp的返回值和比较函数的返回值一一对应起来了。

我们来看运行结果:应该是:lisi,wangwu,zhangsan
【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第12张图片
名字也按照升序的方式进行排序了

2.5qsort函数的浮点型实例

int cmp_float(const void*e1, const void*e2)//浮点型排序
{
//第一种写法
	return ((int)(*(float*)e1 - *(float*)e2));//因为返回是int,要强制类型转换;
	//第二种写法
	if (*(float*)e1 == *(float*)e2)//解引用的类型要改成float
		return 0;
	else if (*(float*)e1 > *(float*)e2)
		return 1;
	else
		return -1;
}
void test3()
{
	float f[] = { 10.0,9.0,8.0,7.0,6.0,5.0,4.0};
	int sz = sizeof(f) / sizeof(f[0]);
	qsort(f, sz, sizeof(f[0]), cmp_float);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%f ",f[i]);
	}
}

因为是浮点型的比较,计算出来也为浮点型,所以要强制类型转换成整型在返回出来,我们来看看看运行结果:
【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第13张图片
我们可以看到也是可以实现的,那我们看到这里有没有觉得牛人很厉害,没关系,我会把你变得更牛人一样的厉害,接下来我就讲怎么自己模拟是实现一个qsort函数。

三、模拟实现qsort函数

我们知道,不管是哪一种排序算法,都是将一些乱序的数排成一组有序的数,那有这么多的排序算法的原因就是每种算法的效率不一样,牛人是使用快排的算法思想,但我们今天不使用快排的算法来模拟是实现,因为还要花好长时间讲快排的思想,所以我们今天就使用冒泡排序的思想来是实现这个函数功能。
我们在文章开头就介绍了冒泡排序,,我们来看图片描述:
【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第14张图片

3.1比较函数的写法

那我们怎么实现比较函数的传参呢??

首先我们传过去的是两个元素的地址,我们通过指针来往后面走来获取每个元素地址即可。
【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第15张图片我们能根据这个图的解释可以把比较函数的参数写成这样:

cmp((char*)base + j * width, (char*)base + (j + 1) * width)

我们类比不管什么类型的参数,这个传参的方式都可以得到两个相邻元素的地址。接下来我们来写交换函数。

❤️‍3.2交换函数的写法

【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第16张图片
根据这个图我们来写出下面的代码:

void _swap(void *e1, void *ee2, int width)
{
    int i = 0;
    for (i = 0; i< width; i++)
   {
        char tmp = *((char *)e1 + i);
       *(( char *)e1 + i) = *((char *) e2 + i);
       *(( char *)e2 + i) = tmp;
   }
}
//优化后的代码
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++;
	}
}

我们的交换函数就写好了。

3.3完整模拟的代码

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++;
	}
}
//实现bubble_sort函数的程序员,他是否知道未来排序的数据类型-不知道
//那程序员也不知道待比较的两个元素的类型,所以函数指针写成void*
void bubble_sort(void* base, int sz, int width, int(*cmp)(void* e1, void* e2))//与上面类似,目的实现上面的函数原理
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		//每一趟比较的对数
		int j = 0;
		for (j = 0; j < sz - i - 1; j++)
		{
			//两个元素的比较,怎么传两个元素的地址呢??
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

比较函数的内部还是和之前的写法一模一样的

到此位置我们的my_sort函数就有和qsort一样的功能,可以实现任意类型的排序,读者可以自己下来测试自己模拟的这个my_sort函数,只需要电泳我这个函数就行了,接下来就演示一组:

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 my_sort(void* base, int sz, int width, int(*cmp)(void* e1, void* e2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		//每一趟比较的对数
		int j = 0;
		for (j = 0; j < sz - i - 1; j++)
		{
			//两个元素的比较,怎么传两个元素的地址呢??
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}
struct Stu
{
	char name[20];
	int age;
};
int cmp_age(const void* e1, const void* e2)//对年龄的比较
{
	return  ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//数字的比较
}
void test4()
{
	struct Stu s[3] = { {"zhangsan",20},{"lisi",10},{"wangwu",30} };
	int sz = sizeof(s) / sizeof(s[0]);
	my_sort(s, sz, sizeof(s[0]), cmp_age);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d \n", s[i].name, s[i].age);
	}
}
int main()
{
	test4();
	return 0;
}

【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第17张图片
我们是对年龄进行排序的,发现结果和我们i想要的一样。

友情解释:在比较函数里面我们的参数加了const,原因是两个数只需要进行比较,内容不会发生变化,防止使用者自己写比较函数的时候修改了你们的数据内容。

四、总结

这篇博客设计的知识点很多,有一个环节不理解的话,就不太容易把这个思路顺清楚,所以还希望读者可以自己下来把这块硬骨头啃下来,好好消化一下,今天博主就先分享到这里了,我们下期再见。

【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看_第18张图片

你可能感兴趣的:(关于C语言的一切,学习,排序算法,算法)