C/C++函数指针详解(转) 1.     无处不见的函数指针 2 函数指针简单介绍 3 C/C++函数指针的语法

这两天在研究C/C++的函数指针,找到一篇讲解比较详细的博客,内容有点多,但是讲解得比较详细,适合初学者。特转之:

       使用函数指针可以设计出更优雅的程序,比如设计一个集群的通信框架的底层通信系统:首先将要每个消息的对应处理函数的指针保存映射表中(使用STL的map,键是消息的标志,值是对应的函数指针),然后启动一个线程在结点上的某个端口侦听,收到消息后,根据消息的编号,从映射表中找到对应的函数入口,将消息体数据作为参数传给相应的函数。我曾看过lam-mpi在启动集群中每个结点的进程时的实现,该模块的最上层就是一个结构体,这个结构体中仅是由函数指针构成,每个函数指针都指向一个子模块,这样做的好处就是在运行时期间可以*的切换子模块。比如某个子模块不适合某个体系结构,只需要改动函数指针,指向另外一个模块就可。

在平时的程序设计中,经常遇到函数指针。如EnumWindows这个函数的参数,C语言库函数qsort的参数,定义新的线程时,这些地方函数指针都是作为回调函数来应用的。

还有就是unix的库函数signal(sys/signal.h)(这个函数我们将多次用到)的声明形式为:

void (*signal)(int signo,void (*func)(int)))(int);

    这个形式是相当复杂的,因为它不仅使用函数指针作为参数,而且返回类型还是函数指针(虽然这个函数在POSIX中不被推荐使用了)。

    还有些底层实现实际上也用到了函数指针,可能你已经猜到了。嗯,就是C++中的多态。这是一个典型的迟绑定(late-binding)的例子,因为在编译时是无法确定到底绑定到哪个函数上执行,只有在运行时的时候才能确定。这个可以通过下面这个例子来帮助理解:

Shape *pSh;

scanf(“%d”,&choice);

if(choice)

{

pSh= new Rectangle();

}

else

{

pSh= new Square();
}

pSh->display();

对于上面这段代码,做以下几个假设:

(1)    Square继承自Rectange

(2)    Rectangle继承自Shape

(3)    display为虚函数,在每个Shape的子类链中都必须实现

正是因为在编译期间无法确定choice的值,所以在编译到最后一行的时候无法确定应该绑定到那个一个函数上,只能在运行期间根据choice的值,来确定要绑定的函数的地址。

总之,使用指针可以让我们写出更加优雅,高效,灵活的程序。另外,和普通指针相比,函数指针还有一个好处就是你不用担心内存释放问题。

但是,函数指针确实很难学的,我认为难学的东西主要有两个原因:(1)语法过于复杂。(2)语义过于复杂。从哲学上讲,可以对应为(1)形式过于复杂。(2)内容过于复杂。

