此段是xzp7772009本人所写:
在 类的成员函数 中, 有一个成员函数A 需要调用 该类中另一个 成员函数B,而成员函数中需要的是 B作为函数指针传入参数, 此情形如果处理不当,就会报类似于如下的错误:
“pthread_create”: 不能将参数 3 从“void *(__thiscall PartModel::* )(void *)”转换为“void *(__cdecl *)(void *)”
仔细观察,发现该问题就是需要将 __thiscall 转化为 __cdecl ,最终的解决方法不是按照下面的文章来解决,而是在类定义中,生命成员函数时,加上static ,而在成员函数的实现中,即.cpp 文件中,不需要加 static。另外只要注意调用类成员函数的指针按下面文章所述即可。
下述文章主要是比较清楚的讲述了普通函数指针以及类成员函数指针
***********************************************************************************************************************************************************************
原创作品,允许转载,转载时请务必以超链接形式标明文章  原始出处 、作者信息和本声明。否则将追究法律责任。 http://hipercomer.blog.51cto.com/4415661/792300

摘要:这篇文章详细介绍C/C++的函数指针,请先看以下几个主题:使用函数指针定义新的类型、使用函数指针作为参数、使用函数指针作为返回值、使用函数指针作为回调函数、使用函数指针数组,使用类的静态函数成员的函数指针、使用类的普通函数成员的指针、定义函数指针数组类型、使用函数指针实现后绑定以及在结构体中定义函数指针。如果您对以上这几个主题都很了解,那么恭喜您,这篇文章不适合您啦~。在一些开源软件中,如Boost, Qt, lam-mpi中我们经常看到函数指针,本文目的是彻底搞定函数指针的语法和语义,至于怎样将函数指针应用到系统架构中不在此文的讨论范围中。各位看官,有砖拍砖啊~

1.     无处不见的函数指针

