目录
1.使用
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
首先,我们闲来看一下关于qsort的解释,已知的是他是有4个参数的,第一个是一个void类型的指针,第二个第三个是size_t类型的数据,第四个是一个函数指针,并且该函数的返回值为int,参数是两个const void*
进行一个关于size_t的小科普,其实这就是unsigned int 类型,只不过被重命名了。
然后我们接着讲,qsort函数的功能就是排序,他的第一个参数接受的是要排序的空间的第二个参数是要排序的元素个数,第三个参数是要排序的元素的大小,第四个参数使我们自定义的一个排序标准,要求以函数的方式书写,返回值为int,并且两个参数都是const void*类型的,如果第一个参数大于第二个参数的话返回一个大于0的数,小于返回小于0的数,等于返回0,这样的话qsort排除来的是升序,如果我们想要的是倒序,可以调一下返回值。
这东西的头文件是stdlib.h
既然大体的对他了解之后,我们就可以开始一些实践。
#include <stdio.h>
#include<stdlib.h>
int compare(const void* a, const void* b)
{
return (*(int*)a - *(int*)b);
}
int main()
{
int arr[] = { 1,4,7,2,5,8,3,6,9,0 };
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), compare);
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
return 0;
}
上述代码的逻辑很简单,就是对于一个qsort函数的简单应用,我们可以看一下输出之后的值
如果我们想排降序的,可以修改一下compare函数的返回值
#include <stdio.h>
#include<stdlib.h>
int compare(const void* a, const void* b)
{
return (*(int*)b - *(int*)a);
}
int main()
{
int arr[] = { 1,4,7,2,5,8,3,6,9,0 };
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), compare);
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
return 0;
}
比如说,我们想把一个字符类型的数组倒序排列
#include <stdio.h>
#include<stdlib.h>
int compare(const void* a, const void* b)
{
return (*(char*)b - *(char*)a);
}
int main()
{
char ch[] = "abcdef";
qsort(ch, sizeof(ch) / sizeof(ch[0]), sizeof(ch[0]), compare);
for (int i = 0; i < sizeof(ch) / sizeof(ch[0]); i++)
{
printf("%c ", ch[i]);
}
return 0;
}
或者说,我们排序一个结构体,将一个结构体按名字顺序来排序
typedef struct students
{
int number;
char name[20];
int age;
}stud;
int compare(const void* a, const void* b)
{
return strcmp(((stud*)a)->name, ((stud*)a)->name);
}
int main()
{
stud stu[5] = { {152,"张三",17},{142,"李四",16},{166,"王五",19},{172,"小明",15},{154,"小红",15} };
qsort(stu, sizeof(stu) / sizeof(stu[0]), sizeof(stu[0]), compare);
for (int i = 0; i < sizeof(stu) / sizeof(stu[0]); i++)
{
printf("%d %s %d\n", stu[i].number,stu[i].name,stu[i].age);
}
return 0;
}
好的,在了解到简单的应用之后,我们再来详细的谈一下关于qsort这个函数
2.qsort的深入理解
首先我们先考虑一个问题,为什么qsort这个函数的参数设计的这么奇怪,他的第一个参数是void类型的指针,这是为什么呢,void类型的指针又有什么用呢?
其实啊,这和qsort设计的初衷有关,qsort在设计的时候,目的是为了根据用户的需求排各种各样不同的变量,并不只是单单为了排序整形变量设计的,所以在参数传递的时候,就会遇到各种各样不同了类型的指针,假设说如果我们的参数设计的是一个int类型的指针,但是我想排序的是一个char类型的数组,这样的话我把char类型的数组传给int类型的指针不就出错了吗?所以,才使用void*类型的参数,这个参数可以接受各种各样的指针。
然后,在传递了排序的元素个数之后为什么又要传递元素的大小呢?这其实还跟第一个参数有关系,大家想一下,在qsort中,不管你第一个参数给我传递什么样的指针,qsort在接受之后都会认为这是一个void类型的指针,这就导致一个问题,void类型的指针是不能进行解引用操作或者是其他一些操作的,这样的话是没法继续处理的啊。不知道大家还是否记得指针类型的意义,指针类型其实代表了指针的权限大小,举个栗子,int类型的指针在解引用或者是进行运算的时候,每一次访问的是4个字节。相信大伙已经想到了,我们可以吧void类型的指针强制类型转化为char类型的指针,这样的话,根据char类型指针的权限特点,他会一次性访问一个字节,我再拿他跟我们要排序的元素大小相乘一下,这样他不就一次性访问大小不就正是我们要排序的那个元素的大小了吗?所以,这个时候只要再结合一下比较函数的返回值就可以进行排序了。
3.模拟实现
既然,上面我们了解了原理,那么现在我们就开始模拟一下qsort的实现。
在模拟实现这东西之前我们再来看一下他的参数
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
值得一提的是qsort使用的是快速排序,但是,我们在这里为了简单就用冒泡排序模拟一下
#include <stdio.h>
void swap(char* temp1, char* temp2, size_t num)//交换函数
{
while (num)//因为char类型的指针权限只有一个字节,而我们要交换num个字节,所以要循环num次
{
char temp = *temp1;
*temp1 = *temp2;
*temp2 = temp;
temp1++;
temp2++;
num--;
}
}
void my_qsort(void* base, size_t sz, size_t num, int(*compare)(const void*, const void*))
{
int i = 0;
int j = 0;
for (i = 0; i < sz; i++)
{
for (j = i + 1; j < sz; j++)
{
if (compare(((char*)base) + i * num, ((char*)base) + j * num) > 0)//利用判断函数
{
swap(((char*)base) + i * num, ((char*)base) + j * num, num);//符合条件后调用交换函数
}
}
}
}
int compare(const void* a, const void* b)//判断函数
{
return *((int*)a) - *((int*)b);
}
int main()
{
int arr[] = { 1,4,7,2,5,8,3,6,9,0,0 };
my_qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), compare);
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
return 0;
}
当然,我们也可以用一些库函数来完成
void myqsort(void * base, size_t nitems, size_t size, int(*compar)(const void *, const void *))
{
int i, j;
char * st = (char *)base; //void *不方便加减,用char *加减轻松且字节跳转为1,方便控制。
char tmp[16]; //考虑到long double类型,临时空间做成16字节比较保险
for (i = 0; i < nitems - 1; i++)
{
for (j = 0; j < nitems - 1 - i; j++) //冒泡常用循环头
{
if (compar(st + j * size, st + (j + 1) * size) > 0) //比较的时候跳转注意乘size
{
memcpy(tmp, st + j * size, size); //交换操作用memcpy完成就不会出问题。
memcpy(st + j * size, st + (j + 1) * size, size);
memcpy(st + (j + 1) * size, tmp, size);
}
}
}
}
char * st = (char *)base;
这段代码的意思是更改st指针的指向为base指向的地址。
ok,今天就暂时到这里,哥们要去学习了