C语言——qsort函数


给自己一点耐心,学习是很漫长的,而且学基础代码学的是思路。

以下内容所有代码均运行于vs的debug x64环境中。

什么是qsort函数?

以下内容来自cplusplus.com
具体网址是:https://legacy.cplusplus.com/reference/cstdlib/qsort/?kw=qsort
注意是legacy,即旧版的网页,当链接打不开时可能是新版的网页替代完毕了,在搜索引擎搜cplusplus即可。

void qsort (void* base, size_t num, size_t size, int (*compar)(const void*, const void*));

上面这段是 qsort 函数的声明,实际使用中引入库 stdlib.h 即可。
对于这段代码的理解是这样的:
首先,void qsort 指明了函数名为 qsort ,函数返回类型为 void ,即不返回;
其次,(void* base, size_t num, size_t size, int (*compar)( , ))中四个即为该函数的输入参数:
——void* base 一个任意类型的地址,这里只取输入数据的首地址。void 表示任意类型。
——size_t num 数组的长度,也就是有多少个要排序的数据。
——size_t size 每个数据的字节长度,与 void* base 和 size_t num 搭配使用就是确定了待排序数组中各个数据的位置。
——int (*compar)(const void*, const void*) 这是一个函数,地址为compar,名字则为*compar,也就是函数名。后面的const void* 则是这个compar函数的输入参数,同样是输入了任意类型的一个地址。具体函数内容要qsort函数的使用者,依照给出的compar函数的输入参数自行写好。如果第一个指针记作p1,第二个指针记作p2,当返回的结果分别为大于0、等于0、小于0时,对应着p1放在p2前、p1不动、p1放在p2后的操作。

qsort的使用思路

构建比较函数compare

首先自然是要确认自己排序的数据是何种类型,int、char乃至自己的自定义结构体等等。

然后就是构建一个符合数据类型的比较函数compare了,以char类型为例:
根据qsort的使用要求,我们要构建int compare (const void* p1, const void* p2) 。
识别一个完整数据,除了数据的地址,还需要知道数据的长度,比如 int 是4字节,char 是1字节,而 const void* p1 只是单纯抓取了p1这么个地址,所以我们需要把抓到的地址进行类型转换,使用的方法就是“强制类型转换”了。
(char*)p1 ,将p1从任意类型地址转化为char类型的指针,但是要比较可不是比较指针大小,而是比较数据的大小,所以还需要解引用,即*(char*)p1 ,这里就是先转换类型,再解引用,找到了对应地址的数据用来比较大小,那么此时的compare 函数可以这样写:

int compare(const void* p1, const void* p2)
{
return *(char*)p1 - *(char*)p2;
}

确认数据的起点地址base、数据个数num、单个数据字节长度size

以 char status[3] = {3, 1, 2} 为例,因为 status 既是数组名,也是数组首地址,所以 base 可以直接填入 staus ,也可以填入 status[0] 。

数据个数很明显是3,当数据个数不容易确认时,可以直接用 sizeof 来求数据个数:

size_t num = sizeof(arr)/sizeof(arr[0]);

size 同样也可以用sizeof运算符直接求得。

size_t num = sizeof(arr[0]);

要注意 num、size 都是 size_t 类型的。

qsort 使用示例

图片: 看不清可以下载或者放大看。代码会附加在图片之后
代码:

int compare(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;//change the type and return the result of the compare. p1 > p2 means p1 move after to p2.
}

void int_print(int* p)
{
	int i;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p+i));
	}
	printf("\n");
}

int main()
{
	int arr[10] = { 9, 3, 2, 4, 6, 7, 8, 1, 5, 0 };
	size_t num = sizeof(arr) / sizeof(arr[0]);
	size_t size = sizeof(arr[0]);
	qsort(arr, num, size, compare);
	int_print(arr);

	return 0;
}

那么如何比较其它类型呢?比如字符串?

这里我创建一个结构体,也顺带复习一下结构体的使用方法。

创建结构体:

struct mem //creat struct 'member', include 'name' and 'age'.
{
	char name[20];
	int age;
};

结构体使用:
结构体变量.成员名
结构体指针->成员名

这里我创建的结构体包含成员“name”和“age”,所以待会可以单独根据它俩进行排序,因为name是字符串而age是整型,所以比较的函数也要区分来写:

