函数指针是将函数的地址取出来,再通过函数地址去调用,那为什么不直接用函数名调用呢??原因是因为函数指针可以用来实现回调函数,而回调函数有自己的应用场景。
回调函数就是⼀个通过函数指针调⽤的函数。 如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数 时,被调⽤的函数就是回调函数。
#include
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;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
以上这段代码中,我们发现case部分的代码总是重复出现,这段代码只有调用函数的逻辑有差异(但是函数的返回类型和形参是一样的),其他输入输出操作都是冗余的,那么这个时候我们可以把调用的函数地址以参数的形式传去,用函数指针接收,函数指针指向什么函数就调用什么函数,这里其实就是使用的回调函数功能。
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(" 0:exit \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;
}
回调函数不是由该函数的实现方直接调⽤,⽽是在特定的事件或条 件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。
怎么理解上面这段话呢?我们可以发现回调函数并非直接调用的,而是当需要进行某种运算时(特定需求的发生),根据需求将函数地址传给pf,然后在calc(另外一方)函数中通过pf(间接调用)来调用这个函数。
前面学习的冒泡排序,只能排序整形数据,那我们如何完成其他数据的排序呢?就得用到qsort
qsort是一个库函数,可以完成任意数据的排序,我们首先通过cplusplus的网站来了解qsort,qsort的头文件是stdlib.h,下面我们能来分析他的形参类型。
1.第一个形参void*base是一个void*类型的指针(因为该数组可能是任意类型,所以只有void*才可以接收任意类型的数据的地址),base指向要排序的数组的第一个元素位置。
2.第二个形参size_t num是一个无符号整型,num指向的是待排序数组中的元素个数。(只要知道元素的个数才能确定比较的次数。)
3.第三个形参size_t size是一个无符号整型,size指向数组中元素的大小(单位是字节,因为qsort完成任何类型的排列,所以对象可能是结构体也可能是整型,需要具体传入去 运算)。
4.第四个形参int (*compar)(const void*,const void*));,compar是一个函数指针,返回类型是int类型,两个形参的类型是void*类型。该函数指针指向的函数是用来比较数组中两个元素的方法。这个方法是根据我们的需求(比较整型或者比较结构体数据),去构造一个函数用来比较,构造的函数返回类型和形参类型必须一致。
qsort通过返回值来判断p1和p2的大小,当返回值>0,说明p1大于p2,返回值=0,说明p1=p2,返回值<0,说明p1<p2。
了解了qsort,下面利用qsort来实现排序。
注意事项:
1.qsort的使用必须包含头文件stdlib.h
2创建比较方法int_cmp函数时要注意该函数返回的结果必须是>0,=0,<0;
3.int_cmp传入的是void*类型的指针,必须强转成int*类型再解引用才可以进行运算。
4.如果想要完成逆序,将int_cmp的代码return(*(int*)p1 - *(int*)p2)中的p1和p2交换即可。
注意事项:
1.要访问结构体成员的两个方法:结构体变量.成员名 结构体指针->成员名
2.strcmp是专门用来比较字符串的大小的,并且它的返回值也恰好和qsort一样,所以可以直接去调用。字符串的比较方法:从左到右的顺序逐个比较两个字符串的字符,直到遇到第一个不同的字符,然乎根据字符的ascii值来确定两个字符串的大小关系。
3.结构体类型相较于整型类型,不能直接用+-<>等运算符,因为结构体中的成员属性可能有多个,直接比较编译器无法判断根据哪一个成员属性来比较。
qsort展现的是不同数据类型的快速排序,在学习qsort之前,我只知道冒泡排序,而冒泡排序只能排序整型类型,那么我们可以通过会回调函数的方法,来改造冒泡排序,使其成为可以排序任意数据类型的排序方法。
在模拟实现前,我们要比较qsort和冒泡排序,两者的数据类型不一样,所以我们对他的改造需要体现在两个方面。
1.由于数据类型不同,所以比较的方法必须改造。
2.由于不同数据类型占用字节大小不同,在利用指针偏移量操作的时候会有差异,所以交换的方法也必须改造。
3.由于数据类型不同,创建比较方法和交换方法时传入的两个参数必须是void*类型
3.模拟实现qsort,就要保证改造的排序函数bubble的返回类型和形参都要保持一致。
要注意的是,由于交换方法和比较方法的改造,由于不知道比较的是什么数据类型,所以都强转成char*类型进行操作,因为char*类型操作一次是一个字节,方便计算。这样恰好就是一次交换一个字节,执行size次后就完成整个元素的交换。所以必须传入size。