C语言函数指针
C语言函数指针
1、什么是函数指针
在程序中定义了一个函数,这个函数就会存储在代码区,这个空间的首地址为这个函数的地址。函数名其实就是一个地址,我们可以定义一个指针变量来存放这个地址,那么这个指针就是函数指针。
void hello()
{
printf("hello\n");
}
printf("address(hello): %p\n", hello);
2、函数指针的定义和使用
函数指针的定义: (返回值类型)(*指针变量名)(参数列表)
#include <stdio.h>
// 定义max函数 int max(int x, int y) { return (x>y)? x : y; } int main() { int (*pmax)(int, int); // 定义pmax指针 pmax = max; // 将max函数的首地址赋给pmax指针 printf("max(1, 2): %d\n", max(1, 2)); printf("pmax(1, 2): %d\n", pmax(1, 2)); // 通过pmax指针间接调用max函数 return 0; }
函数指针还可以作为结构体的变量
#include <stdio.h> int add(int x, int y) { return x+y; } int muliti(int x, int y) { return x*y; } struct T { int (*add)(int, int); // 定义两个函数指针作为结构体的成员变量 int (*muliti)(int, int); }; int main() { int a = 2, b = 3; struct T t; t.add = add; t.muliti = muliti; printf("add(a,b): %d\n", t.add(a, b)); printf("muliti(a,b): %d\n", t.muliti(a, b)); return 0; }
3、函数指针最见的两个用途
函数指针最常见的两个用途:转换表(jump table)和作为参数传递给另一个函数(回调函数)。
3.1 回调函数
下面有一个简单的函数,它用于在一个单链表中查找一个值,它的第一个参数是指向链表的第一个节点的指针,第二个参数是需要查找的值
Node * search_list(Node *node, int const value) { while(node != NULL) { if(node->value == value) { break; } node = node->next; } return node; }
这个函数只适用于值为整数的链表,如果值为浮点或字符型的就不行。为避免重复写代码,一种更为通用的方法是使查找函数与类型无关,这样就能用于任何类型值的链表。首先,改变比较执行的方式,这样函数就可以对任何类型的值进行比较。这个听上去好像不可能,如果编写语句用于比较整型值,它怎么还能用于其他类型的值进行比较呢?解决方案就是使用函数指针。调用者编写一个函数用于比较两个值,然后把一个指向这个函数的指针作为参数传递给查找函数。然后查找函数调用这个函数来执行值的比较。调用者想查找什么类型的值,就编写一个什么类型值的比较函数就行了,不用再重复编写查找函数。
使用这种技巧的函数称为“回调函数(callback function)”,因为用户把以恶函数指针作为参数传递给其他函数,后者将“回调”用户的函数。查找函数能作用于任何类型的值,而我们不知道用户需要进行比较的值的具体类型,所以把回调函数的参数类型声明为void *, 表示“一个指向未知类型的指针”。
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->next; } return node; }
如果用户需要查找整型类型的值,用户需要编写一个比较整型值的函数。
int compare_ints(void const *a, void const *b) { if(* (int *) a == * (int *)b) { return 0; } return 1; }
比较函数的参数必须声明为void *类型以匹配查找函数的原型,然后再强制转换为int *类型,用于比较整型值。
查找函数的使用:
desired_node = search_list(root, &desired_value, compare_ints);
3.2 转换表
下面的一段程序用于实现一个计算器,程序的其他部分已经读入两个数(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, dub, mul, div, ...};
初始化列表中各个函数名的正确顺序取决于程序中用于表示每个操作符的整型代码。这个例子中假定ADD是0,SUB是1,MUL是2等。
第二步就是使用下面这个条语句替换前面的整条switch语句:
result = oper_func[oper](op1, op2);
oper 从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。
(完结)