1.qsort函数
(1).概念
qsort函数是一个库函数,头文件为stdlib.h,用来排序的,可以排序任何类型的数据,它排序的底层原理是快速排序。
(2).函数定义
void qsort (void* base,
size_t num,
size_t size,
int (*compar)(const void*,const void*));
参数:
- void* base, //void*指针可以存放各种类型指针,指向需要排序数组首元素的地址
- size_t num, //数组元素的个数num,数组元素大于0,所以是size_t无符号类型;通常计算数组大小是使用sizeof(数组的地址)/sizeof(数组元素大小),但是这里只有数组首元素的地址,所以必须告诉qsort函数数组大小
- size_t size,//数组元素的大小size(单位:字节),在排序时需要找到两个元素进行比较,有了元素的大小,就知道指针一次要访问多少个字节,就能准确找到元素。
- int (*compar)(const void*,const void*));//比较函数的函数指针 ,不同数据的比较方法不同比较方法,方法是需要我们自己来实现的,将判断方法封装成函数,再将函数的地址传给qsort函数,此外函数的返回值也要满足qsort函数给定的规定
这个比较函数我们在实现时,返回值需要满足如果前面的>后面的,就返回大于0的数,就为真就会交换;等于就返回0,小于就返回小于0的数,就为假不会交换。
(3).函数的使用
A.qsort函数排序整型数组元素
数组元素升序,先来实现一下比较函数:
int int_compar(const void* e1, const void* e2)//整形比较 { return (*(int*)e1) - (*(int*)e2);//使用前先将void*指针转为int*,才能进行解引用 //前者>后者,return > 0;//前者=后者,return = 0;//前者<后者,return < 0; }
再来看下主程序:
#include<stdio.h> #include<stdlib.h> int int_compar(const void* e1, const void* e2) { return (*(int*)e1) - (*(int*)e2); } int main() { int arr[10] = { 1,2,3,7,6,99,23,34,54,65 }; int sz = sizeof(arr) / sizeof(arr[0]); //升序,需要首元素地址,长度,元素大小,比较函数指针 qsort(arr, sz, sizeof(int), int_compar); for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
来看一下结果:
可见,已经帮我们排好序了,使用qsort函数更加方便,只需要写好比较函数就能排序了。
B.qsort函数排序结构体
先来看下这个结构体:
struct Stu { char name[100]; int age; };
这个结构体成员变量分别是名字和年龄,那么有两种就会有比较方式,比较名字和比较年龄
比较名字:
#include<stdio.h> #include<stdlib.h> #include<string.h> struct Stu { char name[100]; int age; }; //按照名字排序,比较字符 int char_compar(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); //字符串比较函数,头文件为string.h,返回条件与qsort所需条件相同 } int main() { struct Stu S[3] = { {"huangzhe",20},{"oujiahui",21},{"ouzhongmin",19} }; int sz = sizeof(S) / sizeof(S[0]); //这里需要结构体数组首元素地址,结构总长度,结构体元素大小,字符比较函数 qsort(S, sz, sizeof(S[0]), char_compar); for (int i = 0; i < sz; i++) { printf("%s,%d\n", S[i].name ,S[i].age); } return 0; }
来看一下结果:
strcmp函数比较字符串是按照对应字符的ASCII码值进行比较,>0返回>0,<0返回<0,=0返回0。
比较年龄:
#include<stdio.h> #include<stdlib.h> #include<string.h> struct Stu { char name[100]; int age; }; //按照年龄排序升序,比较整形 int int_compar(const void* e1, const void* e2) { return (((struct Stu*)e1)->age) - (((struct Stu*)e2)->age); } int main() { struct Stu S[3] = { {"huangzhe",20},{"oujiahui",21},{"ouzhongmin",19} }; int sz = sizeof(S) / sizeof(S[0]); //这里需要结构体数组首元素地址,结构总长度,结构体元素大小,整形比较函数 qsort(S, sz, sizeof(S[0]), int_compar); for (int i = 0; i < sz; i++) { printf("%s,%d\n", S[i].name, S[i].age); } return 0; }
来看一下结果:
2冒泡排序模拟实现qsort函数
(1).冒泡排序
原理:
n个元素比较n-1轮,剩下的那个元素就不用比较了;每一轮两两相互比较,只需要比较n-1次,每比较一轮,就少一个数,第i轮只需要比较n-1-i次。
具体代码以及实现:
#include<stdio.h> void bubble_sort(int arr[10],int sz) { int i, j; //排序 for (i = 0; i < sz - 1; i++) { for (j = 0; j < sz - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } } int main() { int arr[10] = { 5,6,3,4,1,8,9,1,2,7 }; int sz = sizeof(arr) / sizeof(arr[0]); //冒泡排序 bubble_sort(arr, sz); //打印 for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
结果:
可见已经升序。
(2).改进过程
参数:
现在对这个冒泡排序进行修改,使得它能排序任何数据
根据qsort的实现原理结合bubble_sort函数的缺陷,可以发现面对的问题:
- 1.只能排序整形数据,不能接受任意类型的数据
- 解决方法:使用void*的指针接收需要排序的首元素的地址,void* base;
- 存在问题:void*的指针不能进行解引用,加减运算 --> 导致不知道元素个数,不知道元素的类型
- 2.要知道元素的个数
- 解决方法:使用size_t类型接收元素个数,size_t sz;
- 3.不知道元素的类型,就不知道指针一次读取几个字节,就找不到元素,无法比较
- 解决方法:使用size_t类型接收元素所占字节数 size_t size;
- 4.判断不同类型的数据的判断方法不同
- 解决方法:将判断方法封装成函数,满足条件返回大于0的数(真),小于0返小于0,等于0返回等于0(<=0为假)
那么新的函数定义:
void my_qsort(void* base, size_t sz, size_t size, int (*compar)(const void* e1,const void* e2));
结合新的函数定义以及新的参数对原来的bubble_sort函数的函数体进行分析:
发现问题:
1.if (arr[j] > arr[j + 1])//if的判断条件需要改
改后:if (compar(元素1的首地址,元素2的首地址))//调用判断函数
2.首元素的首地址为base,下一个元素的地址是什么?*****
- 不就是首元素跳过一个元素,对应的就是地址跳过一个元素的字节数
- 而元素的字节数就是size,那么首地址base只需要加上size就行了
- 但是size是个数值,指针+数值==指针+数值*指针所指向的数据类型字节数
- 那么我们就要保证指针所指向的数据类型字节数为1就行了,就只需要把void*的指针强制类型转换为char*
改后:
if(compar((char*)base,(char*)base+size))
3.数据交换这里也需要改*****
现在我们知道两个元素的首地址,和元素的大小,我们只需要将每个元素的每个字节的数值,进行交换就行,封装成函数后:
void Swap(char* p1, char* p2,size_t size) { int n; for (n = 0; n < size; n++)//每个字节进行交换 { char tmp = *p1; *p1 = *p2; *p2 = tmp; p1++;//交换之后地址加1,来到下一个字节的地址 p2++; } }
最后改完的bubble_sort函数得到我们自己的my_qsort函数:
改后代码:
void Swap(char* p1, char* p2,size_t size) { int n; for (n = 0; n < size; n++) { char tmp = *p1; *p1 = *p2; *p2 = tmp; p1++; p2++; } } void my_qsort(void* base, size_t sz, size_t size, int (*compar)(const void* e1, const void* e2)) { int i, j; //排序 for (i = 0; i < sz - 1; i++) { for (j = 0; j < sz - 1 - i; j++)//冒泡排序的思想不需要改,即两个for循环 { if (compar((char*)base + size * j, (char*)base + size * (j + 1)) > 0)//if的判断条件需要改 { //数据交换函数 Swap((char*)base + size * j, (char*)base + size * (j + 1), size); } } } }
(3).函数的使用
最后对我们的my_qsort函数进行使用验证:
A.qsort函数排序整型数组元素
代码:
#include<stdio.h> void Swap(char* p1, char* p2,size_t size) { int n; for (n = 0; n < size; n++) { char tmp = *p1; *p1 = *p2; *p2 = tmp; p1++; p2++; } } void my_qsort(void* base, size_t sz, size_t size, int (*compar)(const void* e1, const void* e2)) { int i, j; //排序 for (i = 0; i < sz - 1; i++) { for (j = 0; j < sz - 1 - i; j++)//冒泡排序的思想不需要改,即两个for循环 { if (compar((char*)base + size * j, (char*)base + size * (j + 1)) > 0)//if的判断条件需要改 { //数据交换函数 Swap((char*)base + size * j, (char*)base + size * (j + 1), size); } } } } //整形比较函数 int int_compar(const void* e1, const void* e2) { return (*(int*)e1) - (*(int*)e2); } int main() { int arr[10] = { 5,6,3,4,1,8,9,1,2,7 }; int sz = sizeof(arr) / sizeof(arr[0]); my_qsort(arr, sz, sizeof(arr[0]), int_compar); //打印 for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
来看一下结果:
成功了!!!!!!
B.qsort函数排序结构体
先直接看结果:
比较名字:
比较年龄:
代码:
比较名字:
#include<stdio.h> #include<stdlib.h> #include<string.h> struct Stu { char name[100]; int age; }; void Swap(char* p1, char* p2, size_t size) { int n; for (n = 0; n < size; n++) { char tmp = *p1; *p1 = *p2; *p2 = tmp; p1++; p2++; } } void my_qsort(void* base, size_t sz, size_t size, int (*compar)(const void* e1, const void* e2)) { int i, j; //排序 for (i = 0; i < sz - 1; i++) { for (j = 0; j < sz - 1 - i; j++)//冒泡排序的思想不需要改,即两个for循环 { if (compar((char*)base + size * j, (char*)base + size * (j + 1)) > 0)//if的判断条件需要改 { //数据交换函数 Swap((char*)base + size * j, (char*)base + size * (j + 1), size); } } } } //按照名字排序,比较字符 int char_compar(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); } int main() { struct Stu S[3] = { {"oujiahui",21},{"huangzhe",20},{"ouzhongmin",19} }; int sz = sizeof(S) / sizeof(S[0]); // my_qsort(S, sz, sizeof(S[0]), char_compar); for (int i = 0; i < sz; i++) { printf("%s,%d\n", S[i].name, S[i].age); } return 0; }
比较年龄:
#include<stdio.h> #include<stdlib.h> #include<string.h> struct Stu { char name[100]; int age; }; void Swap(char* p1, char* p2, size_t size) { int n; for (n = 0; n < size; n++) { char tmp = *p1; *p1 = *p2; *p2 = tmp; p1++; p2++; } } void my_qsort(void* base, size_t sz, size_t size, int (*compar)(const void* e1, const void* e2)) { int i, j; //排序 for (i = 0; i < sz - 1; i++) { for (j = 0; j < sz - 1 - i; j++)//冒泡排序的思想不需要改,即两个for循环 { if (compar((char*)base + size * j, (char*)base + size * (j + 1)) > 0)//if的判断条件需要改 { //数据交换函数 Swap((char*)base + size * j, (char*)base + size * (j + 1), size); } } } } //按照年龄排序升序,比较整形 int int_compar(const void* e1, const void* e2) { return (((struct Stu*)e1)->age) - (((struct Stu*)e2)->age); } int main() { struct Stu S[3] = { {"oujiahui",21},{"huangzhe",20},{"ouzhongmin",19} }; int sz = sizeof(S) / sizeof(S[0]); my_qsort(S, sz, sizeof(S[0]), int_compar); for (int i = 0; i < sz; i++) { printf("%s,%d\n", S[i].name, S[i].age); } return 0; }
验证结束了,是正确的呀!!!!!!
本章内容结束了,我们下章见,拜拜!!!