一.qsort函数的使用
首先,我们来先了解一下qsort函数的基本概念。
这里是cplusplus.com网站上qsort函数的描述,看似挺复杂的,哈哈。我们来逐个拆分理解一下。
void qsort (void* base, //base指向你要排序的内容的第一个元素。
size_t num, //待排序的元素的个数。
size_t size,//待排序的元素的大小。
int (*compar)(const void*,const void*)//compar作为一个函数指针,指向的函数可以用来比较两个数的大小,
//并且获得最后的返回值(卖个关子,后面有讲解)
);
注意:qsort库函数的使用还需要包括stdlib.h这个头文件!!!
接下来是官方对每个参数的解释。
拆分理解
1.void* base:base是一个void类型的指针形参,为什么是void类型呢?因为qsort函数作为一个排序函数,他并不能提前知道你要排序什么类型的内容(例如数组,结构体类型),那么这里的void类型的指针就可以很有效的解决这个问题。
2.size_t num:直白的就是元素个数。
3.size_t size:单个元素的内存大小
4.int(compar)(const void ,const void):这里的compar指针指向一个函数,而指向的那个函数也就是回调函数,同时我们可以看到对于compar函数他的返回值对于正负的一个规定。
这里咱们粗略了解一下什么是回调函数
回调函数就是⼀个通过函数指针调⽤的函数。
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数
时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条
件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。
这只是回调函数的概念,我们可以通过后面对qsort函数的模拟实现的过程加深理解
现在了解了qsot函数的基本概念,我们来初步使用一下qsort函数吧
话不多说直接上代码
1.排序数组时
int cmp_int(const void* e1, const void* e2)//cmp_int 函数用来比较来两个数据的大小。
{
return *(int*)e1 - *(int*)e2;
//e1大:返回正值,以此类推。
}
void print_arr(int arr[], int sz)
//print_arr函数用来打印排序前以及排序后的结果以便我们进行区别前后差异。
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 3,1,5,7,2,4,8,6,0,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr, sz);//打印待排序的数组。
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);//打印排序后的数组。
return 0;
}
这里是排序后的结果
2.排序结构体时
//在这里我们定义一个结构体类型,例如要存放学生的个人信息。
struct Stu //学生
{
char name[20];//名字
int age;//年龄
};
//假设按照年龄来比较。
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//假设按照名字来排序。
int cmp_stu_by_name(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->name - ((struct Stu*)e2)->name;//‘->’和‘.’都是访问成员操作符。
}
//当然,除了直接相减获得返回值以外,我们还可以使用库函数strcmp(需要包含头文件string.h)
//strcmp - 是库函数,是专门用来比较两个字符串的大小的。
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//以上两种方法都可以用来比较两个数据的大小。
int main()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);//排序结果看下面第二张图。
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);//排序结果看下面第三张图。
return 0;
}
这是未经过排序的各个成员在内存中存放的顺序。
这是按照年龄的大小进行排序后的结果。
这是按照名字的大小进行排序的结果。
这里名字的排序方式是通过比较名字中的字符的大小(字符通过ASCII码表转换后进行比较)
二.qsort函数的模拟实现
1.思路分析
这所谓qsrort排序函数,其本质也就必定有能用来排序的算法,所以我们也就要写一个可以用来排序的算法(例如快速排序等),这里我们就用我之前写过的冒泡排序作为我们模拟实现的排序算法程序。
然而冒泡排序只能排序数组,所以我们就要多设置几个参数来达到我们排序各种数据目的。
废话不多说,直接上代码!
void Swap(char* buf1, char* buf2, size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void*e1, const void*e2))
{
//趟数
int i = 0;
for (i = 0; i < sz - 1; i++)
{
//一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
//if (arr[j] > arr[j + 1])
if(cmp((char*)base+j*width , (char*)base +(j+1)*width) > 0)
//这里理解起来比较困难,因为要排序的内容的类型不知道,所以我们可以先将我们要进行排序的内容强制转换成char*类型的指针,
//这样如果再加上j*width(或(j加1)*width),就可以得到我们想要进行比较的两个元素的地址
//再将地址传给cmp_int函数进行比较,获得其返回值。然后就可以对任意类型的数据进行交换排序了。
//这部分比较难,大家可以进行画图理解。
{
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
//这里调用swap函数,并且传给swap函数要对其进行交换的两个数据的地址,对两个数据进行交换
}
}
}
}
//最后,我们用这个函数对排序后的数组进行打印,验证我们的猜想。
void print_arr(int arr[],int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 3,1,5,2,4,8,7,6,9,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);//冒泡排序
print_arr(arr, sz);
return 0;
}
这里我们测试的只是数组,当然结构体等其他类型也是可以进行排序的,大家可以自己去测试一下。