C语言基础系列(二)——函数指针
函数指针的定义
由前面的C语言基础系列(一)——指针与字符串,我们了解到指针其实就是指针变量,指针变量的值实际上是一个地址,这个地址是其他变量的存储地址。
同理,在C语言中,函数也是具有一个具体的入口地址,这个入口地址也可被指针变量所指向,指向函数地址的指针变量,称为函数指针
int func(int );
int (*pf)(int ) = &f;
第一句声明了一个函数
第二句声明创建了函数指针pf,并且它初始化指向函数f,第二句有两个括号,第一个括号(*pf)代表着pf是一个指针,第二个括号(int )代表函数调用的操作符,因此pf是一个指针,指向返回值为int类型的函数
其中第二句声明中的取地址符&是可以被省略掉的,即第二句可以被省略为 int (*pf)(int ) = f;
这是由于函数名在使用时,编译器将它转换成了函数指针
了解了函数指针的定义之后,我们来看下面几个例子,看看它们分别代表着什么意思?
int a;
a = f(25);
a = (*pf)(25)
a = pf(25)
- 第一句是简单的调用函数f,但是这个过程可能你不是很清晰,首先编译器将f转换成一个函数指针,这个指针会指定函数在内存中的位置,然后运行时调用该函数,执行开始于这个地址的代码;
- 第二句是将函数指针先解引用成函数名,然后再进行运算,但是这个转换并不是必须的,因为编译器调用之前又会把函数名转换成函数指针
- 第三句就是一个函数指针的直接使用
需要记住函数名在使用时,编译器会先将函数转换成函数指针。
那我们何时需要使用函数指针呢?有两个常用的场景:
- 回调函数
- 转换表
回调函数
假设有这么一段代码,实现的功能是在单链表中去查找一个值,它的参数是一个指向链表的第一个节点的指针以及那个需要查找的值
Node *search_list(Node *node, int const value)
{
while (node != NULL) {
if (node->value == value)
break;
node = node->link;
}
return node;
}
这个函数实现的较为简单,也没什么问题,但是我们仔细考虑下会发现,这个函数只适用于值为整数的链表,如果后面有个新需求,需要去搜索值为字符串的链表,这个函数就不再适用了,因此该函数的可复用性就大打折扣,而且实现另外一个搜索字符串的链表,大部分是相同的,只是第二个输入参数的类型需要改变。
那有没有什么通用的办法,能够实现我们的需求呢,一个函数既能去搜索整型的链表,又能去搜索字符串的链表,答案是有的,那就是函数指针。
我们需要做两件事情:
- 首先,我们要将实际比较的函数,作为指针传入到search_list中;
- 其次,search_list的第二个参数value应该是(void *)类型,这个代表着一个指向未知类型的指针,可以使得任何类型的参数传入,编译器都不会报错;
使用这种技巧,称为回调函数(callback function),怎么理解"回调"?
用户将函数指针当成参数传递给其他函数,而后者又回调前面的函数指针
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;
}
//compare_int
int compare_int(void const *a, void const *b)
{
if (*(int *)a == *(int *)b)
return 0;
else
return 1;
}
//compare_string,标准库中有strcmp函数,刚好能够满足我们的需求
//strcmp描述:[strcmp](https://www.tutorialspoint.com/c_standard_library/c_function_strcmp.html)
//调用compare_int去对比int类型数据时
desired_node = search_list(root, &desired_value, compare_int);
//调用strcmp函数去对比string类型时
desired_node = search_list(root, "helloworld", strcmp);
转换表
当我们需要实现一个袖珍计算器的功能时,假如已经有两个输入参数(op1, op2)和一个操作符(oper),代码如下:
switch (oper)
{
case ADD:
result = add(op1, op2);
break;
case SUB:
result = sub(op1, op2);
break;
.....
}
如果后续各种高级的运算符的需求,这个switch-case的表将会无限大,因此我们可以考虑使用转换表的方式,怎么操作呢?
- 首先声明并初始化一个函数指针数组
- 用一个通用的操作符来替代add、sub等具体操作;
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, ....
};
//i代表的是运算符的index,本例中add是0,sub是1...
result = oper_func[i].(op1, op2);