在学校,虽然我们“学习了c++”,但其实只是接触了它的一点皮毛。在近期个人学习中,qsort这个在学校从未讲授过的函数进入了我的视野。
qsort函数包含在cstdlib头文件中,其作用是排序任意类型数组中的数据。它的原型如下:
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
乍一看很吓人,我们不妨拆解来看。
它不返回值,只是在默默地完成排序工作。
它的第一个参数是一个通用类型指针,说白了就是任何类型的数据都可以丢进去。
第二个参数是numbers of items,待排序元素个数。可以用sizeof(arr)/sizeof(arr[0])处理好。
第三个参数是size of item,单个元素所占空间大小,可以用sizeof(arr[0])处理。
第四个参数是一个函数指针,这个函数的两个参数是两个通用类型指针,返回一个整数。这个整数的要求是:
如果compar返回值< 0,那么p1指向元素会被排在p2指向元素的左面;
如果compar返回值= 0,那么p1指向元素与p2指向元素的顺序不确定。
如果compar返回值> 0,那么p1指向元素会被排在p2指向元素的右面。
所以,要完成不同类型数据的通用排序函数,关键功夫是在cmp函数。
int cmp_int(const void* e1, const void* e2) {//整数版本的cmp函数
return *(int*)e1 - *(int*)e2;
}
我们可以看看整数版本的cmp函数。首先,根据qsort函数的要求,cmp函数必须返回一个整数,因此固定返回值为int。我们转回到cmp的文字定义,它要求cmp<0则e1指向e2左边去。
由此就可以选择排序方法:升序or降序?如果我们规定升序,即越小越往前排,那么e1指向e2左边就说明e1指向的值应该小于e2指向的值,也就是说符合升序的要求。此时要求cmp<0,那我们很容易想到现在e1所指向的值-e2所指向的值<0,用它做返回值再合适不过了。
然而cmp函数传参时,两个参数是(void*)类型的,是通用类型指针,不能做解引用操作。所以,为了获取我们需要的整型的数据,应该将(void*) 强制转换成(int *),然后解引用操作后,返回两个整数的差。也就是:return *(int*)e1 - *(int*)e2;
这样就编写好了符合条件的cmp_int函数。
下面定义一个整形数组,填上数据,调用qsort函数对其排序,打印数组,有:
#include<iostream>
#include<cstdlib>
using namespace std;
int cmp_int(const void* e1, const void* e2) {
return *(int*)e1 - *(int*)e2;
}
int main() {
int arr1[] = { 5,7,8,9,1,2,0,3,4,4 };
qsort(arr1, sizeof(arr1) / sizeof(int), sizeof(int), cmp_int);
for (int i = 0; i < sizeof(arr1) / sizeof(int); i++) {
cout << *(arr1 + i) << " ";
}
cout << endl;
}
运行结果的确是一个升序数组,为:
如果把cmp函数的返回值处调换*(int*)e1 - *(int*)e2;
为*(int*)e2 - *(int*)e1;
,就完成了从升序到降序的转变。
#include<iostream>
#include<cstdlib>
using namespace std;
int cmp_int(const void* e1, const void* e2) {
return *(int*)e2 - *(int*)e1;
}
int main() {
int arr1[] = { 5,7,8,9,1,2,0,3,4,4 };
qsort(arr1, sizeof(arr1) / sizeof(int), sizeof(int), cmp_int);
for (int i = 0; i < sizeof(arr1) / sizeof(int); i++) {
cout << *(arr1 + i) << " ";
}
cout << endl;
}
那么能否用它排序其他类型的数据呢?浮点型,字符串,甚至是结构体或类?
只要给出合适的cmp函数,就都可以排序!
在此给出字符串的cmp函数:
int cmp_char(const void* e1, const void* e2) {
return *(char*)e1 - *(char*)e2;
}
因为处理字符时是按照ASCII码进行的,因此返回ASCII码的差值就可以,不用做修改。
double类型的cmp函数:
int cmp_double(const void* e1, const void* e2) {
if (*(double*)e1 - *(double*)e2 > 0)return 1;
else if (*(double*)e1 - *(double*)e2 < 0) return -1;
else return 0;
}
这次返回值不能用(*(double*)e1 - *(double*)e2;
了。其原因在于,原先形态的返回值其实是一个double型数据,而从double类型转变到int类型是可能丢失数据的。如果两个double型的差值在±1之内,转为int型就直接成为0了!那cmp函数的本意就被破坏掉了,因此,我们换一种返回法,只要大于0统统输出1,只要小于0统统输出-1,这样就规避了类型转换丢失数据的问题,也符合cmp函数的要求。
如果我们想要定义一个类呢?就拿三国时期的几大政权来生成一个类吧:
我们定义了一个leaders类,它有三个公共属性:国祚,国名,省份数量。在主函数中,我们用一个数组初始化了三个实例。下面,我们分别按国祚,国名,省份数量对它们进行了排序。它们的cmp函数编写与自带的数据类型的编写方法很相似。
class leaders {
public:
int Age;
char Name[20];
int states;
};
int cmp_Age(const void* e1, const void* e2) {
return ((class leaders*)e1)->Age - ((class leaders*)e2)->Age;
}
int cmp_states(const void* e1, const void* e2) {
return ((class leaders*)e1)->states - ((class leaders*)e2)->states;
}
int cmp_Name(const void* e1, const void* e2) {
return ((class leaders*)e1)->Name - ((class leaders*)e2)->Name;
}
int main(){
leaders arr4[3] = { {50,"wei",7},{45,"shu",2}, {35,"wu",4} };
qsort(arr4, sizeof(arr4) / sizeof(class leaders), sizeof(class leaders), cmp_Age);
qsort(arr4, sizeof(arr4) / sizeof(class leaders), sizeof(class leaders), cmp_states);
qsort(arr4, sizeof(arr4) / sizeof(class leaders), sizeof(class leaders), cmp_Name);
for (int i = 0; i < sizeof(arr4) / sizeof(class leaders); i++) {
cout << arr4[i].Age<< " ";
}
cout << endl;
for (int i = 0; i < sizeof(arr4) / sizeof(class leaders); i++) {
cout << arr4[i].states << " ";
}
cout << endl;
for (int i = 0; i < sizeof(arr4) / sizeof(class leaders); i++) {
cout << arr4[i].Name << " ";
}
}
下面我把所有类型的排序一起写出,供大家参考:
#define PI 3.1416
#include<iostream>
#include<cstdlib>
using namespace std;
class leaders {
public:
int Age;
char Name[20];
int states;
};
int cmp_int(const void* e1, const void* e2) {
return *(int*)e1 - *(int*)e2;
}
int cmp_double(const void* e1, const void* e2) {
if (*(double*)e1 - *(double*)e2 > 0)return 1;
else if (*(double*)e1 - *(double*)e2 < 0) return -1;
else return 0;
}
int cmp_char(const void* e1, const void* e2) {
return *(char*)e1 - *(char*)e2;
}
int cmp_Age(const void* e1, const void* e2) {
return ((class leaders*)e1)->Age - ((class leaders*)e2)->Age;
}
int cmp_states(const void* e1, const void* e2) {
return ((class leaders*)e1)->states - ((class leaders*)e2)->states;
}
int cmp_Name(const void* e1, const void* e2) {
return ((class leaders*)e1)->Name - ((class leaders*)e2)->Name;
}
int main() {
int arr1[] = { 5,7,8,9,1,2,0,3,4,4 };
char arr2[] = "ajhhcjhwcwauc";
double arr3[] = { 1.5,2.3,410.7,14.015,PI };
leaders arr4[3] = { {50,"wei",7},{45,"shu",2}, {35,"wu",4} };
qsort(arr1, sizeof(arr1) / sizeof(int), sizeof(int), cmp_int);
qsort(arr2, sizeof(arr2) / sizeof(char), sizeof(char), cmp_char);
qsort(arr3, sizeof(arr3) / sizeof(double), sizeof(double), cmp_double);
qsort(arr4, sizeof(arr4) / sizeof(class leaders), sizeof(class leaders), cmp_Age);
qsort(arr4, sizeof(arr4) / sizeof(class leaders), sizeof(class leaders), cmp_states);
qsort(arr4, sizeof(arr4) / sizeof(class leaders), sizeof(class leaders), cmp_Name);
for (int i = 0; i < sizeof(arr1) / sizeof(int); i++) {
cout << *(arr1 + i) << " ";
}
cout << endl;
for (int i = 0; i < sizeof(arr2) / sizeof(char); i++) {
cout << *(arr2 + i) << " ";
}
cout << endl;
for (int i = 0; i < sizeof(arr3) / sizeof(double); i++) {
cout << *(arr3 + i) << " ";
}
cout << endl;
for (int i = 0; i < sizeof(arr4) / sizeof(class leaders); i++) {
cout << arr4[i].Age<< " ";
}
cout << endl;
for (int i = 0; i < sizeof(arr4) / sizeof(class leaders); i++) {
cout << arr4[i].states << " ";
}
cout << endl;
for (int i = 0; i < sizeof(arr4) / sizeof(class leaders); i++) {
cout << arr4[i].Name << " ";
}
}
结果如图,qsort函数很好地帮助我们完成了对各个类型数据数组的升序排序。如果反转return的表达式 ,就可以完成降序排列了。