函数指针最常见的两个用途:作为参数传递给另一个函数(回调函数)和转换表(jump table),本文将探索这两方面的一些技巧。
一.回调函数
有一个简单的函数,它用于一个单链表中查找一个值。它的参数是一个指向链表第1个节点的指针以及那个需要查找的值。
Node*
search_list (Node *node, int const value)
{
while (node != NULL)
{
if (node->value == value)
break;
node = node->link;
}
return node;
}
这个函数看上去相当简单,但它只适用于值为整数的链表。如果你需要在一个字符串链表中查找,你就不得不另外编写一个函数。这个函数和上面那个函数的绝大部分代码相同,只是第二个参数的类型以及节点值的比较方法不同。
一种更为通用的方法是使查找函数与类型无关,这样它就能用于任何类型的值的链表。我们必须对函数的两个方面进行修改,使它与类型无关。
首先,我们必须改变比较的执行方式,这样函数就可以对任何类型的值进行比较。这个目标听上去好像不可能,如果编写语句用于比较整型值,它怎么还可能用于其他类型如字符串的比较呢?解决方案就是使用函数指针。调用者编写一个函数,用于比较两个值,然后把一个指向这个函数的指针作为参数传递给查找函数。然后查找函数调用这个函数来执行值的比较。使用这个方法,任何类型的值都可以进行比较。
其次,是向函数传递一个指向值的指针而不是值本身。函数有一个void *
形参,用于接收这个参数。然后指向这个值的指针便传递给比较函数。这个修改时字符串和数组对象也可以被使用。字符串和数组无法作为参数传递给数组,但指向它们的指针却可以。
使用这种技巧的函数被称为回调函数(callback function),因为用户把一个函数指针作为参数传递给其他函数,后者将“回调”用户的函数。任何时候,如果你所编写的函数必须能够在不同的时刻执行不同类型的工作或者执行只能由函数调用者定义的工作,你都可以使用这个技巧。
我们无法在这个上下文环境中为回调函数编写一个准确的原型,因为我们并不知道进行比较的值的类型。事实上,我们需要查找函数能作用于任何类型的值。解决这个难题的方法是把参数类型声明为void *
,表示“一个指向未知类型的指针”
下面的代码是类型无关查找函数的一种实现方法。注意函数的第三个参数是一个函数指针。
/*
**在一个单链表中查找一个指定值的函数。它的参数是一个指向链表第一个节点的指针
**一个指向我们需要查找的值的指针和一个函数指针,它所指向的函数用于比较存储于链表中的类型的值
*/
#include <stdio.h>
#include "node.h"
Node *
search_list (Node *node, void const *value,
int (*compare)(void const *, void const*))
{
while (node != NULL)
{
if (compare(&node->value, value) == 0)
break;
node = node->link;
}
return node;
}
int
compare_ints (void const *a, void const *b)
{
if (*(int *)a == *(int *)b)
return 0;
else
return 1;
}
这个函数将像下面这样使用:
desired_node = search_list(root, &desired_value, compare_ints);
二.转移表
转移表最好用个例子来解释。下面的代码段取自一个程序,它用于实现一个袖珍式计算器。程序的其他部分已经读入两个数(op1
和op2
)和一个操作符(oper
)。下面的代码对操作符进行测试,然后决定调用哪个函数。
switch (oper)
{
case ADD:
result = add(op1, op2);
break;
case SUB:
result = sub(op1, op2);
break;
case MUL:
result = mul(op1, op2);
break;
case DIV:
result = div(op1, op2);
break;
...
}
对于一个新奇的具有上百个操作符的计算器,这条switch
语句将会非常之长。
为什么要调用函数来执行这些操作呢?把具体操作和选择操作的代码分开是一种良好的设计方案。更为复杂的操作将肯定独立的函数来实现,因为它们的长度可能很长。但即使是简单的操作也可能具有副作用,例如保存一个常量值用于以后的操作。
为了使switch
语句,表示操作符的代码必须是整数。如果它们是从零开始连续的整数,我们可以使用转换表来实现相同的任务。转换表就是一个函数指针数组。
创建一个转换表需要两个步骤。首先,声明并初始化一个函数指针数组。唯一需要留心之处就是确保这些函数的原型出现在这个数组的声明之前。
double add (double, double);
double sub (double, double);
double mul (double, double);
double div (double, double);
...
double (*oper_func[]) (double, double) =
{add, sub, mul, div,...};
初始化列表中各个函数名的正确顺序取决于程序用于表示每个操作符的整型代码。这个例子假定ADD是0,SUB是1,MUL是2,以此类推。
第二个步骤是用下面这条语句替换前面整条switch
语句!
result = oper_func[oper] (op1, op2);
oper
从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。
感谢阅读:)
inner peace
知行合一
---------------------END---------------------