目录
7. 指向函数指针数组的指针
8. 回调函数
下面我们来复习一下冒泡排序:将数组arr中的元素排成升序。
普通做法:
改进后的做法:(多创建一个flag变量)
接下来我们介绍库函数qsort:
把冒泡排序函数改造成一个类似qsort的函数
指向函数指针数组的指针,是个指针,指向的是函数指针数组,里面存的是函数指针数组的地址。
回调函数是一个通过函数指针调用的函数 。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
比如,指针(2):函数指针中的例子:实现一个计算器。不是在主函数中直接调用加减乘除函数,而是将函数的地址作为参数传给calc函数,在calc函数中通过指针调用其所指向的对象(可以是加法函数,减法函数,乘法函数或除法函数)。这样做的好处是,因为作为参数传给calc的函数地址每次可以不同,这样在calc中就可以通过函数指针调用其所指向的不同的函数,calc函数中可以完成多个函数的调用。
int Add(int x,int y)
{
return x+y;
}
calc(Add);
void calc(int (*pf)(int,int))
{
pf(x,y); 就等于Add(x,y)或Sub(x,y)等,要根据主函数部分传的函数名是什么来决定
传的Add就用Add替换pf,传的Sub就用Add替换pf。
}
下面会通过一个例子详细了解回调函数的用途
首先,我们先回想一下冒泡排序,通过相邻两个元素的比较最终完成排序。但是冒泡排序只能用于整型数组的排序。
不足:
即使数组中的元素本来就是排好序的,也会进行9趟冒泡排序。
若数组中的元素进行一趟冒泡排序就排好序了,此代码仍会进行第二趟冒泡排序,一直进行9趟冒泡排序才会结束,代码效率很低。
#include
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;
}
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
只要排好序了,就能直接退出循环,不进行下一趟冒泡排序,代码效率高。
这需要创建一个变量flag,flag = 1;用来假设每一趟冒泡排序开始时数组元素是排好序的
比如,数组中的元素,本来就是排好序的(0,1,2,3,4,5,6,7,8,9),那么进行一趟冒泡排序就会发现没有一个元素需要交换,就可以直接结束循环,不进行下一趟冒泡排序。
再比如,数组中的元素只有一对不符合排序要求(0,1,2,4,3,5,6,7,8,9,),进行一趟冒泡排序后发现数组中的元素排好序了(0,1,2,3,4,5,6,7,8,9),那么当进行第二趟冒泡排序时,就会发现没有一个元素需要排序,那么就可以直接结束循环,不进行下一趟冒泡排序了。
具体做法如下:
先假设每一趟开始时是排好序的,flag=1;若假设不成立,就flag=0,不影响原本的代码,但是如果假设成立了,就能提前退出。减少代码重复无意义运算。这种思想特别好,可以用于需要循环中途跳出的情况。
代码实现:
#include
void bubble_sort(int arr[], int sz)
{
//趟数
int i = 0;
int count = 0;//计算进行了几趟冒泡排序
int flag;
for (i = 0; i < sz-1; i++)
{
flag = 1;//假设每一趟冒泡排序开始时数组元素是排好序的
//一趟冒泡排序的过程
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;
flag = 0;//这一趟冒泡排序有交换,说明还没变成排好序的,flag 置为0,还需在进行下一趟冒泡排序
}
}
count++;
if (flag == 1)//没有一个元素进行了交换,说明排好序了,可以退出循环了,不需要进行下一趟冒泡排序了
{
break;
}
}
printf("%d\n",count);
}
int main()
{
int arr[] = { 0,1,2,4,5,3,6,7,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
使用快速排序的思想实现的一个排序函数,这个函数可以排序任意类型的数据(默认是排成升序)
先来看一下最终代码:(升序)
#include
#include
//比较两个整形元素的比较函数
//e1指向一个整数
//e2指向另一个整数
int int_cmp(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);//e1>e2,返回的是>0的数,是升序
// return (*(int*)e2 - *(int*)e1);//同理,我想降序,就是e10的数才能降序
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), int_cmp);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
如何理解这个代码呢?
首先先来了解一下库函数qsort —— 这是一个排序函数
void qsort( void *base, base: 你要排序的数据的起始位置
size_t num, num: 待排序的数据元素的个数
size_t width, width: 待排序的一个数据元素的大小(单位是字节)
int ( *cmp )(const void *e1, const void *e2 ) );
函数指针,可以接收不同的函数地址
cmp: 这是一个比较函数的指针
且这个函数指针指向的函数返回类型是int,有两个参数,参数的类型是void*
因为qsort是个库函数,所以在这个函数内部到底是怎么进行排序的,这个过程是已经被设置好的,但是通过观察这个函数的函数声明,我们知道这个函数内部一定有一个地方进行了函数指针的调用,这个地方就是两个元素进行比较时。
因为函数指针接收了传过来的比较函数的地址,所以在qsort函数内部是可以通过函数指针来调用比较函数的。只是因为qsort函数不需要我们来写,我们看不到它内部是怎么实现的而已。
下面我们着重分析一下qsort函数的最后一个参数 int ( *cmp )(const void *e1, const void *e2 )
这是个函数指针,是用来接收比较函数的地址的。
因为qsort函数可以排序任意类型的数据,而不同类型的数据在排序的时候,两个元素的比较方式是不同的(整型可以用><来比较,结构体类型比较方式就不一样了)
我们需要qsort函数内部可以实现不同类型数据的比较,即我们需要qsort函数内部可以完成多个函数的调用。
所以,qsort函数参数部分是一个可以接收不同比较函数(整型的比较函数,结构体类型的比较函数)的地址的函数指针,这样,通过函数指针就可以调用其所指向的不同对象了。
我们要自己写的就是比较函数,这个比较函数的返回类型是 int ,有两个参数,参数的类型是void* 这个函数内部只需要实现比较的功能。
比如,我要这个比较函数实现的是两个整形元素的比较,我就可以这样写:
int int_cmp(const void* e1, const void* e2)
{
}
返回类型:
这个函数的返回类型是有规定的,e1指向的元素>e2指向的元素时(需要交换),返回>0的数;=时,返回0;<时,返回<0的数。这样做,最终的排序结果就是升序。
于此类推,我想要返回的结果是降序,那么我就让e1指向的元素
0的数;=时,返回0;>时,返回<0的数。这样做,最终的排序结果就是降序。
也就是说,只要不满足需要的顺序,需要交换,就返回>0
参数:
e1指向要比较的第一个元素,e2指向要比较的第二个元素,
e1是要比较的第一个元素的地址,e2是要比较的第二个元素的地址。
我们发现函数参数的类型是void*,那void*是个什么类型呢,它有什么特点呢?
void*是无具体类型的指针,这种指针可以接收任意类型的地址
又因为void*是无具体类型的指针,所以void*不能解引用操作,也不能+-整数
为什么这个地方要用void*的类型的指针来接收呢?
因为当我不知道传过来的是什么类型的地址时,我只能用void*类型的指针来接收
了解了这些,我们写一个降序排列整形元素的代码:
#include
#include
int int_cmp(const void* e1, const void* e2)
{
return (*(int*)e2 - *(int*)e1);
}
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), int_cmp);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
我们说qsort这个函数可以排序任意类型的数据,下面来排序一下结构体(默认是升序)
1.按名字排序
#include
#include
#include
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* e1, const void* e2)
{
//strcmp --> >0 ==0 <0
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
int main()
{
struct Stu s[] = { {"zhangsan", 15}, {"lisi", 30}, {"wangwu", 25} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
return 0;
}
2.按年龄排序
#include
#include
#include
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* e1, const void* e2)
{
//strcmp --> >0 ==0 <0
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int main()
{
struct Stu s[] = { {"zhangsan", 15}, {"lisi", 30}, {"wangwu", 25} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
return 0;
}
接下来我们把冒泡排序函数改造成一个类似qsort的函数,来进一步理解qsort函数。改造后的冒泡排序函数要可以排序任意类型的数据(默认是排成升序)
再来研究一下qsort这个函数参数的设计:
void qsort( void *base, base: 你要排序的数据的起始位置
size_t num, num: 待排序的数据元素的个数
size_t width, width: 待排序的一个数据元素的大小(单位是字节)
int ( *cmp )(const void *e1, const void *e2 ) );
cmp: 这是一个比较函数的指针
1、为什么base这个函数的类型是void*,因为 qsort函数在设计的时候,作者并不知道我们会使用qsort来排序什么类型的数据,所以不能写具体的某个类型,而void*可以接收任意类型的地址,所以用void*
2、我们在排序时,肯定要遍历一下我的数据,所以要知道元素个数
3、我们还需要知道一个元素是几个字节,我们已经知道了起始位置和元素个数,再知道一个元素占几个字节,我们就可以将这些元素一一找出来,然后就可以对数据进行操作了
4、函数指针,是为了通过函数指针调用其所指向的函数
好啦,我们可以开始改造啦。
代码如下:
#include
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 sum, int width, int (*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0; i < sum - 1; i++)
{
//一趟冒泡排序
int flag = 1;
int j = 0;
for (j = 0; j < sum - 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);
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
int int_cmp(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), int_cmp);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
return 0;
}