今天在鹏哥的B站上学习了库函数qsort,对这个函数有了一定的了解和认识
1.冒泡排序
学习这个库函数之前,回顾了一下冒泡排序
首先呢,我也不知道为啥它叫冒泡排序,可能这程序会吐泡泡?
咳咳咳,题外话也就这么一句,咱继续
9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
上面这个表格就是数组arr啦,咱们现在的需求就是让它变成升序
冒泡排序就是两个两个的进行比较,文字说的不清楚咱用图片表示一下
接下来用代码看看
void bubble_sort(int arr[], int sz)
{
int i = 0;
//冒泡排序的趟数
for (i = 0;i < sz - 1;i++)
{
//一趟冒泡排序
int j = 0;
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;
}
}
}
}
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[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr, sz);
bubble_sort(arr, sz);
print_arr(arr, sz);
return 0;
}
铛铛铛铛铛,闪亮登场
一看代码,so easy,一个main函数加上两个函数
来吧,咱一句一句看
创建了一个整型数组arr,存了9-0,一共10个数字
整型sz存了数组的元素个数
print_arr函数,顾名思义,打印一下数组,看看参数,一个arr,一个sz,arr这里是传了一个指针,指向数组首元素地址
现在进入print_arr了,嘿嘿嘿(狗头),因为不需要返回什么打印数组嘛,也就是把数组遍历一遍,这里的sz也是数组最后一个元素的下标,有第一个元素,和最后一个元素,for循环9次,也就把arr[0]-arr[9]打印出来了
ok接下来咱们就从print_arr出来进入bubble_sort函数啦,先看参数,也是一个arr和一个sz,进来喽,接下来就是要一步一步看啦,先是要进行sz-1轮也就是9轮冒泡排序,就用一个for循环,循环sz-1次,然后咱们就要用嵌套啦,然后咱们看看,第一轮的时候,咱们要sz-1-0次交换,第二轮就要sz-1-1次,最后一轮就要sz-1-8次,所以就是sz-1-i次啦,i其实就是数组的下标,0-8,这就是嵌套在里面的for循环,接下来就要判断一下,判断是不是前一个数大于后一个数,如果是,就让这两个数交换位置,通过用整型tmp来实现
然后出来,再通过函数print_arr打印一下,就好啦
当然冒泡排序有一些局限性,这时候,咱们就可以,试一试库函数,qsort啦
2.库函数qsort
void qsort
(
void* base,
size_t num,
size_t size,
int (*compar)(const void*, const void*)
)
通过网站cplusplus.com - The C++ Resources Network
将这个库函数的参数给查出来了,我不太会英语,所以通过一些翻译,以及视频的学习,大概了解了这个函数的参数类型
void* base,参数base,void*是无具体类型的地址,存放的是待排序数据中第一个对象的地址,也就是比较的可以是整型,可以是字符,可以是其他类型
size_t num num存放的是排序数据元素的个数,就是要排序的元素一共有多少个
size_t size 存放的是,排序数据中一个元素的大小,单位是字节
int (*compar)(const void*, const void*) 这是一个函数指针,指向参数为const void*, const void*,返回类型为int的函数,用来比较待排序数据中的2个元素的函数,也是一个回调函数
int compar(const void*p1, const void*p2),如果这里的两个指针是p1和p2,库函数qsort里面规定,如果返回一个<0的,p1指向的元素位于p2指向的元素之前,如果返回一个0,p1指向的元素等效于p2指向的,如果返回一个>0的,p1指向的元素位于p2之后
这里就要讲一下回调函数啦,回调函数就是,就是你把这个函数的地址(指针),传给另一个函数,另一个函数通过这个地址(指针)来调用这个函数
(1)数组排序
需求是使数组arr变为升序
#include<stdio.h>
#include<stdlib.h>
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0;i < sz;i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void test1()
{
//整型数据的排序
int arr[] = { 9,8,7,6,4,5,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
int main()
{
test1();
return 0;
}
这个代码就是用库函数qsort来排序整型数组arr
用库函数需要头文件,#include<stdlib.h>
接下来咱们看看,库函数qsort所需要的传参
第一个,待排序数据第一个元素的地址,咱们用arr,数组名就是数组首元素地址
第二个,元素个数,用sz=sizeof(arr)/sizeof(arr[0])
第三个,arr[0]就是数组中下标为0的元素,sizeof(arr[0]),就计算出了,数组中一个元素的大小
第四个,这里需要一个比较函数,首先,这个函数的参数,
返回类型要和 int (*compar)(const void*, const void*)一样
这样才能把函数地址传给他int cmp_int(const void* e1, const void* e2),起了个名字我们叫cmp_int
看这个里面的是e1和e2,这是两个指针,指针嘛,要比较,咱们就解引用,可是问题来了,他们的类型是void*,没法解引用啊,所以我们这里就要用到强制类型转换,将其转换为整型指针,
(int*)e1和(int*)e2 ,然后解引用就可以啦,然后返回e1指向的元素减e2指向的元素
这样就可以得到一个升序的数组啦
(2)结构体排序
首先定义一个结构体
struct Stu
{
char name[20];
int age;
};
定义一个学生结构体,里面有名字和年龄
int sort_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int sort_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
}
void test2()
{
//使用qsort函数排序结构体数据
struct Stu s[] = { {"zhangsan",30},{"lisi",34},{"wangwu",20} };
int sz = sizeof(s) / sizeof(s[0]);
//按照年龄来排序
qsort(s, sz, sizeof(s[0]), sort_by_age);
//按照名字来排序
qsort(s, sz, sizeof(s[0]), sort_by_name);
}
int main()
{
test(2);
return 0;
}
需求是通过名字和年龄来分别排序
分析一下,首先呢,定义了一个结构体数组s,存储了一些元素
int sz = sizeof(s) / sizeof(s[0])求出数组里面元素个数
库函数qsort里面存的前三个参数两个排序都一样
第一个,数组名,指向数组首元素地址
第二个,sz数组元素个数
第三个,sizeof(s[0]),数组中一个元素的大小
第四个,传两个进行比较的函数
其中第1个是通过年龄进行排序 int sort_by_age(const void* e1, const void* e2)
参数和返回类型和库函数qsort内置的一样
e1和e2是两个void*无类型指针,所以向将其强制类型转换为结构体指针在其前面加上(struct Stu*)
(struct Stu*)e1和(struct Stu*)e2,在结构体里面指向一个元素用的是->,咱们让其指向age,使得通过年龄来比较
变为((struct Stu*)e1)->age ,((struct Stu*)e2)->age,然后返回两者相减的值
另一个将age变为name就好,返回((struct Stu*)e1)->name ,((struct Stu*)e2)->name
3.冒泡排序实现库函数qsort
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0;i < width;i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0;i < sz - 1;i++)
{
int j = 0;
for (j = 0;j < sz - 1 - i;j++)
{
//两个元素比较
if (cmp((char*)base+j*width, (char*)base + (j+1) * width) > 0)
{
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
}
}
}
创建一个函数,bubble_sort返回类型为void
参数为
void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2)
与库函数qsort内置的函数类似,base是首元素地址,sz是元素个数,width是元素大小,cmp是函数指针,指向比较函数
进入函数bubble_sort,用冒泡排序实现,所以是,先是定义整型i,for循环i<sz-1,i++,也就是sz-1轮冒泡排序,然后内嵌for循环,定义整型j,for循环j<sz-1+i,也就是sz-1+i次冒泡排序,这些都和普通的冒泡排序一样
接下来就不一样啦,再里面的比较部分
if (cmp((char*)base+j*width, (char*)base + (j+1) * width) > 0)
{
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
cmp((char*)base+j*width, (char*)base + (j+1) * width)
这里就是参数里面的指针指向的函数cmp,也就是你给的,当然里面的参数有一点不一样,因为咱们要比较的可能是整型,也可能是字符等等,所以咱们要想个办法
突然想到咱们的char类型是一个字节,咱们先将base也就是传过来的首元素地址,强制类型转换为char*类型,然后传参传过来的width是元素大小,(char*)base+j*width就是第一个参数,当j=0的时候,他和原来传过来的指针相同,而第二个参数就是(char*)base + (j+1) * width,与第一个参数所差的就是传过来的元素大小
如果大于0进入if内部,和库函数qsort一样,大于0,p1就要和p2交换
这里面我们有创建了一个函数Swap用来交换,这里面要有三个参数
(char*)base + j * width, (char*)base + (j + 1) * width,width
也就是第一个元素,下一个元素,以及元素大小
在函数Swap里用char* buf1, char* buf2, int width来接收
在函数Swap里,把width当成了元素个数,buf1和buf2是两个字符指针,如果传过来一个整型比如,buf1和buf2之间差了四个字节,char是一个字节,用表格来表示就是
buf1 | buf2 |
每一个表格都是1个字节,左边四个其实是传过来的元素,右边四个是下一个元素,让他们一个字节一个字节的交换,总共交换元素的大小四个字节,也就是四次
buf1和buf2交换,然后都各自加一,因为是字符指针,所以移动一个字节,这样就可以把两个元素交换啦
然后咱们再把上面的使用库函数qsort的代码,拿下来,用bubble_sort来替代就可以啦