qsort函数保姆式解析,从内部分析并实现qsort函数的自定义

目录海洋馆🐠🐡🦐🦑🐙🦞🦀🐚

   🐠qsort函数绪论:

   🦑qsort函数优点

      🐡Number 1:可以对各种类型的数据进行快速排序

     🐡Number 2:使用方便,无需再重新编写代码,只需给出指定条                                  件(上传指定参数,包括构建传参函数)

   🦞对qsort函数的内部认识

   🐢qsort函数传参以及返回值介绍

     🦀  qsort函数参数

   🦀对qsort函数的使用范例

   🐡(1)对int类型数据进行快排

   🐡(2)对char类型进行快排

    🐡(3)对浮点数进行快排

     🐡(5)对结构体按指定内容进行排序  (name)

   🐡(5)对结构体按指定内容进行排序  (age)  

   🦐qsort函数自定义

    🦀冒泡排序源代码呈现:

   🦀将我们的冒泡排序修改为qsort快速排序

          🦐 STEP 1 : 修改参数

            🦐 STEP 2 : 相邻两个元素进行比较

               🦐 STEP 3 : 符合条件两数交换

   🦑运行效果:

    🦑代码呈现:

   🐠qsort函数绪论:

   🐙还记得我们之前提到的qsort快速排序吗?之前我们可以利用冒泡排序的方式依次将我们相邻的两个元素进行比较,最后给数组元素进行排序,但是,要是每一次都自己写一个冒泡排序的代码来进行数组排序的话是不是感觉很麻烦?这次我们来认识一个函数qsort函数,这个函数可以直接引用,将我们数组中的数据进行快速排序。

   🦑qsort函数优点

   🐙首先我们来认识一下qsort函数的使用优点,相信你一定会一下子爱上快速排序的。

      🐡Number 1:可以对各种类型的数据进行快速排序

   🐙当我们还在使用冒泡排序的时候,会发现在我们的冒泡函数设计好之后,排序的数组变量名也已经固定好了。例如:

                         

我们会发现,我们上传的参数只能是int类型的数组,而我们的快速排序可以适应各种各样类型的数组数据。

     🐡Number 2:使用方便,无需再重新编写代码,只需给出指定条                                  件(上传指定参数,包括构建传参函数)

   🐙qsort函数由于可以对各种类型的数据进行排序,因此需要上传的参数也相对较多,在使用这个函数的时候我们只需要将各部分参数说清楚即可。

   🦞对qsort函数的内部认识

   🐢qsort函数传参以及返回值介绍

   🐙首先我们要强调的是在使用qsort函数的时候:

        需要先引用一个头文件<stdlib.h>!!!!

       之后qsort函数的具体参数我们通过一张图片进行了解,并解释qsort函数的传参类型以及返回值:

   🐙是不是看起来有些迷茫?不要着急,我们一起来分析一下:qsort函数一共有四个参数值,分别是上图中说到的 void* base,size_t num,size_t size,int(*compar)(const void*,const void*),我们将其逐步拆分,逐项击破。

     🦀  qsort函数参数

                    🐚 PAR 1 :  void * base 我们可以从数据定义的形式看出,这是一个指针参数,我们                                           需要将想要排序的数组的首地址作为参数传递给函数

                    🐚 PAR 2 :  size_t num 这个参数我们所需要上传的需要排序的数组元素的个数,方                                           便我们qsort函数在排完序之后及时结束

                    🐚 PAR 3 : size_t size 这个参数的含义是我们数组单个元素所占字节的大小

                    🐚 PAR 4 : int(*compar)(const void*,const void*) 最后看起来超级复杂的参数是另一                                          个需要我们新建的函数,作用是向qsort提示,如何进行相邻两个数据判断                                        大小

   🐙上面就是我们qsort函数参数的大致介绍,但是为什么要传输这四个参数呢?第一个和第二个参数肯定不用解释了,直接从第三个参数进行解析:据我们所指的是 char 类型的数据和 int 类型的数据在地址中所占的字节大小不同,只是对于一种类型进行操作那么不用传参也没有关系,但是我们这个函数是对所有类型的参数进行判断排序,那么上传数据类型的大小就显得很有必要了,可以方便我们函数进行判断,解析完第一个数据的时候需要调过多少个字节才可以读取到下一个数据。而第四个参数和我们第三个参数的意义很像,我们同样需要向qsort说明这个数组的类型,如何判断相邻数据的大小,从而方便我们函数的计算。

   🦀对qsort函数的使用范例

   🐡(1)对int类型数据进行快排

      🐙运行效果:

    🐙值得我们注意的是:在构建参数4函数时qsort函数会自动将数组相邻两个元素进行传参进入我们的cmp函数中,因此我们需要做的是进行传入参数进行强制类型转换,将函数本身的void*类型强转为int*类型, 强制类型转换之后在解引用得到的就是我们前两个数组元素。实际上我们新建的cmp函数相当于strcmp函数,两个数据相同则返回0,第一个数据大于第二个数据就返回一个>0的数字,反之则返回一个<0的数据。返回之后qsort函数自行进行判断。

    🐙代码呈现:

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

