【C语言】qsort的秘密

一,本文目标

qsort函数可以对任意类型数据甚至是结构体内部的数据按照你想要的规则排序,它的功能很强大,可是为什么呢?

我将通过模拟实现qsort函数来让你对这整个过程有一个清晰的深刻的理解。


二,qsort函数原型

cpp 复制代码
void qsort (void* base,//要排序的对象的第一个元素的首地址
            size_t num, //对象的个数
            size_t size,//每一个对象的大小 Size in bytes
            int (*compar)(const void*,const void*));//Pointer to a function that compares two elements.(并且这个函数要自己写)

qsort函数底层通过快速排序进行,但是这并不是我们感兴趣的点,我想要知道qsort为什么可以对任意类型数据进行排序,与它用什么排序算法排序没有关系,所以,我们用相对简单的冒泡排序来代替快速排序的算法,把冒泡排序赋予可以对任意类型数据进行排序的强大功能。


三,冒泡排序------大数沉底,小数上浮

对于冒泡排序,其基本思想是大数沉底,小数上浮;

这里直接给出源码:

cpp 复制代码
void Bubble_sort(int arr[], int size)
{
	int j,i;
	for (i = 0; i < size-1;i++)//排序趟数等于元素个数-1
	{
		int f = 0;
		for (j = 0; j < size - 1 - i; j++)//每一趟都有一个元素复位,需要排序的次数每次-1
		{
			if ( arr[j] > arr[j+1] )
			{
                int tem = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tem;
				f = 1;
			}
		}
		if (f == 0)	
		{
            break;
        }
	}

3.1相对于qsort,冒泡排序的局限性

只能排序整形数据

四,两个问题:

4.1不定类型比较问题

1.对于if内部的判断,虽然字符型可以用 >,<,= 比较,但是对于字符串类型,无法通过常规方法比较;

4.2不定类型交换问题

2.对于交换部分,由于不同元素的类型不同,占用的内存空间也不同,如何判断传入数据的类型,并实现两个数据的交换呢?


五,改造冒泡排序

我们将自己模拟实现的qsort函数称my_qsort,实现如下:

cpp 复制代码
void my_qsort(void* base,size_t sz,size_t width,int (*cmp)(const void* p1,const void* p2))//cmp 是函数指针,在my_sort中被多次调用
{//它指向的函数是int_cmp,int_cmp就是回调函数
	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);
			}
		}
	}
}

5.1不同类型比较

我们定义一个cmp函数来实现比较:

5.1.1cmp实参

我们传入排序的数组的首元素地址base,将base强制类型转化为 char* 类型,这样base与整数的运算就只会跨过这个整数个字节;

如果再知道每个元素的大小(长度),那么我们就可以精确的访问到每个元素了;


怎么理解呢?

e.g.1:对于int

图中的base指针的位置是 j == 1的位置

e.g.2:对于char

图中的base指针的位置是 j == 5 的位置

5.1.2cmp形参

cpp 复制代码
int int_cmp(const void* p1,const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

由于比较的对象是整型,所以定义一个比较整形的函数,void* 类型不能直接解引用,所以将p1,p2强制类型转化,将两数相减返回。

5.2不定类型交换问题

定义交换函数Swap

cpp 复制代码
void Swap(char* buf1,char* buf2,int width)//按字节逐个交换
{
	for(int i = 0;i < width;i++)
	{
		char tem = *buf1;
		*buf1 = *buf2;
		*buf2 = tem;
		buf1++;//如果是整型,则要遍历四个字节
		buf2++;
	}
}

这时候,我们就知道定义width的意义了,对于一个数据类型,我们虽然不知道它的类型,但是我们可以根据它的长度,一个一个字节地交换数据。


整体代码运行

对于所有类型,我们的冒泡排序都可以排序了,它的作用与库函数qsort一致,仍然需要我们自己写cmp函数:;

对int型数据的排序:

cpp 复制代码
#include<stdio.h>
#include<string.h>
int int_cmp(const void* p1,const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}
void Swap(char* buf1,char* buf2,int width)//按字节逐个交换
{
	for(int i = 0;i < width;i++)
	{
		char tem = *buf1;
		*buf1 = *buf2;
		*buf2 = tem;
		buf1++;//如果是整型,则要遍历四个字节
		buf2++;
	}
}

void my_qsort(void* base,size_t sz,size_t width,int (*cmp)(const void* p1,const void* p2))//cmp 是函数指针,在my_sort中被多次调用
{//它指向的函数是int_cmp,int_cmp就是回调函数
	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);
			}
		}
	}
}

