先来看最简单的Hello World程序:
#include <stdio.h>//函数实现
void sayHello(){
printf("hello world\n");
}// main函数调用
int main() {
sayHello();
}
如果要把上面的直接调用改为用函数指针调用,只要把上面的main函数改为:
int main() {
void (*sayHelloPtr)() = sayHello;
(*sayHelloPtr)();
}
首先来理解代码中的void (*sayHelloPtr)()=sayHello;:
1. void 说明指针指向的函数的返回值类型。
2. sayHelloPtr 是函数指针的名字,我们用 * 符号表示这是一个指针,就像声明其他类型的指针一样。
3. *sayHelloPtr 两端的括号是必需的,否则 void * sayHelloPtr() 表示的是一个返回 void * 的函数。
4. 参数列表紧跟在指针名后面,本例中没有参数,所以括号中什么都没有。
5. 综上所述,这行代码的意义就是,sayHelloPtr 是一个指向名为 sayHello 的返回值为 void 的函数指针。
对上述代码中的(*sayHelloPtr)();的理解应该注意下面几点:
1. 对 sayHelloPtr 的解引用方式也和其他指针的解引用一样,即在指针之前使用解引用符 * 。
2. 同样的,为了使其能被识别为函数指针,需要在两端加上括号,即(*sayHelloPtr)。所以应该记住声明和解引用时都要在两端加上括号。
3. 括号操作符用于C语言中的函数调用,如果有参数参与,将其置于括号中。
除了上面的调用方式,我们还可以用其他方法调用函数指针,就像对待一个普通函数那样:
int main() {
void (*sayHelloPtr)() = sayHello;
sayHelloPtr();
}
可以认为 sayHelloPtr(); 和 (*sayHelloPtr)(); 具有相同的意义。
实例代码是最好的阐述方式:
#include <stdio.h>//函数实现
void subtractAndPrint(int x, int y) {
int z = x - y;
printf("Simon says, the answer is: %d\n", z);
}//main函数调用
int main() {
void (*sapPtr)(int, int) = subtractAndPrint;
(*sapPtr)(10, 2);
sapPtr(10, 2);
}
道理很简单,只要把上面代码中的关键字 void 改为相应的其他关键字即可。
#include <stdio.h>// 函数实现
int subtract(int x, int y) {
return x - y;
}// main函数调用
int main() {
int (*subtractPtr)(int, int) = subtract;int y = (*subtractPtr)(10, 2);
printf("Subtract gives: %d\n", y);int z = subtractPtr(10, 2);
printf("Subtract gives: %d\n", z);
}
对于返回值的使用也与普通函数调用无异。
#include <stdio.h>// 加法 x+ y
int add(int x, init y) {
return x + y;
}// 减法 x - y
int subtract(int x, int y) {
return x - y;
}// 根据输入执行函数指针
int domath(int (*mathop)(int, int), int x, int y) {
return (*mathop)(x, y);
}// main函数调用
int main() {// 用加法调用domath
int a = domath(add, 10, 2);
printf("Add gives: %d\n", a);// 用减法调用domath
int b = domath(subtract, 10, 2);
printf("Subtract gives: %d\n", b);
}
一个函数名(或称标签),被转换成了一个函数指针本身。这表明在函数指针被要求当作输入的地方,就能够使用函数名。这也导致了一些看起来很糟糕的代码却能够正常运行。比如:
#include <stdio.h>// 加法 x + y
void add(char *name, int x, int y) {
printf("%s gives: %d\n", name, x + y);
}// main函数调用
int main() {// 一些糟糕的函数指针赋值
void (*add1Ptr)(char*, int, int) = add;
void (*add2Ptr)(char*, int, int) = *add;
void (*add3Ptr)(char*, int, int) = &add;
void (*add4Ptr)(char*, int, int) = **add;
void (*add5Ptr)(char*, int, int) = ***add;// 仍然能够正常运行
(*add1Ptr)("add1Ptr", 10, 2);
(*add2Ptr)("add2Ptr", 10, 2);
(*add3Ptr)("add3Ptr", 10, 2);
(*add4Ptr)("add4Ptr", 10, 2);
(*add5Ptr)("add5Ptr", 10, 2);// 当然,这也能运行
add1Ptr("add1PtrFunc", 10, 2);
add2Ptr("add2PtrFunc", 10, 2);
add3Ptr("add3PtrFunc", 10, 2);
add4Ptr("add4PtrFunc", 10, 2);
add5Ptr("add5PtrFunc", 10, 2);
}
这是一个简单的例子,运行这段代码,你会看到每个函数指针都回执行,只是会收到一些关于字符转换的警告。但是,这些函数指针都能正常工作。我们讨论一下上述代码中的函数指针赋值部分。
1. 指针赋值的第1行, add 作为函数名,返回这个函数的地址,它被隐式地转换为一个函数指针。如前所述,在函数指针被要求当作输入的地方,就能够使用函数名。
2. 指针赋值的第2行,解引用符 * 作用于 add 之前,即返回在这个地址的函数。之后跟函数名一样,被隐式地转换为一个函数指针。
3. 指针赋值的第3行,取地址符作用于 add 之前,返回这个函数的地址,之后又得到一个函数指针。
4. 再后面的两行,add 不断解引用自身,不断返回函数名,并转换为函数指针。到最后,它们的结果都和函数名没有区别。
显然,这段代码不是优秀的实例代码。我们从中得到如下知识。第一,函数名会被隐式地转换为函数指针,就像作为参数传递的时候,数组名被隐式地转换为指针一样。在函数指针被要求当作输入的任何地方,都能够使用函数名。第二,解引用符*和取地址符&用在函数名之前基本都是多余的。
本文主要参考C语言函数指针基础