比如,如果我们要描述“美女”这种动物(老婆不要生气啊~),如果在原始时代,我们可能需要通过以下这种方式:

 

     _____                 &&&&_) )

   /,---<                &&&&&&

   ( )c~c~~@~@            )- - &&

    C   >/                <   |&/

     \_O/ - 哇塞          _`*-'_/ /

   ,- >o<-.              / ____ _/

  /   /               / /  _)_)

 / /|  | |           / /  )   |

  |  | |/ /          /    |

  \_  | |_/             \_    |

  /_/`___|_            /_/\____|

    |  | |                    |

    |  | |                   `. )

    |  | |                   / /

    |__|_|_                 /_/|

    (____)_)                |\_\_

而现在我们只需要用语言来抽象就行,即用两个汉字“美女”或者英文“beauty”就行了。这就是形式上的简化,也就方便了我们的交流。另外一种就是内容上的复杂度过高,一个高度抽象的表达式后面蕴含着巨大的复杂度对于我们理解问题也是很难的,例如:

P=NP?

由于接触过的书上所讲的关于函数指针方面的都是蜻蜓点水一样,让我很不满足。我认为C/C++语言函数指针难学的主要原因是由于其形式上的定义过于复杂,但是在内容上我们一定要搞清楚函数的本质。函数的本质就是表达式的抽象,它在内存中对应的数据结构为堆栈帧,它表示一段连续指令序列,这段连续指令序列在内存中有一个确定的起始地址,它执行时一般需要传入参数,执行结束后会返回一个参数。和函数相关的,应该大致就是这些内容吧。

2 函数指针简单介绍

2.1 什么是函数指针

函数指针是一个指向函数的指针(呃,貌似是废话),函数指针表示一个函数的入口地址。使用函数指针的好处就是在处理“在运行时根据数据的具体状态来选择相应的处理方式”这种需求时更加灵活。

2.2 一个简单的例子

下面是一个简单的使用函数指针取代switch-case语句的例子,为了能够比较出二者效率差异,所以在循环中进行了大量的计算。

  1 /* 
  2  
  3 *Author:Choas Lee 
  4  
  5 *Date:2012-02-28 
  6  
  7 */ 
  8  
  9 #include<stdio.h> 
 10 #define UNIXEVN 
 11  
 12 #if defined(UNIXENV) 
 13  
 14 #include<sys/time.h> 
 15  
 16 #endif 
 17  
 18 #define N 1000000 
 19  
 20 #define COE 1000000 
 21  
 22 float add(float a,float b){return a+b;} 
 23  
 24 float minus(float a,float b){return a-b;} 
 25  
 26 float multiply(float a,float b){return a*b;} 
 27  
 28 float divide(float a,float b){return a/b;} 
 29  
 30 typedef float (*pf)(float,float); 
 31  
 32 void switch_impl(float a,float b,char op) 
 33  
 34 { 
 35  
 36       float result=0.0; 
 37  
 38       switch(op) 
 39  
 40       { 
 41  
 42              case '+': 
 43  
 44                     result=add(a,b); 
 45  
 46                     break; 
 47  
 48              case '-': 
 49  
 50                     result=minus(a,b); 
 51  
 52                     break; 
 53  
 54              case '*': 
 55  
 56                     result=multiply(a,b); 
 57  
 58                     break; 
 59  
 60              case '/': 
 61  
 62                     result=divide(a,b); 
 63  
 64                     break; 
 65  
 66       } 
 67  
 68 } 
 69  
 70 void switch_fp_impl(float a,float b,pf p) 
 71  
 72 { 
 73  
 74       float result=0.0; 
 75  
 76       result=p(a,b); 
 77  
 78 } 
 79  
 80 int conversion(struct timeval tmp_time) 
 81  
 82 { 
 83  
 84       return tmp_time.tv_sec*COE+tmp_time.tv_usec; 
 85  
 86 } 
 87  
 88 int main() 
 89  
 90 { 
 91  
 92       int i=0; 
 93  
 94 #if defined(UNIXENV) 
 95  
 96       struct timeval start_point,end_point; 
 97  
 98   
 99  
100       gettimeofday(&start_point,NULL); 
101  
102 #endif 
103  
104       for(i=0;i<N;i++) 
105  
106       { 
107  
108              switch_impl(12.32,54.14,'-'); 
109  
110       } 
111  
112 #if defined(UNIXENV) 
113  
114       gettimeofday(&end_point,NULL); 
115  
116       printf("check point 1:%d
",conversion(end_point)-conversion(start_point)); 
117  
118       
119  
120       gettimeofday(&start_point,NULL); 
121  
122 #endif 
123  
124       for(i=0;i<N;i++) 
125  
126       { 
127  
128              switch_fp_impl(12.32,54.14,minus); 
129  
130       } 
131  
132 #if defined(UNIXENV) 
133  
134       gettimeofday(&end_point,NULL); 
135  
136       printf("check point 2:%d
",conversion(end_point)-conversion(start_point)); 
137  
138 #endif 
139  
140       return 0; 
141  
142 } 
View Code

下面是执行结果:

 1 [lichao@sg01 replaceswitch]$ ./replaceswitch 
 2  
 3 check point 1:22588 
 4  
 5 check point 2:19407 
 6  
 7 [lichao@sg01 replaceswitch]$ ./replaceswitch 
 8  
 9 check point 1:22656 
10  
11 check point 2:19399 
12  
13 [lichao@sg01 replaceswitch]$ ./replaceswitch 
14  
15 check point 1:22559 
16  
17 check point 2:19380 
18  
19 [lichao@sg01 replaceswitch]$ ./replaceswitch 
20  
21 check point 1:22181 
22  
23 check point 2:19667 
24  
25 [lichao@sg01 replaceswitch]$ ./replaceswitch 
26  
27 check point 1:22226 
28  
29 check point 2:19813 
30  
31 [lichao@sg01 replaceswitch]$ ./replaceswitch 
32  
33 check point 1:22141 
34  
35 check point 2:19893 
36  
37 [lichao@sg01 replaceswitch]$ ./replaceswitch 
38  
39 check point 1:21640 
40  
41 check point 2:19745 
View Code

从上面可以看出,使用函数指针:(一)在某种程度上简化程序的设计(二)可以提高效率。在这个例子中,使用函数指针可以提高10%的效率。

注意:以上代码在unix环境下实现的,如果要在windows下运行,可以稍微改下,把“#define UNIXENV”行删掉即可

3 C/C++函数指针的语法

从语法上讲,有两种不兼容的函数指针形式:

(1)    指向C语言函数和C++静态成员函数的函数指针

(2)    指向C++非静态成员函数的函数指针

不兼容的原因是因为在使用C++非静态成员函数的函数指针时,需要一个指向类的实例的this指针,而前一类不需要。

3.1 定义一个函数指针

指针是变量,所以函数指针也是变量,因此可以使用变量定义的方式来定义函数指针,对于普通的指针,可以这么定义:

int a=10;

int *pa=&a;

这里,pa是一个指向整型的指针,定义这个指针的形式为:

int * pa;

区别于定义非指针的普通变量的“形式”就是在类型中间和指针名称中间加了一个“*”,所以能够表达不同的“内容”。这种形式对于表达的内容是完备的,因为它说明了两点:(1)这是一个指针(2)这是一个指向整型变量的指针

以下给出三个函数指针定义的形式,第一个是C语言的函数指针,第二个和第三个是C++的函数指针的定义形式(都是指向非静态函数成员的函数指针):

int (*pFunction)(float,char,char)=NULL;

int (MyClass::*pMemberFunction)(float,char,char)=NULL;

int (MyClass::*pConstMemberFunction)(float,char,char) const=NULL;

我们先不管函数指针的定义形式,如果让我们自己来设计指向函数的函数指针的定义形式的话,我们会怎么设计?

首先,要记住一点的就是形式一定要具备完备性,能表达出我们所要表达的内容,即指向函数这个事实。我们知道普通变量指针可以指向对应类型的任何变量,同样函数指针也应该能够指向对应类型的任何变量。对应的函数类型靠什么来确定?这个我们可以想一下C++的函数重载靠什么来区分不同的函数?这里,函数类型是靠这几个方面来确定的:(1)函数的参数个数(2)函数的参数类型(3)函数的返回值类型。所以我们要设计一种形式,这种形式定义的函数指针能够准确的指向这种函数类型的任何函数。

在C语言中这种形式为:

返回类型 (*函数指针名称)(参数类型,参数类型,参数类型,…);

嗯,定义变量的形式显然不是我们通常见到的这种形式:

类型名称 变量名称;

但是,这也是为了表达函数这种相对复杂的语义而不得已采用的非一致表示形式的方法。因为定义的这个函数指针变量,能够明确的表达出它指向什么类型的函数,这个函数都有哪些类型的参数这些信息,确切的说,它是完备的。你可能会问为什么要加括号?形式上讲能不能更简洁点?不能,因为不加括号就会产生二义性:

返回类型 *函数指针名称(参数类型,参数类型,参数类型,…);

这样的定义形式定义了一个“返回类型为‘返回类型*’参数为(参数类型,参数类型,参数类型,…)的函数而不是函数指针了。

接下来,对于C++来说,下面这样的定义形式也就不难理解了(加上类名称是为了区分不同类中定义的相同名称的成员函数):

返回类型 (类名称::*函数成员名称)(参数类型,参数类型,参数类型,….)

3.2 函数的调用规则

一般来说,不用太关注这个问题。调用规则主要是指函数被调用的方式,常见的有_stdcall,_fastcall,_pascal,_cdecl等规则。不同的规则在参数压入堆栈的顺序是不同的,同时在有调用者清理压入堆栈的参数还是由被调用者清理压入堆栈的参数上也是不同的。一般来说,如果你没有显式的说明调用规则的话,编译器会统一按照_cdecl来处理。

3.3 给函数指针赋值和调用

给函数指针赋值,就是为函数指针指定一个函数名称。这个过程很简单,下面是两个例子:

int func1(float f,int a,int b){return f*a/b;}

int func2(float f,int a,int b){return f*a*b}

然后我们给函数指针pFunction赋值:

pFunction=func1;

pFunction=&func2;

上面这段代码说明了两个问题:(1)一个函数指针可以多次赋值(想想C++中的引用)(2)取地址符号是可选的,却是推荐使用的。

我们可以思考一下为什么取地址符号是可选的,在普通的指针变量赋值时,如上面所示,需要加取地址符号,而这里却是可选的?这是由于要同时考虑到两个因素(1)避免二义性(2)形式一致性。在普通指针赋值,需要加取地址符号是为了区别于将地址还是将内容赋给指针。而在函数赋值时没有这种考虑,因为这里的语义是清晰的,加上&符号是为了和普通指针变量一致---“因为一致的时候就不容易出错”。

最后我们来使用这个函数

pFunction(10.0,’a’,’b’);

(*pFunction)(10.0,’a’,’b’);

       上面这两种使用函数指针调用函数的方式都是可以的,原因和上面一样。

下面来说明C++中的函数指针赋值和调用,这里说明非静态函数成员的情况,C++中规则要求的严格的多了。让我感觉C++就像函数指针的后爸一样,对函数指针要求特别死,或许是因为他有一个函数对象这个亲儿子。

在C++中,对于赋值,你必须要加“&”,而且你还必须再次之前已经定义好了一个类实例,取地址符号要操作于这个类实例的对应的函数成员上。在使用成员函数的指针调用成员函数时,你必须要加类实例的名称,然后再使用.*或者->*来使用成员函数指针。举例如下:

MyClass

{

public:

      int func1(float f,char a,char b)

      {

             return f*a*b;

      }

      int func2(float f,char a,char b) const

      {

return f*a/b;
      }

}

首先来赋值:

MyClass mc;

pMemberFunction= &mc.func1;  //必须要加取地址符号

pConstMemberFunction = &mc.func2;

接下来,调用函数:

(mc.*pMemberFunction)(10.0,’a’,’b’);

(mc.*pConstMemberFunction)(10.0,’a’,’b’);

我感觉,C++简直在虐待函数指针啊。

下面是一个完整的例子:

  1 /* 
  2  
  3 *Author:Choas Lee 
  4  
  5 *Date:2012-02-28 
  6  
  7 */ 
  8  
  9 #include<stdio.h> 
 10  
 11 float func1(float f,char a,char b) 
 12  
 13 { 
 14  
 15       printf("func1
"); 
 16  
 17       return f*a/b; 
 18  
 19 } 
 20  
 21 float  func2(float f,char a,char b) 
 22  
 23 { 
 24  
 25       printf("func2
"); 
 26  
 27       return f*a*b; 
 28  
 29 } 
 30  
 31 class MyClass 
 32  
 33 { 
 34  
 35 public: 
 36  
 37       MyClass(float f) 
 38  
 39       { 
 40  
 41              factor=f; 
 42  
 43       } 
 44  
 45       float func1(float f,char a,char b) 
 46  
 47       { 
 48  
 49              printf("MyClass::func1
"); 
 50  
 51              return f*a/b*factor;      
 52  
 53       } 
 54  
 55       float func2(float f,char a,char b) const 
 56  
 57       { 
 58  
 59              printf("MyClass::func2
"); 
 60  
 61              return f*a*b*factor; 
 62  
 63       } 
 64  
 65 private: 
 66  
 67       float factor; 
 68  
 69 }; 
 70  
 71 int main(int argc,char *argv[]) 
 72  
 73 { 
 74  
 75       
 76  
 77       float (*pFunction)(float,char,char)=NULL; 
 78  
 79       float (MyClass::*pMemberFunction)(float,char,char)=NULL; 
 80  
 81       float (MyClass::*pConstMemberFunction)(float,char,char)const=NULL; 
 82  
 83       
 84  
 85       float f=10.0; 
 86  
 87       char a='a',b='b'; 
 88  
 89       float result; 
 90  
 91       pFunction=func1; 
 92  
 93       printf("pointer pFunction's address is:%x
",pFunction); 
 94  
 95       result=(*pFunction)(f,a,b); 
 96  
 97         printf("result=%f
",result); 
 98  
 99       
100  
101       pFunction=&func2; 
102  
103       printf("pointer pFunction's address is:%x
",pFunction); 
104  
105       result=pFunction(f,a,b); 
106  
107         printf("result=%f
",result); 
108  
109       if(func1!=pFunction) 
110  
111              printf("not equal.
"); 
112  
113       
114  
115       pMemberFunction=&MyClass::func1; 
116  
117       MyClass mc1(0.2); 
118  
119       printf("pointer pMemberFunction's address is:%x
",pMemberFunction); 
120  
121       result=(mc1.*pMemberFunction)(f,a,b); 
122  
123         printf("result=%f
",result); 
124  
125       pConstMemberFunction=&MyClass::func2; 
126  
127       MyClass mc2(2); 
128  
129       printf("pointer pConstMemberFunction's address is:%x
",pConstMemberFunction); 
130  
131       result=(mc2.*pConstMemberFunction)(f,a,b); 
132  
133         printf("result=%f
",result); 
134  
135   
136  
137       return 0; 
138  
139 } 
View Code

运行结果为:

 1 pointer pFunction's address is:400882 
 2  
 3 func1 
 4  
 5 result=9.897959 
 6  
 7 pointer pFunction's address is:400830 
 8  
 9 func2 
10  
11 result=95060.000000 
12  
13 not equal. 
14  
15 pointer pMemberFunction's address is:400952 
16  
17 MyClass::func1 
18  
19 result=1.979592 
20  
21 pointer pConstMemberFunction's address is:4008f2 
22  
23 MyClass::func2 
24  
25 result=190120.000000 
View Code

注意:上面的代码还说明了一点就是函数指针的一些基本操作,函数指针没有普通变量指针的算术操作,但是可以进行比较操作。如上面代码所示。

使用类的静态函数成员的函数指针和使用C语言的函数很类似,这里仅仅给出一个例子和其执行结果:

程序代码为:

 1 /* 
 2  
 3 *Author:Chaos Lee 
 4  
 5 *Date:2012-02-28 
 6  
 7 */ 
 8  
 9 #include<iostream> 
10  
11 class MyClass 
12  
13 { 
14  
15 public: 
16  
17       static float plus(float a,float b) 
18  
19       { 
20  
21              return a+b; 
22  
23       }     
24  
25 }; 
26  
27 int main() 
28  
29 { 
30  
31       float result,a=10.0,b=10.0; 
32  
33       float (*p)(float,float); 
34  
35       p=&MyClass::plus; 
36  
37       result=p(a,b); 
38  
39       printf("result=%f
",result); 
40  
41       return 0; 
42  
43 } 
44 //执行结果为:
45  
46 //result=20.000000 
View Code

3.4 函数指针作为参数

如果你已经明白了函数的参数机制,而且完全理解并实践了3.3节的内容,这一节其实是很简单的。只需要在函数的参数列表中,声明一个函数指针类型的参数即可,然后再调用的时候传给它一个实参就可以了。你可以这么想象,就是把函数指针的赋值语句的等号换成了形参和实参结合的模式就行。

下面给一个简单的例子:

 1      /* 
 2  
 3 *Author:Choas Lee 
 4  
 5 *Date:2012-02-28 
 6  
 7 */ 
 8  
 9 #include<stdio.h> 
10  
11 float add(float a,float b){return a+b;} 
12  
13 float minus(float a,float b){return a-b;} 
14  
15 float multiply(float a,float b){return a*b;} 
16  
17 float divide(float a,float b){return a/b;} 
18  
19 int pass_func_pointer(float (*pFunction)(float a,float b)) 
20  
21 { 
22  
23       float result=pFunction(10.0,12.0); 
24  
25       printf("result=%f
",result); 
26  
27 } 
28  
29 int main() 
30  
31 { 
32  
33       pass_func_pointer(add); 
34  
35       pass_func_pointer(minus); 
36  
37       pass_func_pointer(multiply); 
38  
39       pass_func_pointer(divide); 
40  
41       return 0; 
42  
43 } 
View Code

输出结果为:

1 result=22.000000 
2  
3 result=-2.000000 
4  
5 result=120.000000 
6  
7 result=0.833333 
View Code

3.5 使用函数指针作为返回值

函数指针可以作为返回值。我们先类比的思考一下,如果说整型可以作为返回值,你会怎么声明函数?嗯,应该是下面这个样子的:

int func(){}

整数对应的类型为int。同样再类比以下,如果说整型指针可以作为返回值,你会怎么声明?嗯,这个貌似难度也不大:

int * func(){}

好吧,现在说函数指针如果可以作为返回值,你该怎么声明?首先要保证的一点就是返回的函数指针的类型必须是能够明显的表达在这个函数的声明或者定义形式中的,也就是说在这个形式中,要能够包含函数指针所对应的能够确定函数类型的信息:这个函数类型的返回值类型,这个函数类型的参数个数,这个函数类型的参数类型。,

现在我们在类比一次,如果要返回浮点型指针,那么返回类型应该表达为:

float *

如果要函数指针对应的函数是返回值为浮点型,带有两个参数,两个参数都是浮点型,那么返回类型应该表达为下面的表达形式:
       float (*)(float ,float )

嗯,没办法,函数的语义比较复杂,对应的表现就是形式的复杂性了。对于返回为浮点型指针的情况,定义的函数的名称放在“float *”的后面,而对于返回为上面类型的函数指针的话,定义的函数就要放在“(*)”这个括号中的*的后面了。

所以对于以下形式:

float (* func(char op) ) (float ,float)

其具体含义就是,声明了这样一个函数:

l  其名称为func,其参数的个数为1个;

l  其各个参数的类型为:op—char;

l  其返回变量(函数指针)类型为:float(*)(float,float)

再次强调:函数指针时变量哦。

到了这里之后,我们再来分析一下unix的系统调用函数signal的定义形式:

void (*signal)(int signo,void (*func)(int)))(int);

其具体含义为就是,声明了这样一个函数:

l  其函数名称为:signal

l  其参数个数为:2

l  其各个参数的类型为:signo--int, func— void (*)(int)

l  其返回的变量(函数指针)的类型为:void(*)(int)

上面这个函数比较经典,有一个参数类型为函数指针,返回值还是函数指针。

哦,我的天,如果你一步一步看到这里了,就快大功告成啦。嘿嘿,接下来看一个例子:

 1 /* 
 2  
 3 *Author:Choas Lee 
 4  
 5 *Date:2012-02-28 
 6  
 7 */ 
 8  
 9 #include<stdio.h> 
10  
11 #include<stdlib.h> 
12  
13 #include<string.h> 
14  
15 float add(float a,float b){return a+b;} 
16  
17 float minus(float a,float b){return a-b;} 
18  
19 float multiply(float a,float b){return a*b;} 
20  
21 float divide(float a,float b){return a/b;} 
22  
23   
24  
25 float(* FunctionMap(char op) )(float,float) 
26  
27 { 
28  
29       switch(op) 
30  
31       { 
32  
33              case '+': 
34  
35                     return add; 
36  
37                     break; 
38  
39              case '-': 
40  
41                     return minus; 
42  
43                     break; 
44  
45              case '*': 
46  
47                     return multiply; 
48  
49                     break; 
50  
51              case '\': 
52  
53                     return divide; 
54  
55                     break; 
56  
57              default: 
58  
59                     exit(1); 
60  
61       } 
62  
63 } 
64  
65   
66  
67 int main() 
68  
69 { 
70  
71       float a=10,b=5; 
72  
73       char ops[]={'+','-','*','\'}; 
74  
75       int len=strlen(ops); 
76  
77       int i=0; 
78  
79       float (*returned_function_pointer)(float,float); 
80  
81       for(i=0;i<len;i++) 
82  
83       { 
84  
85              returned_function_pointer=FunctionMap(ops[i]); 
86  
87              printf("the result caculated by the operator %c is %f
",ops[i],returned_function_pointer(a,b)); 
88  
89       } 
90  
91       return 0; 
92  
93 } 
View Code

计算的结果为:

1 the result caculated by the operator + is 15.000000 
2  
3 the result caculated by the operator - is 5.000000 
4  
5 the result caculated by the operator * is 50.000000 
6  
7 the result caculated by the operator  is 2.000000 
View Code

转自http://hipercomer.blog.51cto.com/4415661/792300