函数在内存中有一个物理位置,而这个位置是可以赋给一个指针的。函数的地址就是该函数的入口点。因此,函数指针可被用来调用一个函数。函数的地址是用不带任何括号或参数的函数名来得到的。(这很类似于数组地址的得到方法,即,在只有数组名而无下标是就得到数组地址。)
1、怎样说明一个函数指针变量呢?
为了说明一个变量fn_pointer的类型是"返回值为int的函数指针",你可以使用下面的说明语句:
int (*fn_pointer) ();
*fn_pointer必须用括号围起来。若漏了这对括号,则int*fn_pointer();的意思完全不同了。fn_pointer将是一个函数名,其返回值为int类型的指针。
2、函数指针变量
在C语言中规定,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为"函数指针变量"。
函数指针变量定义的一般形式为:类型说明符(*指针变量名)();
其中"类型说明符"表示被指函数的返回值的类型。"(*指针变量名)"表示"*"后面的变量是定义的指针变量。最后的空括号表示指针变量所指的是一个函数。例如:int(*pf)();表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。
下面通过例子来说明用指针形式实现对函数调用的方法。
#include <stdio.h> int max(int a,int b) { if(a > b) return a; else return b; } int main() { int max(int a,int b); int(*pmax)(); int x,y,z; pmax = max; printf("inputtwonumbers:\n"): scanf("%d%d",&x,&y); z = (*pmax)(x,y); printf("maxmum=%d",z); }
从上述程序可以看出用,函数指针变量形式调用函数的步骤如下:
1>.先定义函数指针变量,如后一程序中第9行int(*pmax)();定义pmax为函数指针变量。
2>.把被调函数的入口地址(函数名)赋予该函数指针变量,如程序中第11行pmax=max;
3>.用函数指针变量形式调用函数,如:z=(*pmax)(x,y);调用函数的一般形式为:(*指针变量名)(实参表)使用函数指针变量还应注意以下两点:
a.函数指针变量不能进行算术运算,这是与数组指针变量不同的。数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素,而函数指针的移动是毫无意义的。
b.函数调用中"(*指针变量名)"的两边的括号不可少,其中的*不应该理解为求值运算,在此处它只是一种表示符号。
看下面的例子:
A) char * (*fun1)(char * p1,char * p2);
B) char * *fun2(char * p1,char * p2);
C) char * fun3(char * p1,char * p2);
看看上面三个表达式分别是什么意思?
C):这很容易,fun3 是函数名,p1,p2 是参数,其类型为char *型,函数的返回值为char *类型。
B):也很简单,与C)表达式相比,唯一不同的就是函数的返回值类型为char**,是个二级指针。
A):fun1是函数名吗?回忆一下数组指针时的情形。数组指针这么定义或许更清晰:int (*)[10] p;
再看看A),这里fun1 不是什么函数名,而是一个指针变量,它指向一个函数。这个函数有两个指针类型的参数,函数的返回值也是一个指针。
再看下面的例子:
#include <stdio.h> #include <string.h> char * fun(char * p1,char * p2) { int i = 0; i = strcmp(p1,p2); if (0 == i) { return p1; } else { return p2; } } int main() { char * (*pf)(char * p1,char * p2); pf = &fun; (*pf) ("aa","bb"); return 0; }
我们使用指针的时候,需要通过钥匙(“*”)来取其指向的内存里面的值,函数指针使用也如此。通过用(*pf)取出存在这个地址上的函数,然后调用它。
3、*(int*)&p ----这是什么?
#include <stdio.h> #include <string.h> void Function() { printf("Call Function!\n"); } int main() { void (*p)(); *(int*)&p=(int)Function; (*p) (); return 0; }
void (*p)();这行代码定义了一个指针变量p,p 指向一个函数,这个函数的参数和返回值都是void。
&p 是求指针变量p 本身的地址,这是一个32 位的二进制常数(32 位系统)。
(int*)&p 表示将地址强制转换成指向int 类型数据的指针。
(int)Function 表示将函数的入口地址强制转换成int 类型的数据。
分析到这里,相信你已经明白*(int*)&p=(int)Function;表示将函数的入口地址赋值给指针变量p。那么(*p) ();就是表示对函数的调用。
讲解到这里,相信你已经明白了。其实函数指针与普通指针没什么差别,只是指向的内容不同而已。
使用函数指针的好处在于,可以将实现同一功能的多个模块统一起来标识,这样一来更容易后期的维护,系统结构更加清晰。或者归纳为:便于分层设计、利于系统抽象、降低耦合度以及使接口与实现分开。
4、(*(void(*) ())0)()------这是什么?
这是《C Traps and Pitfalls》这本经典的书中的一个例子。下面我们就来分析分析:
第一步:void(*) (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。
第二步:(void(*) ())0,这是将0 强制转换为函数指针类型,0 是一个地址,也就是说一个函数存在首地址为0 的一段区域内。
第三步:(*(void(*) ())0),这是取0 地址开始的一段内存里面的内容,其内容就是保存在首地址为0 的一段区域内的函数。
第四步:(*(void(*) ())0)(),这是函数调用。
5、函数指针数组
现在我们清楚表达式“char * (*pf)(char * p)”定义的是一个函数指针pf。既然pf 是一个指针,那就可以储存在一个数组里。把上式修改一下:
char * (*pf[3])(char * p);
这是定义一个函数指针数组。它是一个数组,数组名为pf,数组内存储了3 个指向函数的指针。这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数。这念起来似乎有点拗口。不过不要紧,关键是你明白这是一个指针数组,是数组。
函数指针数组怎么使用呢?这里也给出一个非常简单的例子,只要真正掌握了使用方法,再复杂的问题都可以应对。如下:
#include <stdio.h> #include <string.h> char * fun1(char * p) { printf("%s\n",p); return p; } char * fun2(char * p) { printf("%s\n",p); return p; } char * fun3(char * p) { printf("%s\n",p); return p; } int main() { char * (*pf[3])(char * p); pf[0] = fun1; // 可以直接用函数名 pf[1] = &fun2; // 可以用函数名加上取地址符 pf[2] = &fun3; pf[0]("fun1"); pf[0]("fun2"); pf[0]("fun3"); return 0; }
6、定义一个简单的函数指针数组指针:
char * (*(*pf)[3])(char * p);
注意,这里的pf 和上一节的pf 就完全是两码事了。上一节的pf 并非指针,而是一个数组名;这里的pf 确实是实实在在的指针。这个指针指向一个包含了3 个元素的数组;这个数字里面存的是指向函数的指针;这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数。这比上一节的函数指针数组更拗口。其实你不用管这么多,明白这是一个指针就ok 了。其用法与前面讲的数组指针没有差别。下面列一个简单的例子:
#include <stdio.h> #include <string.h> char * fun1(char * p) { printf("%s\n",p); return p; } char * fun2(char * p) { printf("%s\n",p); return p; } char * fun3(char * p) { printf("%s\n",p); return p; } int main() { char * (*a[3])(char * p); char * (*(*pf)[3])(char * p); pf = &a; a[0] = fun1; a[1] = &fun2; a[2] = &fun3; pf[0][0]("fun1"); pf[0][1]("fun2"); pf[0][2]("fun3"); return 0; }