int compare_mem_by_name(const void* n1, const void* n2)//for compare strings, we can use 'strcmp' function of <string.h>
{ 
	return strcmp(((struct mem*)n1)->name,((struct mem*)n2)->name);
}
//about 'strcmp'
//strcmp(const char* str1, const char* str2);

int compare_mem_by_age(const void* a1, const void* a2)
{
	return ((struct mem*)a1)->age - ((struct mem*)a2)->age;
}

比较字符串用到了函数“strcmp”,这是一个需要引用库<string.h>的函数,记得引用。
而且它的返回值是前者大于后者时返回大于0的值,反之为小于0,相等则返回0。

然后就是公式化使用qsort了:

	struct mem arr[3] = { {"zhangsan", 19}, {"lisi", 23}, {"wangwu", 27} };
	int num = sizeof(arr) / sizeof(arr[0]);
	int size = sizeof(arr[0]);
//	qsort(arr, num, size, compare_mem_by_name);
	qsort(arr, num, size, compare_mem_by_age);

虽说qsort要求num、size为size_t类型,但是用int来创建num与size也不是不行。
另外,是不是看见结构体明明内容长度不一样,但是求num和size的方式却没变化?
因为创建数组时,每个元素都占有同样大小的空间,这里计算的是占用的空间而不是实际内容的大小。就比如同样大的两块地,一个是建了小房子,一个是建了大房子,虽然房子有大有小,但它们在规划中的占地面积是一样大的。

模仿qsort函数创建一个类似的通用排序函数

这里我使用的排序方式是简单的冒泡排序,两两比较就继续使用上文的代码:

void tmp(void* p1, void* p2)//地址对应的字节交换,所以使用“强制类型转换”后要接解引用符号“*”以交换地址对应的字节内容
{
		char c = *(char*)p1;
		*(char*)p1 = *(char*)p2;
		*(char*)p2 = c;
}

void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* p1, const void* p2))
{
	int i, j;
	for (i = 0; i < num - 1; i++)//比较的轮次,第几轮比较
	{
		for (j = 0 ; j < num - 1 - i; j++)//具体的比较,相邻的A、B比较,A大于B则A在B之后
		{
			if ((*cmp)((char*)base + size * j, (char*)base + size * (j + 1)) > 0)//char*地址是单个字节的,结合size*j就可以表示出待比较数组中各个元素的首地址
			{
				//根据上一条注释所说,我们用char*地址是单个字节的特性,把单个元素所包括的各个字节一一对应替换,就完成了元素交换
				int k;
				for (k = 0; k < size; k++)
				{
					tmp((char*)base + size * j + k, (char*)base + size * (j + 1) + k);
				}
			}
		}
	}
}

接下来我分析一下,自己创建的思路:
-首先就是设置好比较的次数,第几轮的第几次比较,分别由 i 和 j 两个变量来记录
-接着就是比较的实现了,引入其他函数,根据对比的数据的类型写的比较函数*cmp
-之后就是根据比较结果,进行数据的交换或者不交换,下面我重点说一下这个交换数据的函数

void tmp(void* p1, void* p2)
{
		char c = *(char*)p1;
		*(char*)p1 = *(char*)p2;
		*(char*)p2 = c;
}

我们知道,数据存储与存储单元中,以字节为单位,char类型的数据只占有一个单位,那么交换数据的思路就是”化整为零“。以 int 型变量为例,假设此处一个 int 的字节长度为 4 ,那么我们可以把它拆为4个 char 类型数据:
在这里插入图片描述
那么,交换两个 int 类型的数据就可以通过一个一个 char 类型数据的交换来实现:
交换前
像这样一个一个交换后,原本的”甲“就与”乙“内容互换了,就像是两个书架上的书籍互相调换了:
交换后
那么,在代码实现中,我们具体要注意什么呢?
因为输入给tmp函数的是viod*地址,所以我们要先把地址的类型转换成char*类型:

(char*)p1

因为这个转换是临时的(大致是 分号;之后 p1 就变回void*类型的指针),所以我们要当场用 解引用符号* 来调用(char*)p1 这个地址对应的内容,也就是:

*(char*)p1

之后就是公式化互相交换了,三个碗,一个空,一个装自来水,一个装白开水,让两碗水互换,那就是用空碗作为中转进行交换。

结束语就不写了,可能以后还得过来补内容、擦屁股啥的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值