//向qsort函数传参
int cmp(const void* e1, const void* e2)     //qsort函数会自动将数组相邻两个元素进行传参
                                            //进入我们的cmp函数中
{                                           //因此我们需要做的是进行传入参数进行强制类型
                                            //转换,将函数本身的void*类型强转为int*类型
	return *(int*)e1 - *(int*)e2;           //强制类型转换之后在解引用得到的就是
                                            //我们前两个数组元素
}

int main()
{
	int arr[10] = { 12,23,45,32,16,43,37,87,96,8 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);       //求数组大小
	qsort(arr, sz, sizeof(int), cmp);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

   🐡(2)对char类型进行快排

   🐙趁热打铁我们来进行对char排序的示例,运行效果:

   🐙代码呈现:

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

//向qsort函数传参
int cmp(const void* e1, const void* e2)     
{                                          
	return *(char*)e1 - *(char*)e2;           
}

int main()
{
	char ch[10] = "hfwkxiovf";
	int len=strlen(ch);
	qsort(ch, len, sizeof(char), cmp);
	printf("%s", ch);
	return 0;
}

    🐡(3)对浮点数进行快排

   🐙运行效果:

   🐙代码呈现:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#define EPS 1e-7

//向qsort函数传参
int cmp(const void* e1, const void* e2)
{
	if (fabs(*(float*)e1 - *(float*)e2) < EPS)
	{
		return 0;
	}
	else if (*(float*)e1 - *(float*)e2 < EPS)
	{
		return -1;
	}
	else
	{
		return 1;
	}
}

int main()
{
	int i = 0;
	float arr[10] = { 1.23,1.56,23,43,7.65,0.12,3.75,56.77,68.88,21.22 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(float), cmp);
	for (i = 0; i < sz; i++)
	{
		printf("%.2f\t", arr[i]);
	}
	return 0;
}

     🐡(5)对结构体按指定内容进行排序  (name)

   🐙运行结果:

   🐙代码呈现:

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

struct Stu
{
	char name[20];
	int age;
	int ID;
};
//向qsort函数传参
int cmp(const void* e1, const void* e2)     
{                                          
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

int main()
{
	struct Stu s[3] = { {"张三",18,123456},{"李四",20,123454},{"王五",19,123457} };
	int i = 0;
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp);
	for (i = 0; i < sz; i++)
	{
		printf("%s %d %ld\n",s[i].name,s[i].age,s[i].ID);
	}
	return 0;
}

   🐡(5)对结构体按指定内容进行排序  (age)  

   🐙运行结果:

   🐙代码呈现:

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

struct Stu
{
	char name[20];
	int age;
	int ID;
};
//向qsort函数传参
int cmp(const void* e1, const void* e2)     
{                                          
	return ((struct Stu*)e1)->age- ((struct Stu*)e2)->age;
}

int main()
{
	struct Stu s[3] = { {"张三",18,123456},{"李四",20,123454},{"王五",19,123457} };
	int i = 0;
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp);
	for (i = 0; i < sz; i++)
	{
		printf("%s %d %ld\n",s[i].name,s[i].age,s[i].ID);
	}
	return 0;
}

    🐙上面我们的qsort展示用例也就结束了,相信大家通过上面的例子已经基本知道了qsort的使用方法了。接下来我们来谈一谈qsort函数的内部实现,从更深层次了解qsort函数。

   🦐qsort函数自定义

   🐙俗话说的好:要想了解一个东西最好的方法无非是分析其内部构造,那么我们就来亲自实现以下这个函数,相信这一定会是大家受益匪浅。经过我们上面的示例,我们会通过分析qsort函数的参数发现,其实qsort函数和我们之前设计的冒泡排序很相似,只不过是多了几个参数而已,其本质无非还是相邻的两个元素进行比较然后交换位置而已。只不过把编写冒泡函数这个复杂的操作交给了其他人而已。那么我们就可以从冒泡排序入手,将一个冒泡排序修改变成我们的自定义qsort函数。

    🦀冒泡排序源代码呈现:

#include<stdio.h>

//冒泡排序
void bubble_sort(int* arr1, int sz)
{
	int i = 0;   //外部循环变量
	int j = 0;    //内部循环变量
	int ret = 0;    //交换需要的中间变量
	for (i = 0; i < sz; i++)
	{
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr1[i] > arr1[i + j + 1])
			{
				ret = arr1[i];
				arr1[i] = arr1[i + j + 1];
				arr1[j + i + 1] = ret;
			}
		}
	}
	return;
}

