我们学习指针有了一段时间了,今天我们来继续学习与指针有关的一个函数:qsort函数。
目录
1.回调函数
老规矩,在学习之前是要有一些铺垫的,我们今天要学习的qsort函数与指针中的回调函数有关联,所以我们先介绍一下回调函数。
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
void calc(int(*pf)(int, int))
{
int ret = 0;
int x, y;
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("ret = %d\n", ret);
}
int main()
{
int input = 1;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
calc(add);
break;
case 2:
calc(sub);
break;
case 3:
calc(mul);
break;
case 4:
calc(div);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
看完了代码,是不是对回调函数有了一定的理解,大家还记得我们之前在学习函数的时候讲过函数的嵌套调用和函数的链式访问,我们在这里将他们三个区分一下:
回调函数:通过函数指针调用的函数,也就是将一个函数作为另一个函数的参数。
嵌套调用:在一个函数内部使用了另一个函数,但函数不作为另一个函数的参数。
链式访问:将一个函数的返回值作为另一个函数的参数。
好的,回调函数先说到这里,让我们继续往下学习:
2.qsort函数
现在我们进入正题,来学习qsort函数。qsort函数是一个排序函数,它可以对各种各样类型的数组进行排序,达到想要的效果。
我们在c++网站上可以搜索到qsort函数;
这一段翻译过来说的是qsort函数对数组进行排序 ,我们可以看到qsort函数有4个参数,分别是:
1.数组首元素地址
因为qsort函数要排序各种类型的数组,所以使用void*指针来接收数组首元素地址
2.数组的元素个数
3.数组每个元素的大小
4.根据数组元素类型提供的比较方法函数
针对比较方法函数,返回值是整形,通过返回值来判断比较的两个数组元素的大小关系
而函数没有返回值,只是对数组进行了排序。
在c++网站中,给了一个qsort的使用示例,我们来看一下:
#include <stdio.h> /* printf */
#include <stdlib.h> /* qsort */
int values[] = { 40, 10, 100, 90, 20, 25 };
int compare(const void* a, const void* b)
{
return (*(int*)a - *(int*)b);
}
int main()
{
int n;
qsort(values, 6, sizeof(int), compare);
for (n = 0; n < 6; n++)
printf("%d ", values[n]);
return 0;
}
首先,我们可以看到qsort函数的调用需要引用头文件<stdlib.h>,然后根据整形数组自定义了一个比较方法函数,可以看到返回值>0时,有a>b;返回值<0时,有a<b;返回值=0时,有a=b。这就是一个简单的qsort函数的调用。
那么,如果想对结构体进行排序要怎么做呢?
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int com_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Student*)e1)->name, ((struct Student*)e2)->name);
}
int com_by_age(const void* e1, const void* e2)
{
return ((struct Student*)e1)->age - ((struct Student*)e2)->age;
}
struct Student
{
int age;
char name[20];
};
int main()
{
struct Student arr[] = { {15,"zhangsan"},{16,"lisi"},{14,"wangwu"}};
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
qsort(arr, sz, sizeof(arr[0]), com_by_name);
qsort(arr, sz, sizeof(arr[0]), com_by_age);
return 0;
}
对于结构体的比较,应该是从结构体的不同成员来进行比较
对字符串进行比较,我们使用strcmp函数来比较两个字符串的大小,而对于strcmp函数,它实际上对两个字符串的每个字符的ASCII码值进行比较,若相等,则比较下一组字符。它的返回值正好与qsort函数规定的比较方法函数的返回值相同。
而对于整形比较,我们只需要返回两个数相减后的结果就能达到比较方法函数对返回值的要求。
3.模拟实现qsort函数
说了这么多,如果我们想自己写出一个qsort函数,应该怎么来呢?
我们首先思考一下qsort函数的实现方法,对于一个给定类型的数组,我们想对其进行排序,可以使用什么方法?
我们前面刚刚学到了冒泡排序,那么我们就在冒泡排序的基础上将其改造成一个qsort函数。
在冒泡排序中,两层循环中是对相邻两元素的比较方法,然后发生两个数据的交换,那么如果想实现任意数据的比较和交换,应该怎么做呢?
我们来看具体的实现代码:
void swap(void* e1, void* e2, int size)
{
int i = 0;
for(i = 0; i < size; i++)
{
char tmp = *((char*)e1 + i);
*((char*)e1 + i) = *((char*)e2 + i);
*((char*)e2 + i) = tmp;
}
}
void bubble_qsort(void* base, int count, int size, int(*cmp)(void*, void*))
{
int i = 0, j = 0;
for (i = 0; i < count; i++)
{
for (j = 0; j < count-1-i; j++)
{
if (cmp((char*)base+j * size, (char*)base+(j + 1) * size) > 0)
{
swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
}
}
}
}
我们可以看到在两层循环的内部有一个比较方法,将base强制转化为char*类型可以保证在加减整数时跳过正常的字节数(+1跳过1个字节),后面加上了j*size表示数组的第j个元素,则进行比较的就是数组的第j个元素和第j+1个元素,在比较之后,swap函数就是进行交换。
我们知道,在所有类型的数据中最小的类型就是char类型了,所以对于两个int类型的数据,我们要交换他们,可以将其转化为char类型并交换四次,同理,我们对char类型的数据交换size次就达到了交换两个类型大小为size的数据。
当然,比较方法函数还是要我们根据比较的类型进行改写。
本篇对qsort函数的介绍就结束了,我们下篇文章再见啦。