目录
(一)认识qsort函数
1.qsort函数定义
如果我们想要实现qsort函数,我们就应当了解了解qsort函数
void qsort (void* base, size_t num, size_t size,
int (*compar) (const void*,const void*));
以上是CPlusPlus上对qsort函数的定义。
通过翻译,它的意思指的是:
第一行:“通过base指针指向的数组,将数组里的num个元素进行排序,其中每一个元素占size个字节,并且使用比较函数来确定顺序”。
第二行:“此函数使用的排序算法通过调用指定的比较函数来比较两个元素,该函数是用函数指针作为参数。”
第三行:“该函数没有返回值,但通过按compare定义的对其元素进行基本重排序来修改所指向的数组的内容。”
第四行:“等效元素的顺序未定义。”
2.分析参数
base:指针指向数组待排序的第一个元素。
num:数组里元素的个数。
size:每个元素占多少字节
compar:是一个函数指针,调用它时会将传进来的两个参数p1和p2进行比较。
若p1大于p2 返回>0的数
若p1等于p2 返回=0
若p1小小于p2 返回<0的数
int compar (const void* p1, const void* p2);
3.运用qsort函数
#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
void print(int* arr,int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test01()
{
int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print(arr, sz);
}
struct Stu
{
char name[20];
int age;
};
int cmp_stu_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void test02()
{
struct Stu arr[] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 10 } };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_age);
}
int main()
{
//test01();
test02();
return 0;
}
以上就是运用qsort函数的代码实现。
在这里,我们不仅可以使用qsort来排序整形,而且还可以运用qsort来排序结构体。
需要注意的是,在这里需要对e1和e2进行强制类型转换。
因为转换前的e1和e2是作为void*的类型存放的,void*类型的数据不可以进行加减乘除。
所以我们在这里需要进行强制类型转换,转换的类型由你的数组类型决定。
以上就是qsort函数的基本定义还有qsort函数的基本使用方法。
(二)关于冒泡排序
我们在实现自主编写的qsort函数之前,我们应当先复习复习冒泡排序。
首先给定一个数组
int arr[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
由图可知该数组是一个逆序的数组,我们想要将这个数组变成升序的数组,怎么做呢?
我们可以运用到我们现阶段非常熟悉的一个方法:冒泡排序。
实现操作:
int arr[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
int sz = sizeof(arr) / sizeof(arr[0]);
for(int i = 0; i < sz - 1; i++)
{
int j = 0;
for(j = 0; j < sz - 1 - i; j++)
{
int tmp = 0;
if(arr[j] > arr[j+1])
{
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
具体的原理和底层逻辑,就不在这里赘述了,可以参考我的上篇博客
这里面对冒泡排序的底层逻辑有全面的讲解。
为什么要在这里提到冒泡排序呢?
因为在接下来的my_qsort函数实现了,脱离不了与冒泡排序之间的关系。
(三)实现my_qsort函数
1.关于整形数组:
void test01() //整形排序
{
int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
int num = sizeof(arr) / sizeof(arr[0]);
print(arr, num);
my_qsort(arr, num, sizeof(arr[0]), cmp);
print(arr, num);
}
int cmp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void print(int* arr, int num)
{
for (int i = 0; i < num; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void swap(char* buf1, char* buf2, size_t size)
{
for (int i = 0; i < size; i++)
{
char tmp = 0;
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void my_qsort(void* base, size_t num, size_t size, int(*cmp)(const void*, const void*))
{
for (int i = 0; i < num - 1; i++)
{
for (int j = 0; j < num - 1 - i; j++)
{
//arr[j] > arr[j + 1]; 错误,对于结构体就不适用
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
以上就是关于整形数组的排序的代码实现
2.关于结构体数组:
struct stu
{
char name[20];
int age;
};
int cmp_struct_age(const void* e1, const void* e2)
{
return (((struct stu*)e1)->age - ((struct stu*)e2)->age);
}
int cmp_struct_name(const void* e1, const void* e2)
{
return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
void swap(char* buf1, char* buf2, size_t size)
{
for (int i = 0; i < size; i++)
{
char tmp = 0;
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void my_qsort(void* base, size_t num, size_t size, int(*cmp)(const void*, const void*))
{
for (int i = 0; i < num - 1; i++)
{
for (int j = 0; j < num - 1 - i; j++)
{
//arr[j] > arr[j + 1]; 错误,对于结构体就不适用
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
void test02()
{
struct stu arr[] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 10 } };
int num = sizeof(arr) / sizeof(arr[0]);
//my_qsort(arr, num, sizeof(arr[0]), cmp_struct_age); //对结构体age年龄排序
my_qsort(arr, num, sizeof(arr[0]), cmp_struct_name); //对结构体name名字排序
}
以上为结构体数组的排序。分为两种:一种是年龄的排序,另一种就是对名字首字符大小的排序。
3.流程:
//流程:
//调用 自制的my_qsort()函数
//实现冒泡排序
//再在排序里判断 (cmp((char*)base + j * size, (char*) base + (j + 1) * size) > 0) ?
//为了不仅仅可以排序整形,还可以排序结构体中的变量,所以我们使用
//(char*)base + size * j 和
//(char*)base + size *(j+1)
//对于int arr[] = {9,8,7,6,5,4,3,2,1,0}
//我们现在想要获得'9'和'8'的地址,但是我们不能直接base+0和base+1
//因为base 的类型为void*,void*类型不能+1或者-1操作
//所以应当强制类型转换
//那这里为什么用(char*)而不用(int*)呢?
//如果我们仅仅排序int型整形,当然可以选择int*,因为一次就可以跳过4个字节
//但是,我们想要排序结构体数组,一次可能就要跳过大于4个字节的操作。
//如不是4的倍数将无法进行精准的跳跃字节的操作
//所以我们用(char*),这样一次只跳过一个字节,若是一个元素有9个字节即跳过9个字节
//便可以使用(char*)base + size
//size是一个元素的字节大小
//若是相要找到下一个元素的位置,则可以size*(j+1).
//
//对于(int*)cmp(const void* e1, const void* e2)
//e1是一个指针,指向了要比较的第一个元素的地址
//e2也是一个指针,指向了要比较的第二个元素的地址
//e1指向的元素大于e2指向的元素,返回>0的数字
//e1指向的元素等于e2指向的元素,返回=0
//e1指向的元素小于e2指向的元素,返回<0的数字
//
总结:
以上就是实现my_qsort函数的底层逻辑和代码演示。
自主实现该函数可以提高我们对函数调用和回调函数的使用的熟练程度。
这里需要注意的是对一下代码的理解:
这一部分在上面讲的十分清楚,第一遍看不懂很正常,多看几遍就可以熟练,熟练之后就可以尝试自主编写。
对完整的代码参考可以访问我的Gitee仓库:
my_qsort/my_qsort/test.c · 无双/test_for_code_with_X1 - Gitee.com
其中对当中细节讲解也很到位。
学习了解之后下来,可以进行适当的练习。
记住“坐而言不如起而行!”
Actions speak louder than words!
在以后的学习中要保持空杯形态。