前言:在学习C语言的过程中,我第一次接触到了函数qsort,并深入地对它进行了一些了解,现特意将自己的所思所感分享给大家,希望能对各位带来一些帮助
目录
一:qsort函数的介绍
我们要实现一个函数必然要先了解这个函数,那我们接下来就先来了解一下这个函数的相关知识。
1. qsort函数的简述
qsort是C语言的一个库函数,它的使用需要引用头文件<stdlib.h>,它的作用是对任意类型的数据进行快速排序(是不是很牛),是基于快速排序算法的一个函数。
2.函数的定义
void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
光看这个函数在库中的定义是无法准确地理解这个函数,接下来我会从各方面细细分析,以便于大家理解。这里为了更好的理解,我将上述定义进行稍微简化,以便接下来的介绍。
简化:
void qsort( void *base, size_t num, size_t width, int (*cmp )(const void *e1, const void *e2 ) );
3: 函数的参数
对于一个函数而言,参数必然是最重要的,由定义可知这个函数有四个参数,分别是
void * base size_t num size_t width int (*cmp)(const void *e1,const void*e2 )) 我在这里会对这四个参数进行逐一介绍:
1-1:void * base
第一个参数是void * base,很明显这是一个类型为void*的指针,它代表了目标序列的起始地址,指向了目标序列的首元素。这里我们要知道的重点是指针类型为void*(无类型指针),这样设计满足了对任意类型数据进行排序,因为任意类型数据序列的首元素地址都可以传参给qsort函数,是一个十分重要的前提条件。
1-2:size_t num
第二个参数是size_t num,一个类型为无符号整型的整数,它表示了要排序列元素个数的多少。
1-3:size_t width
第三个参数是size_t width,一个类型为无符号整型的整数,他表示了要排序列中每个元素的字节大小。
1-4:int (*cmp)(const void *e1,const void*e2 )
第四个参数是int (*cmp)(const void *e1,const void*e2 ),是其中最重要的,也是最难理解的一个,从定义我们可以知道这是一个函数指针,指向了一个返回类型为int,两个参数为const void *e1,const void*e2 的cmp函数。这个函数执行着qsort函数对序列进行快速排序的重要步骤——对前后两个元素进行比较,判断是否要进行交换。这个函数需要qsort函数的使用者自己实现,以达到对不同种类型数据进行比较交换的效果,其中的参数e1是一个无类型指针,指向了排序序列的开始,而e2则是一个指向要与e1地址元素进行比较的元素的无类型指针。
这里还需要重点介绍一下有关cmp函数的返回值,其实这个函数的返回值不难理解,有下列三种情况:
情况 条件 >0 *e1 > *e2 <0 *e1 < *e2 0 *e1 = *e2 即表示了两个地址所指向元素进行比较后返回值的正负,以达到对两个元素进行比较的效果,进一步达到排序的效果。(满足上述表格为升序排序)
交换思路:
排升序时大于0才交换,排降序时小于0才交换,进行函数内部e1,e2位置的交换就可以控制升序降序的排列。
cmp函数的返回值 < 0(不进行置换),> 0 (进行置换),0 (不进行置换)。
注意:如果两个元素的值是相同的(即*e1=*e2时),那么它们的前后顺序是不确定(即不知道是否会进行交换)的。也就是说qsort()函数的实现是一个不稳定的排序算法。(下面对浮点型数据进行排序时会简要说明)
4: 函数的返回值
qsort函数的返回值由定义就知道是空类型,即不存在,这里不过多说明,知道即可。
小结:
使用一个函数的前提是了解这个函数的完整结构和组成,这里我们需要在注意到的一点是这个函数的参数用到了许多的空类型void去定义,这是为了达到对不同类型数据进行排序的效果,因为void类型可以接收所有类型的数据,进一步介绍会在函数的实现过程中会涉及到,大家了解好有关qsort函数的相关知识后就可以开始应用了。
二:用qsort函数对数据进行排序
1. 整型数据的排序
对整型数据进行排序是最简单的一项,我们只要了解好了qsort函数的定义,那么实现起来就不难
//1.整数排序
int cmp(const void* e1, const void* e2)
{
return *(int*)e2 - *(int*)e1; //交换e1,e2的位置可以控制升降序的排列,现在是降序
}
int main()
{
int arr[] = { 1,4,2,5,3,7,6,8,10,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(int), cmp);
int i = 0;
for (i = 0;i < sz;i++)
{
printf("%d ", arr[i]);
}
return 0;
}
注意:这里还需要强调一点的是为了接受多种类型的数据,比较函数的形参是void*类型,在该函数内部使用两个参数时要根据具体情况对参数进行数据类型的强制转换(eg:这里转换成int*类型)。
2. 字符型数据的排序
对字符型数据进行排序其实跟整型数据差不多,因为内存中存储的就是字符的ASCII码值,这里不做过多阐述,直接见代码:
//2.字符型数据的排序
int cmp(const void* e1, const void* e2)
{
return *(char*)e1 - *(char*)e2;
}
int main()
{
char arr[] = { 'c','f','r','g','h','j' };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(char), cmp);
int i = 0;
for (i = 0;i < sz;i++)
{
printf("%c ", arr[i]);
}
return 0;
}
3. 字符串的排序
用qsort函数对字符串进行排序是我看来是一个难点,要清楚地掌握qsort函数参数的意义,这里我就详细说明一下我自己的见解。
先展示主函数的代码:
int main()
{
char arr[10][10] = { "aefwww","abcwwww","asebf","aczdwwww","afcd","axsd","athseww","aesdew","asdfg","an" };
///字符串的末尾有'\0'
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
printf("排序前\n");
for (i = 0; i < sz; i++)
{
printf("%s\n", arr[i]);
}
printf("\n"); //要搞清楚qsort函数内部的参数是什么
qsort(arr, sz, sizeof(char) * 10, cmp_str);//这里要注意参数中字符串的宽度(即第三个参数),其意义是要比较数据的总字节大小,所以要用定义的序列长度表示
printf("排序后\n");
for (i = 0; i < sz; i++)
{
printf("%s\n", arr[i]);
}
printf("\n");
return 0;
}
介绍:这里创建二维数组是因为在C语言中没有字符串类型,char类型容纳不下字符串,所以将每一个字符串看成一个一维数组,并且每个字符串的内容后面是有多个\0字符的(决定着qsort函数的第三个参数)。
接下来就是如何实现cmp_str函数了,我在这里想了三种比较字符串的方法,在这里一一展示
int cmp_str(const void* e1, const void* e2)//1.只按各字符串首字母大小进行比较
{
char* p1 = (char*)e1;
char* p2 = (char*)e2;
return (*p1 - *p2);
}
int cmp_str(const void* e1, const void* e2)//2.按各字符串长度进行比较
{
return (strlen((char*)e1) - strlen((char*)e2));
}
int cmp_str(const void* e1, const void* e2)//3.利用strcmp函数以字典序进行比较
{
return strcmp((char*)e1, (char*)e2);
}
这三种比较的方法其实都不难理解,前提是各位知道其中一些字符串函数的用法,这里要提醒大家的是,为了观察不同比较函数带来的差别以及特点,我们可以通过改变二维数组arr的元素(各个字符串的内容)来进行多次测试。
4. 浮点型数据的排序
对浮点型数据排序也是较为重要的一点,这里特别需要注意的是qsort函数中比较函数(cmp函数的使用),因为比较函数的返回值是int类型的,当两个浮点型数据的差值在1以内时返回值会自动转化成数字0,无法真正实现比较的功能,所以我们需要思考如何在cmp函数内部解决这个问题,这里我采取了以下这种方法(cmp_lf函数的实现)。
//排序浮点型数据
int cmp_lf(const void* e1,const void* e2)//比较过程的实现
{
//由于排升序时只有大于0才会交换,排降序时只有小于0才会交换,所以等于0时,这个三目运算符会其与将两个否定条件进行相同结果的转换,从而不影响排序过程,所以可以不进行==0结果的判断
return (*(double*)e1 - *(double*)e2) > 0 ? 1 : -1;
}
int main()
{
double arr[6] = { 1.2,3.4,8.2,5.2,1.5,3.8 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("排序前:\n");
for (int i = 0;i < sz;i++)
{
printf("%.2lf ", arr[i]);
}
qsort(arr, sz, sizeof(arr[0]), cmp_lf);
printf("\n排序后:\n");
for (int i = 0;i < sz;i++)
{
printf("%.2lf ", arr[i]);
}
return 0;
}
这里要特别说明一种错误的实现方法:如果按照这种方法在内部实现比较功能的话,会将差值大于0小于1的数据强制转换成0(比较的两个数据相等),这样就无法完成比较交换的功能了:
int cmp_lf(const void* e1, const void* e2)
{
return (*(double*)e1 - *(double*)e2);
}
5. 结构体成员变量的排序
用qsort函数对结构体变量进行排序也是一个重点,有了上面四种类型排序的介绍,理解这个也不难,这里举两个例子以便大家理解
(1) 以字符串为比较对象
struct stu
{
char name[10];
int age;
double grade;
};
int cmp_name_Long(const void* e1, const void* e2)//以名字长度作比较
{
return strlen(((struct stu*)e1)->name) - strlen(((struct stu*)e2)->name);
}
int cmp_name_str(const void* e1, const void* e2)//以名字字典序作比较
{
return strcmp(((struct stu*)e1)->name,((struct stu*)e2)->name);
}
void print_name(struct stu* s, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s\n", (s + i)->name);//这里要注意用到了结构体成员变量的打印方法
} //结构体变量的地址->结构体成员变量
}
void test()//对名字字符串进行排序也可以自主改变字符串内容,多次测试查看效果
{
struct stu s[3] = { {"zhangsan",22,56.6},{"lisi",21,43.7},{"wangwu",15,33.3} };
int sz = sizeof(s) / sizeof(s[0]);
printf("交换前\n");
print_name(s, sz);
qsort(s, sz, sizeof(s[0]), cmp_name);
printf("交换后\n");
print_name(s, sz);
}
int main()
{
test();
return 0;
}
(2)以浮点型数据为比较对象
struct stu
{
char name[10];
int age;
double grade;
};
int cmp_grade(const void* e1,const void* e2) (2)以分数为标准排序
{
return (((struct stu*)e1)->grade - ((struct stu*)e2)->grade) > 0 ? 1 : -1;
}
void print_grade(struct stu* s, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%.2lf\n", (s + i)->grade);
}
}
void test()
{
struct stu s[3] = { {"zhangsan",22,56.6},{"lisi",21,43.7},{"wangwu",15,33.3} };
int sz = sizeof(s) / sizeof(s[0]);
printf("交换前\n");
print_grade(s, sz);
qsort(s, sz, sizeof(s[0]), cmp_grade);
printf("交换后\n");
print_grade(s, sz);
}
int main()
{
test();
return 0;
}
总结
到这里,就把C语言中的qsort函数简单的介绍完了,希望我的分享可以帮助大家。当然,还有一些更深层次的我还没有完全掌握,就点到为止了。谢谢大家的阅读,再见。