指针一直都是c语言中的难点,但是指针在使用中,更多的情况是指向数组、变量等类型。而函数指针一般比较少直接运用。对于函数指针,最常见的两个用途就是转移表和作为参数传递给另一个函数。函数指针也和其他指针一样,对函数指针进行访问前必须把它初始化为指向某个函数。
首先讲一下回调函数,回调函数是指用户把一个函数指针作为参数传递给其他函数,后者将“回调”用户所传递的函数,使用这样技巧的函数称回调函数。举个例子来说,在某个时刻,需要比较两个整形值的大小,从而进行相应的操作,这样的情况下,当然可以通过编写一个参数为整形的比较函数来实现,在需要调用的地方进行调用,但是如果在代码中另一处又想比较浮点型的参数的大小呢,是不是又要编写一个浮点型参数的比较函数,假如还要比较字符串呢(当然了字符串的话,有库函数可以调用,这里只是举例说明情况而已),可以看到不同类型的比较,都需要相应的接口函数实现,那么能不能实现一个函数,只管传递参数,不用管参数的类型,也能得到正确的比较结果,答案是可以的。这样一来就可以统一对外提供的接口,做到便于代码的管理,所使用的技巧就是回调函数。下面就以实现比较函数的回调函数来作为例子,来说明如何实现回调函数的用法。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int compare_type_int(void const *va, void const *vb)
{
int a = *(int *)va;
int b = *(int *)vb;
if (a == b)
{
return 0;
}
else
{
return -1;
}
}
int compare_type_str(void const *va, void const *vb)
{
char *a = (char *)va;
char *b = (char *)vb;
return strcmp(a,b);
}
int compare_function(void const *ta, void const *tb, int (*compare)(void const *a, void const *b))
{
return compare(ta, tb);
}
int main(void)
{
int val_a = 5;
int val_b = 5;
char *str_a = "abc";
char *str_b = "abd";
if (compare_function((void const *)&val_a, (void const *)&val_b, compare_type_int) == 0)
printf("val_a == val_b\n");
else
printf("val_a != val_b\n");
if (compare_function((void const *)&str_a, (void const *)&str_b, compare_type_str) == 0)
printf("str_a == str_b\n");
else
printf("str_a != str_b\n");
system("pause");
return 0;
}
函数指针的第二用法就是实现转移表。以整形数的加减乘除操作为例来说明转移表的实现方式(这里旨在说明用法,不作函数健壮性判断)。
#include <stdio.h>
#include <stdlib.h>
int ope_add(int a, int b)
{
return a + b;
}
int ope_sub(int a, int b)
{
return a - b;
}
int ope_mul(int a, int b)
{
return a * b;
}
int ope_div(int a, int b)
{
return a / b;
}
int main(void)
{
int a;
int b;
int oper_type;
int result;
scanf("%d%d%d",&a, &b, &oper_type);
switch(oper_type)
{
case 1:
result = ope_add(a,b);
break;
case 2:
result = ope_sub(a, b);
break;
case 3:
result = ope_mul(a, b);
break;
case 4:
result = ope_div(a, b);
break;
}
printf("result = %d\n",result);
system("pause");
return 0;
}
可以看到,对于指定的操作类型,在实现中调用相应的函数。但是如果这样的操作类型远不止例子中的4个,则switch中的case语句将会是相当多的,整个函数的代码会也因此而变得很长,不利于程序阅读和维护。这时就可以用转移表来作替代实现。代码如下:
#include <stdio.h>
#include <stdlib.h>
int ope_add(int a, int b)
{
return a + b;
}
int ope_sub(int a, int b)
{
return a - b;
}
int ope_mul(int a, int b)
{
return a * b;
}
int ope_div(int a, int b)
{
return a / b;
}
int (*oper_fun[])(int a, int b) = {ope_add, ope_sub, ope_mul, ope_div};
int main(void)
{
int a;
int b;
int oper_type;
int result;
scanf("%d%d%d",&a, &b, &oper_type);
if (oper_type>= 0 && oper_type <= 3)
result = oper_fun[oper_type](a, b);
else
{
printf("no such operation\n");
return -1;
}
printf("result = %d\n",result);
system("pause");
return 0;
}
定义一个函数指针数组,这样当需要添加新的操作类型的函数时,只需要定义该类型的函数,并添加数组成员,就可以了。对于操作类型oper_type要做一个安全性判断,如果发生下标越界访问,在一个复杂的程序代码中,将会是很难调节的,因为其结果是未定义的,并不能知道程序什么时候会运行失败,所以在写代码时,做好这一步健壮性判断是必须的。但是假如某程序中使用了转移表,而在实现调试过程中,怀疑转移表访问出问题了,可以在那个函数调用前后各打印一条信息。因为如果转移表访问越界了,函数调用是不会返回的。用这种办法就可以判断是不是转移表访问越界了。
转移表虽然方便,但也是也有限制,就是转移表内的函数类型要相同。