只有当确实需要时,你才应该使用多层间接访问。不然的话,你的程序将会变得更庞大、更缓慢并且更难于维护。
高级指针声明,例如
int *f(); // 其是返回值类型为整型指针的函数
int (*f)(); // 其是返回值为整型的函数指针
int *f[]; // 其是元素为整型指针的数组
int (*f[])(); // 其是一个数组 数组元素是返回值为整型的函数指针
int *(*f[])(); // 其是一个数组
// 数组元素是返回值为整型指针的函数指针
int (*f)(int, float); // 其是一个函数指针 形参是int和float
int *(*g[])(int, float); // 其是一个数组
// 数组元素是返回值为整型指针形参为int和float的函数指针
你一般不会使用函数指针,但它们确实有用武之地,最常见的两个用途是转换表和作为参数传递给另一个函数。
简单声明一个函数指针并不意味着马上就能使用。和其他指针一样,对函数指针执行间接访问之前必须把它初始化为指向某个函数。例如:
int f(int);
int (*pf)(int) = &f; // 函数指针初始化
其中初始化表达式中的&操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针。&操作符只是显式地说明了编译器将隐式执行的任务。在函数指针被声明且初始化之后,我们就可以使用3种方式调用函数:
int ans;
ans = f(25);
ans = (*pf)(25);
ans = pf(25);
编译器在实际处理各种函数时,都是以函数指针作为最终的处理目标,其他形式都将转换为函数指针形式。
通过编写语句使函数既能处理整型值又能处理字符串型这种不同类型的操作时,可使用函数指针来解决。函数有一个(void*)形参,用于接收这个参数。然后指向这个值的指针便传递给比较函数。这个修改使字符串和数组对象也可以被使用。字符串和数组无法作为参数传递给函数,但指向它们的指针却可以。
使用这种技巧的函数被称为回调函数,因为用户把一个函数指针作为参数传递给其他函数,后者将回调用户的函数。任何时候,如果你所编写的函数必须能够在不同的时刻执行不同类型的工作或者执行只能由函数调用者定义的工作,你都可以使用这个技巧。事实上,我们需要查找函数能够作用于任何类型的值。解决这个难题的方法是把参数类型声明为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;
}
}
// 调用函数
Node *desired_node = search_list(root, &desired_value,
compare_ints);
为了使用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, ...
};
第二步是用下面这条语句替换整条switch语句:
double result = oper_func[oper](op1, op2);
oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。
处理命令行参数是指向指针的指针的另一个用武之地。有些操作系统,包括Linux和MS-DOS,让用户在命令行中编写参数来启动一个程序的执行。这些参数被传递给程序,程序按照它认为合适的任何方式对它们进行处理。即:
int main(int argc, char **argv)
注意在测试**/argv之前先测试argv是非常重要的。如果argv为NULL,那么**argv中的第2个间接访问就是非法的。
对于字符串指针,对一个指针执行间接访问操作时,其结果就是指针所指向的内容。字符串常量的类型是“指向字符的指针”,所以这个间接访问的结果就是它所指向的字符。注意表达式的结果并不是整个字符串,而只是它的第1个字符。