使用函数指针可以设计出更优雅的程序,比如设计一个集群的通信框架的底层通信系统:首先将要每个消息的对应处理函数的指针保存映射表中(使用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. *Author:Choas Lee
  3. *Date:2012-02-28
  4. */
  5. #include<stdio.h>
  6. #define UNIXEVN
  7. #if defined(UNIXENV)
  8. #include<sys/time.h>
  9. #endif
  10. #define N 1000000
  11. #define COE 1000000
  12. float add(float a,float b){return a+b;}
  13. float minus(float a,float b){return a-b;}
  14. float multiply(float a,float b){return a*b;}
  15. float divide(float a,float b){return a/b;}
  16. typedef float (*pf)(float,float);
  17. void switch_impl(float a,float b,char op)
  18. {
  19. float result=0.0;
  20. switch(op)
  21. {
  22. case '+':
  23. result=add(a,b);
  24. break;
  25. case '-':
  26. result=minus(a,b);
  27. break;
  28. case '*':
  29. result=multiply(a,b);
  30. break;
  31. case '/':
  32. result=divide(a,b);
  33. break;
  34. }
  35. }
  36. void switch_fp_impl(float a,float b,pf p)
  37. {
  38. float result=0.0;
  39. result=p(a,b);
  40. }
  41. int conversion(struct timeval tmp_time)
  42. {
  43. return tmp_time.tv_sec*COE+tmp_time.tv_usec;
  44. }
  45. int main()
  46. {
  47. int i=0;
  48. #if defined(UNIXENV)
  49. struct timeval start_point,end_point;
  50. gettimeofday(&start_point,NULL);
  51. #endif
  52. for(i=0;i<N;i++)
  53. {
  54. switch_impl(12.32,54.14,'-');
  55. }
  56. #if defined(UNIXENV)
  57. gettimeofday(&end_point,NULL);
  58. printf("check point 1:%d\n",conversion(end_point)-conversion(start_point));
  59. gettimeofday(&start_point,NULL);
  60. #endif
  61. for(i=0;i<N;i++)
  62. {
  63. switch_fp_impl(12.32,54.14,minus);
  64. }
  65. #if defined(UNIXENV)
  66. gettimeofday(&end_point,NULL);
  67. printf("check point 2:%d\n",conversion(end_point)-conversion(start_point));
  68. #endif
  69. return 0;
  70. }

下面是执行结果:

  
  1. [lichao@sg01 replaceswitch]$ ./replaceswitch
  2. check point 1:22588
  3. check point 2:19407
  4. [lichao@sg01 replaceswitch]$ ./replaceswitch
  5. check point 1:22656
  6. check point 2:19399
  7. [lichao@sg01 replaceswitch]$ ./replaceswitch
  8. check point 1:22559
  9. check point 2:19380
  10. [lichao@sg01 replaceswitch]$ ./replaceswitch
  11. check point 1:22181
  12. check point 2:19667
  13. [lichao@sg01 replaceswitch]$ ./replaceswitch
  14. check point 1:22226
  15. check point 2:19813
  16. [lichao@sg01 replaceswitch]$ ./replaceswitch
  17. check point 1:22141
  18. check point 2:19893
  19. [lichao@sg01 replaceswitch]$ ./replaceswitch
  20. check point 1:21640
  21. check point 2:19745

从上面可以看出,使用函数指针:(一)在某种程度上简化程序的设计(二)可以提高效率。在这个例子中,使用函数指针可以提高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. *Author:Choas Lee
  3. *Date:2012-02-28
  4. */
  5. #include<stdio.h>
  6. float func1(float f,char a,char b)
  7. {
  8. printf("func1\n");
  9. return f*a/b;
  10. }
  11. float  func2(float f,char a,char b)
  12. {
  13. printf("func2\n");
  14. return f*a*b;
  15. }
  16. class MyClass
  17. {
  18. public:
  19. MyClass(float f)
  20. {
  21. factor=f;
  22. }
  23. float func1(float f,char a,char b)
  24. {
  25. printf("MyClass::func1\n");
  26. return f*a/b*factor;
  27. }
  28. float func2(float f,char a,char b) const
  29. {
  30. printf("MyClass::func2\n");
  31. return f*a*b*factor;
  32. }
  33. private:
  34. float factor;
  35. };
  36. int main(int argc,char *argv[])
  37. {
  38. float (*pFunction)(float,char,char)=NULL;
  39. float (MyClass::*pMemberFunction)(float,char,char)=NULL;
  40. float (MyClass::*pConstMemberFunction)(float,char,char)const=NULL;
  41. float f=10.0;
  42. char a='a',b='b';
  43. float result;
  44. pFunction=func1;
  45. printf("pointer pFunction's address is:%x\n",pFunction);
  46. result=(*pFunction)(f,a,b);
  47. printf("result=%f\n",result);
  48. pFunction=&func2;
  49. printf("pointer pFunction's address is:%x\n",pFunction);
  50. result=pFunction(f,a,b);
  51. printf("result=%f\n",result);
  52. if(func1!=pFunction)
  53. printf("not equal.\n");
  54. pMemberFunction=&MyClass::func1;
  55. MyClass mc1(0.2);
  56. printf("pointer pMemberFunction's address is:%x\n",pMemberFunction);
  57. result=(mc1.*pMemberFunction)(f,a,b);
  58. printf("result=%f\n",result);
  59. pConstMemberFunction=&MyClass::func2;
  60. MyClass mc2(2);
  61. printf("pointer pConstMemberFunction's address is:%x\n",pConstMemberFunction);
  62. result=(mc2.*pConstMemberFunction)(f,a,b);
  63. printf("result=%f\n",result);
  64. return 0;
  65. }

运行结果为:

  
  1. pointer pFunction's address is:400882
  2. func1
  3. result=9.897959
  4. pointer pFunction's address is:400830
  5. func2
  6. result=95060.000000
  7. not equal.
  8. pointer pMemberFunction's address is:400952
  9. MyClass::func1
  10. result=1.979592
  11. pointer pConstMemberFunction's address is:4008f2
  12. MyClass::func2
  13. result=190120.000000

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

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

程序代码为:

  
  1. /*
  2. *Author:Chaos Lee
  3. *Date:2012-02-28
  4. */
  5. #include<iostream>
  6. class MyClass
  7. {
  8. public:
  9. static float plus(float a,float b)
  10. {
  11. return a+b;
  12. }
  13. };
  14. int main()
  15. {
  16. float result,a=10.0,b=10.0;
  17. float (*p)(float,float);
  18. p=&MyClass::plus;
  19. result=p(a,b);
  20. printf("result=%f\n",result);
  21. return 0;
  22. }

执行结果为:

  
  1. result=20.000000

由于字数比较多,一篇发不完,和函数指针相关的更精彩的内容请看下一篇博文哈~

本文出自 “相信并热爱着” 博客,请务必保留此出处http://hipercomer.blog.51cto.com/4415661/792300

__thiscall 转 __cdecl 时的问题,关于函数指针相关推荐

  1. 成员函数指针与高性能的C++委托

    成员函数指针与高性能的C++委托 http://www.cnblogs.com/jans2002/archive/2006/10/13/528160.html Member Function Poin ...

  2. 成员函数指针与高性能的C++委托 (Member Function Pointers and the Fastest Possible C++ Delegates)...

    标准C++中没有真正的面向对象的函数指针.这一点对C++来说是不幸的,因为面向对象的指针(也叫做"闭包(closure)"或"委托(delegate)")在一些 ...

  3. C/C++基础知识:函数指针和指针函数的基本概念

    [函数指针] 在程序运行中,函数代码是程序的算法指令部分,它们和数组一样也占用存储空间,都有相应的地址.可以使用指针变量指向数组的首地址,也可以使用指针变量指向函数代码的首地址,指向函数代码首地址的指 ...

  4. c语言指针自定义函数,c语言函数指针定义,指针函数和函数指针的区别

    往往,我们一提到指针函数和函数指针的时候,就有很多人弄不懂.下面就由小编详细为大家介绍C语言中函数指针,指针函数和函数指针之间的区别. c语言指针函数定义: 函数指针是指向函数的指针变量. 因此&qu ...

  5. 成员函数指针与高性能的C++委托(中篇)

    成员函数指针与高性能的C++委托(中篇) 撰文:Don Clugston (接上篇) 成员函数指针--为什么那么复杂? 类的成员函数和标准的C函数有一些不同.与被显式声明的参数相似,类的成员函数有一个 ...

  6. C语言进阶——函数指针

    作者:敲代码の流川枫 博客主页:流川枫的博客 专栏:C语言从入门到进阶 语录:Stay hungry stay foolish 工欲善其事必先利其器,给大家介绍一款超牛的斩获大厂offer利器--牛客 ...

  7. c++成员函数指针的本质

    关于c++成员函数指针的声明,类型定义,赋值和调用就不再赘述了,需要了解的朋友可以看这篇文章: http://www.cppblog.com/colys/archive/2009/08/18/2578 ...

  8. c语言函数指针封装函数,C语言之函数指针、回调函数的使用

    一.背景 首先看下如下代码,这个定义是放在头文件的,在程序中tCdrvCallbackFkt也定义了另一个变量,而且括号后面还跟定义了几个变量,不理解这个定义. typedef void (PUBLI ...

  9. C++57个入门知识点_17 类的访问权限及C语言模拟类的封装(类的私有权限突破方法:编译期进行权限检查,运行期通过指针修改类的私有成员变量;利用函数指针对结构体中成员变量进行修改;CPU大小尾排列)

    接上篇:C++57个入门知识点_16 类的标准写法(类名.成员变量.成员函数及对象命名规则:成员变量一般为私有,成员函数为公有并暴露给外部使用成员变量:防止类过大,声明写在.h,实现写在.cpp,调用 ...

最新文章

  1. R语言层次聚类(hierarchical clustering):使用scale函数进行特征缩放、hclust包层次聚类(创建距离矩阵、聚类、绘制树状图dendrogram,在树状图上绘制红色矩形框)
  2. 互联网思维-产品思维(1)
  3. 没想到这家老牌AI公司越来越不“纯粹”了
  4. django 学习笔记
  5. [洛谷P1440]求m区间内的最小值
  6. 用DPM(Deformable Part Model,voc-release3.1)算法在INRIA数据集上训练自己的人体检测模型
  7. JavaScript——易班优课YOOC课群在线测试自动答题解决方案(十五)整合升级+引入jQuery
  8. 2018年第九届蓝桥杯C/C++ C组国赛 —— 第一题:年龄问题
  9. 常见的不同类型运算的转换方式
  10. 将米转换为度(经纬度) 画圆上下多出来一些区域
  11. 互联网后端技术栈大全,建议收藏!
  12. KL散度、JS散度以及交叉熵对比
  13. steam加速_PC电脑steam有没有免费试用的加速器?首选电狐加速器
  14. Java、JSP水费管理系统
  15. ROS与Arduino:ros_arduino_bridge功能包的使用解读
  16. java fps计算_帧率(FPS)计算的六种方法总结
  17. API 接口应该如何设计?如何保证安全?如何签名?如何防重?
  18. 2023年深圳技能大赛——大鹏新区潮式风味菜烹饪职业技能竞赛
  19. 2021年新规下申请测绘资质甲级、乙级需要满足的条件有哪些?
  20. prometeus, grafana部署以及监控mysql

热门文章

  1. ubuntu下jrtplib的安装和使用
  2. 让策划人员一键生成创意方案,这款 AI 产品是如何做到的?
  3. 基于Android的校园跳蚤二手闲置市场的设计与实现
  4. 推荐一款在线生成Integrity验证信息工具(SRI Hash 生成器)
  5. javaSocket笔记
  6. iPAD低价挡不住Android平板崛起
  7. Algorithm: 如何判断一个点是否在一个三角形内
  8. 运动学模型(一)-----传感器选型与技术路线
  9. Zigbee组网控制流程
  10. 上海交通大学考研复试模块小结——防火墙技术