int main()
{
	int arr[10] = { 34,23,56,787,35,987,121,23,45,2 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//调用自定义函数进行排序
	bubble_sort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

   🦀将我们的冒泡排序修改为qsort快速排序

          🦐 STEP 1 : 修改参数

     🐙由于我们设计的新的冒泡排序需要有和qsort同样的功能,同样可以适应各种类型数据的排序,因此我们的函数的返回值类型以及参数均可以参考并仿制qsort函数的形式进行设计。

void bubble_sort(const void*e1,int num,int width,int(*cmp)(const void*e1,const void*e2))
{
	int i = 0;   //外部循环变量
	int j = 0;    //内部循环变量
	int ret = 0;    //交换需要的中间变量
	for (i = 0; i < num-1; i++)
	{
		for (j = 0; j < num - i - 1; j++)
		{
			//两元素进行比较
            
		}
	}
	return;
}

   🐙我们会发现整体外部循环的代码都是可以直接运用的,修改的部分之后函数内部的相邻两个数据比较并交换的部分,那么我们就来进行我们的第二步。

            🦐 STEP 2 : 相邻两个元素进行比较

//冒泡排序
void bubble_sort(const void*base,int num,int width,int(*cmp)(const void*e1,const void*e2))
{
	int i = 0;   //外部循环变量
	int j = 0;    //内部循环变量
	int ret = 0;    //交换需要的中间变量
	for (i = 0; i < num-1; i++)
	{
		for (j = 0; j < num - i - 1; j++)
		{
			//两元素进行比较
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换函数

			}
		}
	}
	return;
}

   🐙这一部分理解起来对于基础较差的朋友们来说可能有些困难,不过没有关系,可以先去复习巩固一下指针进阶,本人主页博客中也有关于指针进阶相关的内容哦,可以作为参考。废话少说,下面开始解释原因:首先我们要来分析我们之前qsort函数所建立的参数。                                           🐙int (*compar) (const void*, const void*) 很显然我们所需要建立的函数没有返回类型,在上面使用函数的时候在cmp比较函数中进行强转操作,才可以比较两个数据之间的大小。                         🐙在这里其实就是我们上面改所创建的函数,但是我们在设计这个函数的时候并不知道使用者会将参数强转为什么样的数据类型,因此,我们在使用的时候只能进行最小化判断即将我们的实参强转为最小的数据类型也就是char类型。                                                                                             🐙后面的  j * width 是单个元素所占字节的大小。这样我们用一个元素进行 +1 操作的时候跳过的就会是一个该数据的大小了。分析完之后我们抓紧时间进入下一步。

               🦐 STEP 3 : 符合条件两数交换

//交换函数
void swap(char* e1, char* e2,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *e1;
		*e1 = *e2;
		*e2 = tmp;
		e1++;
		e2++;
	}
}

//冒泡排序
void bubble_sort(const void*base,int num,int width,int(*cmp)(const void*e1,const void*e2))
{
	int i = 0;   //外部循环变量
	int j = 0;    //内部循环变量
	int ret = 0;    //交换需要的中间变量
	for (i = 0; i < num-1; i++)
	{
		for (j = 0; j < num - 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);
			}
		}
	}
	return;
}

   🐙同样的道理在使用交换函数的时候我们也一个字节一个字节进行交换,同时将单个元素字节的长度作为参数传给swap函数,这样我们相邻两个数据就可以正常的进行交换了。到这里我们的函数也就改造完成了,我们的冒泡排序也不再是一个普通的冒泡排序了,变成了一个万能的快速冒泡了,哈哈哈。万事俱备,那么就让我们看一看我们的代码是否能够顺利运行吧! 

   🦑运行效果:

   🐙顺利运行,那么我把整体的代码呈现给大家。

    🦑代码呈现:

#include<stdio.h>

int cmp(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

//交换函数
void swap(char* e1, char* e2,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *e1;
		*e1 = *e2;
		*e2 = tmp;
		e1++;
		e2++;
	}
}

//冒泡排序
void bubble_sort(const void*base,
	             int num,
	             int width,
	             int(*cmp)(const void*e1,const void*e2))
{
	int i = 0;   //外部循环变量
	int j = 0;    //内部循环变量
	int ret = 0;    //交换需要的中间变量
	for (i = 0; i < num-1; i++)
	{
		for (j = 0; j < num - 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);
			}
		}
	}
	return;
}

int main()
{
	int arr[10] = { 34,23,56,787,35,987,121,23,45,2 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//调用自定义函数进行排序
	bubble_sort(arr, sz,sizeof(int),cmp);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

   🐙到此位置我们本次博客的内容也就圆满结束了,各方面细节的讲述下来总共到达了7000多字,算是上一篇长篇博客啦,那么感谢大家的观看,祝大家天天开心。

  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿白逆袭记

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

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

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

打赏作者

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

抵扣说明:

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

余额充值