在对数组进行排序的时候,很多的初学者都是使用冒泡排序,但是冒泡排序的效率却不高,而且还需要自己动手,没错我就是懒,并只能排序数字类型的数据
而qsort基本上所有的数组都能够排序,可见其强大;
冒泡排序如下:
#include <stdio.h>
//冒泡排序函数
int* bubble_sort(int* arr, int sz)
{
for (int i = 0; i < sz - 1; i++)
{
for (int j = 0; j < sz - i - 1; j++)
{
if (*(arr + j) > *(arr + j + 1))
{
int tmp = *(arr + j);
*(arr + j) = *(arr + j + 1);
*(arr + j + 1) = tmp;
}
}
}
}
//打印数组函数
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[5] = { 1,45,2,62,3 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
print_arr(arr, sz);
return 0;
}
如果我们使用的是库函数qsort呢。
qsort函数(快速排序函数)
使用qsort函数需要导入 stdlib.h
这个头文件
这是qsort函数的组成
void qsort( void * base ,
size_t num ,
size_t width ,
int(__cdecl * compare )(const void* elem1 , const void* elem2));
从以上可以看到,如果我们想要调用这个函数,需要有四个参数:
void* base
是我们要排序数组的指针。
size_t num
是我们要导入数组的数组长度,单位是byte。
size_t width
是数组单个字节的宽度;比如:char类型的数据宽度是1,int类型的宽度是4。
int(__cdecl * compare )(const void* elem1 , const void* elem2)
是一个函数指针,注意函数实参都必须是void*
类型的。在这个函数指针中会进行比较:看下图
可能大家看上面的解释觉得很苦涩,我们直接在代码中学习吧
#include <stdio.h>
#include <stdlib.h>
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2; //我们比较的是int类型的数组,先强制类型转换为(int*)类型
//如果是整型类的数据,我们可以直接用相减来判断我们返回的值;
//如果e1比e2大,则返回大于零,如果小,就是小于0,相等的话就是0;
}
int main()
{
int arr[10] = { 1,3,5,7,9,0,2,4,6,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
return 0;
}
在第七行我们定义了一个数组,然后第八行求出了整个数组的长度,
第九行使用qsort函数 , 在函数中(函数首元素地址,数组长度,数组宽度,函数指指针)
注意函数指针cmp_int
是我们由创建的,并且其中的参数是固定的,返回类型必须是int
,我们要排序什么类型的数组,就要在 这个函数中强制转换。
现在我们来一些深入的内容
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//我们定义的结构体,代表考试的成绩
struct Exam {
char name[20];
float math;
int sum;
};
//比较 数学 成绩的函数
int cmp_math(const void* e1, const void* e2) //float类型的数值是不能相减的,会照成精度丢失
{ //所以采用原始一些的方法:比较
if (((struct Exam*)e1)->math > ((struct Exam*)e2)->math) //注意,因为是结构体类型,所以要转化为结构体指针
return 1;
else if (((struct Exam*)e1)->math < ((struct Exam*)e2)->math)
return -1;
else
return 0;
}
//比较 总成绩 的函数
int cmp_sum(const void* e1, const void* e2)
{
return ((struct Exam*)e1)->sum - ((struct Exam*)e2)->sum;//整数类型采用相减
}
//比较 名字 的函数
int cmp_name(const void* e1, const void* e2)
{
return strcmp(((struct Exam*)e1)->name, ((struct Exam*)e2)->name);//字符串的话我们使用了一个strcmp的库函数
} //需要引入头文件 string.h
//打印数组
void print_arr(struct Exam arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%s %f %d\n", arr[i].name, arr[i].math, arr[i].sum);
}
}
//函数主体
int main()
{
struct Exam arr[] = { {"lihua",85.6f,253} ,{"huiyun",99.9f,299},{"zhangli",86.6f,266} };
int sz = sizeof(arr) / sizeof(arr[0]);
//my_qsort(arr, sz, sizeof(arr[0]), cmp_name); //排序名字
qsort(arr, sz, sizeof(arr[0]), cmp_math); //排序数学成绩
//qsort(arr, sz, sizeof(arr[0]), cmp_sum); //排序总成绩
print_arr(arr, sz);
return 0;
}
我们先打印 math 的排序,其他类型也一样,
当能看懂这个函数时,我们就可以再次深入一些,复现qsort
函数
复现qsort
函数
我所复现的qsort
函数和原函数的排序方法是不同的, 原函数使用的是快速排序,我使用的是冒泡方法来构造这个函数。
因为我们的qsort
函数时所有类型都能排序的,那它接收的类型是什么呢?没错就是 void*
,我们看下void*
的定义
void* 是一种无类型的指针,无具体类型的指针
void* 的指针变量可以存放任意类型的地址
void* 的指针不能直接进行解引用操作
void* 的指针不能直接进行+—整数
总而言之,void*
就像一个垃圾桶,什么类型的指针都可以往里扔。
直接看代码,还是沿用了之前的代码,我就是懒,但并不是所有代码,只是核心代码,我们分析这里即可。
#include <stdio.h>
#include <string.h>
struct Exam {
char name[20];
float math;
int sum;
};
//我们传入的类型现在已经是固定的 char* 了,所以指针类型在这只能用 char* 接收
//我们在这个函数中是一个字节一个字节的交换,并不是整个元素的交换,因为只有这样,才能适用所有类型
void Swap(char* p1, char* p2,int width)
{
for (int i = 0; i < width; i++)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
//这就是我们自己构造的 my_qsort 函数
//1.得到数组长度 sz,因为我们需要得到的边间
//2.得到元素宽度 width,因为只有得到了元素的宽度才能在数组内进行跳转。
//3.得到数组的首元素地址void * base,注意是void* 因为只有得到了首元素地址,我们才能结合元素宽度,得到数组中每个元素的相隔的距离,
//4.的到函数指针 int (*cmp)(const void* e1, const void* e2) ,只有得到函数指针,才能将我们的数组中的每个元素进行比较
void my_qsort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
for (int i = 0; i < sz - 1; i++)
{
for (int j = 0; j < sz - i - 1; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) //将它们都强制换化为 char* 类型。 是因为 char* 是最小的字节,而且每次加1的话,只会移动一个字节
{ //而将 j *width 是因为width的宽度是随着我们导入的类型决定的,这里比较的像是 a[j]>a[j+1]
Swap((char*)base + j * width, (char*)base + (j + 1) * width,width); //这个就是交换函数了,同时也限定了交换的范围
}
}
}
}
以上就是我们核心代码的分析,以是我尽力而为,若有不懂,可以私信我,免费解答
接下来我们运行下所有代码,也就只是加上了,那些比较函数
#include <stdio.h>
#include <string.h>
struct Exam {
char name[20];
float math;
int sum;
};
void Swap(char* p1, char* p2,int width)
{
for (int i = 0; i < width; i++)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
void my_qsort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
for (int i = 0; i < sz - 1; i++)
{
for (int j = 0; j < sz - i - 1; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
}
}
}
int cmp_math(const void* e1, const void* e2)
{
if ( ((struct Exam*)e1)->math > ((struct Exam*)e2)->math) //因为score里的数据是float类型的,所以不能相减
return 1; // 若 0.9- 0.8 = 0.1,而我们的返回类型是int型,返回的值就会变成int
else if (((struct Exam*)e1)->math < ((struct Exam*)e2)->math)//使用这里不能使用减号,要使用关系运算符(也就是 比较 )
return -1;
else
return 0;
}
int cmp_sum(const void* e1, const void* e2)
{
return ((struct Exam*)e1)->sum - ((struct Exam*)e2)->sum;
}
int cmp_name(const void* e1, const void* e2)
{
return strcmp(((struct Exam*)e1)->name, ((struct Exam*)e2)->name);
}
void print_arr(struct Exam arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%s %f %d\n", arr[i].name,arr[i].math,arr[i].sum);
}
}
int main()
{
struct Exam arr[] = { {"lihua",85.6f,253} ,{"zhangli",86.6f,266},{"huiyun",99.9f,299} };
int sz = sizeof(arr) / sizeof(arr[0]);
my_qsort(arr, sz, sizeof(arr[0]), cmp_name);
//my_qsort(arr, sz, sizeof(arr[0]), cmp_math);
//my_qsort(arr, sz, sizeof(arr[0]), cmp_sum);
print_arr(arr, sz);
return 0;
}
这次我们运行的是 name
的排序:
字符串排序是根据ascii码表来排序的。
本次的分享就到这里,若有错误,欢迎大家指正,若此问题不懂,可以私信我,无偿解答。
愿你我都能在编程的道路上越走越远!!!!