这里有一个简单的函数,它用于在一个单链表中查找一个值。它的参数是一个指向链表第一个节点的指针以及那个需要查找的值。
Node * search_list(Node * node, const int value)
{
while (node != NULL)
{
if (node->value == value)
break;
node = node->next;
}
return node;
}这个函数看上去相当简单,但它只适用于值为整数的链表。如果你需要再一个字符串链表中查找,你不得不另外编写一个函数。这个函数和上面那个函数的绝大部分代码相同,只是第二个参数的类型以及节点值得比较方法不同。
一种更为通用的方法是使查找函数与类型无关,这样它就能用于任何类型的值的链表。我们必须对函数的两个方面进行修改,使它与类型无关。首先,我们必须改变比较的执行方式,这样函数就可以对任何类型的值进行比较。这个目标听上去好像不可能,如果你编写语句用于比较整型值,它怎么还可能用于其他类型如字符串的比较呢?解决方案就是使用函数指针。调用者编写一个函数,用于比较两个值,然后把一个指向这个函数的指针作为参数传递给查找函数。然后查找函数调用这个函数来执行值的比较。使用这种方法,任何类型的值都可以进行比较。
我们必须修改的第二个方面是向函数传递一个指向值得指针而不是值本身。函数有一个 void * 形参,用于接收这个参数。然后指向这个值得指针便传递给比较函数。这个修改使字符串和数组对象也可以被使用。字符串和数组无法作为参数传递给函数,但指向它们得指针却可以。
使用这种技巧的函数被称为回调函数(callback function),因为用户把一个函数指针作为参数传递给其他函数,后者将“回调”用户的函数。任何时候,如果你所编写的函数必须能够在不同的时刻执行不同类型的工作或者执行只能由函数调用者定义的工作,你都可以使用这个技巧。许多 GUI 系统使用回调函数连接多个动作,如拖拽鼠标和点击按钮来指定用户程序中的某个特定函数。
我们无法在这个上下文环境中为回调函数编写一个准确的原型,因为我们并不知道进行比较的值的类型。事实上,我们需要查找函数能作用于任何类型的值。解决这个难题的方法是把参数类型声明为 void * ,表示“一个指向未知类型的指针”。
在使用比较函数中的指针之前,它们必须被强制转换成正确的类型。因为强制类型转换能够躲开一般的类型检查,所以在使用时必须格外小心,确保函数的参数类型是正确的。
在这个例子中,回调函数比较两个值。查找函数向比较函数传递两个指向需要进行比较的值的指针,并检查比较函数的返回值。例如,零表示相等的值,非零值表示不相等的值。现在,查找函数就与类型无关,因为它本身并不执行实际的比较。确实,调用者必须编写必需的比较函数,但这样做是很容易的,因为调用者知道链表中所包含的值的类型。如果使用几个分别包含不同类型值得链表,为每种类型编写一个比较函数就允许单个查找函数作用于所有类型的链表。
下面函数是类型无关查找函数的一种实现方法。注意函数的第三个参数是一个函数指针。这个参数用一个完整的原型进行声明。同时注意虽然函数绝不会修改参数 node 所指向的任何节点,但 node 并未被声明为 const 。如果 node 被声明为 const ,函数将不得不返回一个 const 结果,这将限制调用程序,它便无法修改查找函数所找到的节点。
Node * search_list(Node * node, const void * value, int (*compare)(const void *, const void *))
{
while (node != NULL)
{
if (0 == compare(&node->value, value))
break;
node = node->next;
}
return node;
}指向值参数的指针和 &node->value 被传递给比较函数。后者是我们当前所检查的节点的值。在选择比较函数的返回值时,选择相等返回零值,不相等返回非零值,目的是为了与标准中的一些函数所使用的比较函数规范兼容。在这个规范中,不相等操作数的返回方式更为明确:负值表示第一个参数小于第二个参数,正值表示第一个参数大于第二个参数。
在一个特定的链表中进行查找时,用户需要编写一个适当的比较函数,并把指向该函数的指针和指向需要查找的值的指针传递给查找函数。例如,下面是一个比较函数,它用于在一个整数链表中进行查找。
int compare_ints(const int * a, const int * b)
{
if (*(int *)a == *(int *)b)
return 0;
return 1;
}这个函数将像下面这样使用:
desired_node = search_list(root, &desired_value, compare_ints);注意强制类型转换:比较函数的参数必须声明为 void * 以匹配查找函数的原型,然后它们再强制类型转换为 int * 类型,用于比较整型值。
如果你希望再一个字符串链表中进行查找,下面的代码可以完成这项任务:
#include <cstring>
desired_node = search_list(root, "desired_vaalue", strcmp);碰巧,库函数 strcmp 所执行的比较和需要的完全一致,不过有些编译器会发出警告信息,因为它的参数被声明为 char * 而不是 void * 。