void test1(void)
{
	int arr[] = {2,5,8,9,6,3,1,4,7,0};
	int sz = sizeof(arr)/sizeof(arr[0]);
	my_qsort(arr,sz,sizeof(arr[0]),int_cmp);
	
	for(int i = 0;i < sz;i++)
	{
		printf("%d ",arr[i]);
	}
}

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

对于结构体类型,也可根据结构体内部某一元素的特征排序:

结构体类型

对结构体内的int型数据排序:

cpp 复制代码
struct stu
{
	char name[20];
	int age;
};

int struct_cmp_by_age(const void* p1,const void* p2)
{
	return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}

void test2(void)
{
	struct stu arr[] = {{"zhangsan",18},{"lisi",25},{"wangwu",30},{"xiaoming",40}};
	int sz = sizeof(arr)/sizeof(arr[0]);
	my_qsort(arr,sz,sizeof(arr[0]),struct_cmp_by_age);
	for(int i = 0;i < sz;i++)
	{
		printf("%s %d\n",arr[i].name,arr[i].age);
	}
}

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

对结构体内的char型数据排序:

cpp 复制代码
struct stu
{
	char name[20];
	int age;
};

int struct_cmp_by_name(const void* p1,const void* p2)
{
	return strcmp(((struct stu*)p1)->name,((struct stu*)p2)->name);
}

void test3(void)
{
	struct stu arr[] = {{"zhangsan",18},{"lisi",25},{"wangwu",30},{"xiaoming",40}};
	int sz = sizeof(arr)/sizeof(arr[0]);
	my_qsort(arr,sz,sizeof(arr[0]),struct_cmp_by_name);
	for(int i = 0;i < sz;i++)
	{
		printf("%s %d\n",arr[i].name,arr[i].age);
	}
}


int main()
{
	
	//test1();
	//test2();
	test3();
	return 0;
}

1.对字符串的比较用到<string.h>中的strcmp函数,而它的返回值正好符合qsort函数第四个参数:函数的要求

第一个字符串大于第二个,strcmp返回大于0的数,对应qsort函数要求第四个函数的返回值大于0,表示第一个参数大一第二个。


本文回顾

目录

一,本文目标

二,qsort函数原型

三,冒泡排序------大数沉底,小数上浮

3.1相对于qsort,冒泡排序的局限性

四,两个问题:

4.1不定类型比较问题

4.2不定类型交换问题

五,改造冒泡排序

5.1不同类型比较

5.1.1cmp实参

5.1.2cmp形参

5.2不定类型交换问题

整体代码运行

对int型数据的排序:

结构体类型

对结构体内的int型数据排序:

对结构体内的char型数据排序:


完~

未经作者同意禁止转载

相关推荐
沐怡旸14 分钟前
【算法】【链表】328.奇偶链表--通俗讲解
算法·面试
掘金安东尼3 小时前
Amazon Lambda + API Gateway 实战,无服务器架构入门
算法·架构
码流之上4 小时前
【一看就会一写就废 指间算法】设计电子表格 —— 哈希表、字符串处理
javascript·算法
用户6120414922135 小时前
C语言做的文本词频数量统计功能
c语言·后端·敏捷开发
快手技术6 小时前
快手提出端到端生成式搜索框架 OneSearch,让搜索“一步到位”!
算法
CoovallyAIHub1 天前
中科大DSAI Lab团队多篇论文入选ICCV 2025,推动三维视觉与泛化感知技术突破
深度学习·算法·计算机视觉
NAGNIP1 天前
Serverless 架构下的大模型框架落地实践
算法·架构
moonlifesudo1 天前
半开区间和开区间的两个二分模版
算法
moonlifesudo1 天前
300:最长递增子序列
算法
CoovallyAIHub1 天前
港大&字节重磅发布DanceGRPO:突破视觉生成RLHF瓶颈,多项任务性能提升超180%!
深度学习·算法·计算机视觉