函数指针

函数指针教程 原版:http://www.newty.de/fpt/index.html
译者:
Lymons Lau

  导引

  1. 函数指针简介

  2.  C C++ 函数指针语法

  3. C C++里怎么实现回调函数 ?

  4.  封装C C++函数指针的仿函数

  5. 相关链接

 

   1.1 什么是函数指针 ?

   1.2  开场例子或者怎么替换Switch语句

函数指针提供了一些极其有趣,有效和绝妙的编程技术。你能用它代替switch/if语句来实现你自己的晚绑定(late-binding)或者作为回调(callback)来使用。不幸的是可能由于它的语法比较复杂几乎所有的电脑书籍和文档上都讲解的不多。即便如此,它们也只是做了相当简单和肤浅的说明。而对于函数指针你只需要明白它是什么以及它的语法,因为它和一般的指针比起来从来不用关心内存的分配和释放,所以它被使用的时候是不易产生错误的。但你要注意的是: 要时常问自己是否真的需要函数指针。因为虽然用它来实现晚绑定也很漂亮,但用既存的C++数据结构的话会使代码更可读和更简洁。另外,晚绑定的一方面实际上就是运行期(runtime: 如果你调用了一个虚拟函数,你的程序会根据一个存储所有函数的虚拟表(V-Table)自己来确定到底真正调用的是哪一个。这就要花费一些时间而用函数指针代替虚拟函数的话有可能会节省一些时间。BTW: 现代的编译器在这方面都做得非常好!就那我的Borland编译器来说这个时间就比调用一次虚拟函数能节省2%

注:(late binding)可能来自c++的术语,也为动态(dynamic binding),它主要是来实现态机制。既是一在运行时动态确定语义的机制。

1.1 什么是函数指针?

函数指针就是一个指针,也就是一个指向函数地址的变量。你必须注意的是,一个正在运行的程序在主内存内获得一段固定的空间,并且编译出来的可执行程序代码和代码中的变量都驻留在这段内存里。而在这个程序代码里的一个函数无非就是一个地址。重要的是你只要知道或者说你的编译器/处理器,怎么来解释一个指针指向的那段内存中的内容。

1.2 开场例子和怎么来代替一个Switch-语句

当你想要在程序中的某一个地方调用函数DoIt()的时候, 你只需要在源代码的这个地方放上函数DoIt()的调用即可.那么,在编译完这段代码后当你的程序执行到这个地方时这个DoIt()函数就会被调用.好像看上来一切都ok.但是,如果你不知道在代码的构建时期(build-time)哪一个函数将要被调用的话你能做什么呢?在运行期你要是决定哪一个函数要被调用的话那你需要做什么呢?这时你可能会使用一个回调函数(Callback-Function)或者你想在一堆函数列表中选择其中的一个。然而,你也能使用switch语句,在想要调用函数的地方使用不同的分支来解决这个问题。但是,这里我们讲述的是另一个方式:就是使用函数指针!

在下面的例子中,我们能看到它的主要处理就是执行一个算术操作(共有4个算术操作)。首先是使用了switch语句来实现,另外还使用了函数指针处理这个调用。这仅仅是个处理简单的例子,我想可能正常情况下没有人会使用函数指针来这么作吧;-)

 

    
    
// ------------------------------------------------------------------------------------
//  1.2 开场例子和怎么替代一个Switch-语句
//  任务:通过字符'+', '-', '*' 和 '/'
//        选择执行一个基本的算术操作. 

//  四个算术操作  使用swicth或者一个函数指针
//  在运行期选择这些函数中的一个
float  Plus    ( float  a,  float  b) {  return  a + b; }
float  Minus   ( float  a,  float  b) {  return  a - b; }
float  Multiply( float  a,  float  b) {  return  a * b; }
float  Divide  ( float  a,  float  b) {  return  a / b; }


//  switch-语句的解决方案 - <opCode> 要选择那一个函数的操作码
void  Switch( float  a,  float  b,  char  opCode)
{
   
float  result;

   
//  执行操作
    switch (opCode)
   {
      
case   ' + '  : result  =  Plus     (a, b);  break ;
      
case   ' - '  : result  =  Minus    (a, b);  break ;
      
case   ' * '  : result  =  Multiply (a, b);  break ;
      
case   ' / '  : result  =  Divide   (a, b);  break ;
   }

   cout 
<<   " Switch: 2+5= "   <<  result  <<  endl;          //  显示结果
}
 

//  函数指针的解决方案 - <pt2Func> 是一个指向带有两个float型参数
//  和float型返回值的函数. 这个函数指针“指定”了那一个函数将被执行.
void  Switch_With_Function_Pointer( float  a,  float  b,  float  ( * pt2Func)( float float ))
{
   
float  result  =  pt2Func(a, b);     //  调用函数指针

   cout 
<<   " Switch replaced by function pointer: 2-5= " ;   //  显示结果
   cout  <<  result  <<  endl;
}


//  执行样例代码
void  Replace_A_Switch()
{
   cout 
<<  endl  <<   " Executing function 'Replace_A_Switch' "   <<  endl;

   Switch(
2 5 /*  '+' 指定了 'Plus'函数将被执行  */   ' + ' );
   Switch_With_Function_Pointer(
2 5 /*  指向'Minus'函数的指针  */   & Minus);
}

提示:一个函数指针总是使用一个特定的标识来指向一个函数!然而,一旦你想要用一个函数指针指向很多函数,那的保证这些函数拥有相同的参数和返回值。

 

  导引

  1. 函数指针简介

  2.  C C++ 函数指针语法

  3. C C++里怎么实现回调函数 ?

  4.  封装C C++函数指针的仿函数

  5. 相关链接

 

2.  C C++ 函数指针语法

   2.1  定义函数指针

   2.2  调用规约

   2.3  给函数指针赋值

   2.4  比较函数指针

   2.5  通过函数指针调用函数

   2.6  怎么把函数指针作为参数进行传递 ?

   2.7  怎么返回一个函数指针?

   2.8  怎么使用函数指针数组?

 

2.1  定义函数指针

在语法上, 函数指针有两种不同的类型: 一种是指向普通函数或静态C++成员函数的指针. 另一种是指向非静态C++成员函数. 它们之间基本的区别就是所有指向非静态成员函数的函数指针需要一个隐含参数: 成员函数所属类的实例. 经常要注意的是: 这两种类型之间的函数指针是相互不兼容的.

因为函数指针无非就是一个变量, 所以它的定义也跟正常变量一样定义. 在下面的例子里,我们定义了3个函数指针,分别是pt2Function, pt2Member pt2ConstMember. 它们指向的函数的参数(一个float和两个char)和返回值都相同. C++ 的例子里,指向的函数都是必须是类TMyClass的成员函数.

//  2.1 定义一个函数指针并初始化为NULL
int  ( * pt2Function)( float char char =  NULL;                         //  C
int  (TMyClass:: * pt2Member)( float char char =  NULL;                 //  C++
int  (TMyClass:: * pt2ConstMember)( float char char const   =  NULL;      //  C++


2.2  调用规约

一般的情况下你不用考虑一个函数的调用规约(calling convention): 如果你没有指定另外的规约的话编译器是把__cdecl 作为默认的规约. 如果你想要了解的更多的话, 那就继续往后读吧... 我们说的这个调用规约就是告诉编译器做一些时而,比如怎么去传递参数或者怎么去生成函数的名字. 某些例子里还有其它的调用规约如__stdcall, __pascal __fastcall. 这些调用规约实际上是属于函数的标识(signature): 那么函数与和自己有些不同调用规约的函数指针之间是相互不兼容的! 对于Borland Microsoft 的编译器规定你要指定的调用规约应该放在返回值和函数或函数名之间. 而对于GNU GCC 的编译器是使用 __attribute__ 关键字: 也就是通过关键字__attribute__把调用规约写在函数定义的后面并用双括号把它括上. 如果有谁还知道更多的调用规约的话: 请让我知道;-) 另外,你想要知道函数调用在编译规约下是怎么工作的,请阅读Paul Carter PC Assembly Tutorial Subprograms 这一章节.

//  2.2 定义一个调用规约
void  __cdecl DoIt( float  a,  char  b,  char  c);                              //  Borland and Microsoft
void          DoIt( float  a,  char  b,  char  c)  __attribute__((cdecl));      //  GNU GCC


2.3  给函数指针赋值

把一个函数的地址赋给一个函数指针是非常容易. 你只要知道函数和成员函数的名字. 尽管大多数编译器需要在函数名的前面加上一个地址符& 但为了代码的可移植行你应该这么做. 当指向成员函数的时候你还需要在该函数前面加上类名和域操作符(::). 你还要保证, 在你赋值的地方已经被允许访问该函数.

//  2.3 给函数指针赋值
//      注意: 尽管你能删掉这个地址符也能在大多数的编译器编译通过
//      但是为了提高程序的移植性你应该使用这个正确的方法. 

//  C
int  DoIt  ( float  a,  char  b,  char  c){ printf( " DoIt/n " );    return  a + b + c; }
int  DoMore( float  a,  char  b,  char  c) const { printf( " DoMore/n " );  return  a - b + c; } 

pt2Function 
=  DoIt;       //  短格式
pt2Function  =   & DoMore;    //  使用地址符的正确赋值方法 

//  C++
class  TMyClass
{
public :
   
int  DoIt( float  a,  char  b,  char  c){ cout  <<   " TMyClass::DoIt " <<  endl;  return  a + b + c;};
   
int  DoMore( float  a,  char  b,  char  c)  const
         { cout 
<<   " TMyClass::DoMore "   <<  endl;  return  a - b + c; }; 

   
/*  more of TMyClass  */
};
 
pt2ConstMember 
=   & TMyClass::DoMore;  // 使用地址符的正确赋值方法
pt2Member  =   & TMyClass::DoIt;  //  注意: <pt2Member> 也可以指向 &DoMore函数

 

2.4  比较函数指针

对于函数指针你也能像正常写法一样使用比较操作符(==, !=). 在下面的例子里,检查pt2Function pt2Member 是否是真的等于函数DoIt TMyClass::DoMore 的地址. 相当的时候输出一段字符串.

//  2.4 比较函数指针 

//  C
if (pt2Function  > 0 ){                            //  检查是否被初始化
    if (pt2Function  ==   & DoIt)
      printf(
" Pointer points to DoIt/n " ); }
else
   printf(
" Pointer not initialized!!/n " ); 

//  C++
if (pt2ConstMember  ==   & TMyClass::DoMore)
   cout 
<<   " Pointer points to TMyClass::DoMore "   <<  endl;

 

2.5  通过函数指针调用函数

C语言里你能显示地使用解引用操作符*来调用一个函数. 还可以直接使用函数指针来代替你要调用函数的名字. C++ 里面.* ->* 这两个操作符可以分别与类事例在一起使用来调用这些类的成员函数(非静态). 如果这个调用发生这些类的成员函数里则可以使用this-指针.

//  2.5 使用函数指针调用函数
int  result1  =  pt2Function    ( 12 ' a ' ' b ' );           //  C偷懒格式
int  result2  =  ( * pt2Function) ( 12 ' a ' ' b ' );           //  C 

TMyClass instance1;
int  result3  =  (instance1. * pt2Member)( 12 ' a ' ' b ' );    //  C++
int  result4  =  ( * this . * pt2Member)( 12 ' a ' ' b ' );        //  C++ 如果this指针能被使用 

TMyClass
*  instance2  =   new  TMyClass;
int  result4  =  (instance2 ->* pt2Member)( 12 ' a ' ' b ' );   //  C++, instance2 是一个指针
delete instance2;

 

2.6  怎么把一个函数指针作为参数进行传递?

你能把一个函数指针作为一个函数的调用参数. 下列代码显示了怎么传递一个函数指针(返回值为int型,第一个参数为float型,第二,三个参数都为char型):

// ------------------------------------------------------------------------------------
//  2.6 怎么传递一个函数指针 

//  <pt2Func> 是一个指向带有一个float型和两个int型参数以及返回值是int型的函数
void  PassPtr( int  ( * pt2Func)( float char char ))
{
   
int  result  =  ( * pt2Func)( 12 ' a ' ' b ' );      //  调用函数指针
   cout  <<  result  <<  endl;


//  执行样例代码 - 'DoIt' 是在上面2.1-4定义个函数 
void  Pass_A_Function_Pointer()
{
   cout 
<<  endl  <<   " Executing 'Pass_A_Function_Pointer' "   <<  endl;
   PassPtr(
& DoIt);
}


2.7  怎么返回函数指针 ?

把一个函数指针作为返回值需要一个小技巧. 就像下面的例子一样有两种方法来返回一个带有两个参数和返回值的函数指针. 如果你想要返回一个指向成员函数的指针你只需改一下函数指针的定义/声明.

// ---------------------------------------------------------------------------
//  2.7 怎么返回一个函数指针
//      'Plus' 和'Minus'参看前面的定义. 它们都返回一个float 和 带有两个float参数 

//  直接方案: 定义了一个带有char型参数并且返回一个指向带有两个float型和返回值为float
//  型的函数. <opCode>则是决定哪个函数被返回
float  ( * GetPtr1( const   char  opCode))( float float )
{
   
if (opCode  ==   ' + ' )
      
return   & Plus;
   
else
      
return   & Minus;  //  如果传递的参数为无效时,是缺省函数
}

//  使用typedef的方案: 定义一个指向带有两个floats型和返回值是float型的函数
typedef  float ( * pt2Func)( float float ); 

//  定义带有一个char型参数和返回一个上面定一个的函数指针的函数
//  <opCode> 决定那一个函数被返回
pt2Func GetPtr2( const   char  opCode)
{
   
if (opCode  ==   ' + ' )
      
return   & Plus;
   
else
      
return   & Minus;  // 如果传递的参数为无效时,是缺省函数


//  执行样例代码
void  Return_A_Function_Pointer()
{
   cout 
<<  endl  <<   " Executing 'Return_A_Function_Pointer' "   <<  endl; 

   
//  定义函数指针并初始化为NULL
    float  ( * pt2Function)( float float =  NULL;

   pt2Function
= GetPtr1( ' + ' );    //  通过函数指针'GetPtr1'得到调用函数
   cout  <<  ( * pt2Function)( 2 4 <<  endl;    //  调用该函数 

   pt2Function
= GetPtr2( ' - ' );    // 通过函数指针'GetPtr2'得到调用函数
   cout  <<  ( * pt2Function)( 2 4 <<  endl;    // 调用该函数
}

 

2.8  怎么使用函数指针数组?

操作函数指针数组是非常有意思的事情. 这使得用一个索引来选择一个函数指针变得可能. 这个语法表示起来较困难,常常导致混淆. 下面有两种方法可以在C C++里定义并使用一个函数指针数组. 第一个方法是使用typedef, 第二个方法是直接定一个数组. 愿意使用那种方法完全取决于你.

// --------------------------------------------------------------------------- 2.8 怎么使用函数指针数组 

//  C -----------------------------------------------------------------------
//  类型定义: 'pt2Function' 现在能被作为一个类型来使用
typedef  int  ( * pt2Function)( float char char );

//  阐述一个函数指针数组是怎么工作的
void  Array_Of_Function_Pointers()
{
   printf(
" /nExecuting 'Array_Of_Function_Pointers'/n " );

   
//  定义一个数组并初始化每一个元素为NULL, <funcArr1> 和 <funcArr2> 是带有
   
//  10个函数指针的数组

   
//  第一个方法是使用 typedef
   pt2Function funcArr1[ 10 =  {NULL};

   
//  第二个方法是直接定义这个数组
    int  ( * funcArr2[ 10 ])( float char char =  {NULL};

   
//  赋于函数的地址 - 'DoIt' 和 'DoMore' 的定义请参照上面2.1-4
   funcArr1[ 0 =  funcArr2[ 1 =   & DoIt;
   funcArr1[
1 =  funcArr2[ 0 =   & DoMore;

   
/*  更多的赋值  */

   
//  使用一个索引来调用这些函数指针
   printf( " %d/n " , funcArr1[ 1 ]( 12 ' a ' ' b ' ));          //   偷懒格式
   printf( " %d/n " , ( * funcArr1[ 0 ])( 12 ' a ' ' b ' ));       //  "正确" 的调用方式
   printf( " %d/n " , ( * funcArr2[ 1 ])( 56 ' a ' ' b ' ));
   printf(
" %d/n " , ( * funcArr2[ 0 ])( 34 ' a ' ' b ' ));
}

//  C++ -------------------------------------------------------------------

//  类型定义: 'pt2Member' 现在能被作为类型来使用
typedef  int  (TMyClass:: * pt2Member)( float char char );

//  阐述成员函数指针是怎么工作的
void  Array_Of_Member_Function_Pointers()
{
   cout 
<<  endl  <<   " Executing 'Array_Of_Member_Function_Pointers' "   <<  endl;

   
//  定义一个数组并初始化每一个元素为NULL, <funcArr1> 和 <funcArr2> 是带有
   
//  10个函数指针的数组

   
//  第一个方法是使用 typedef
   pt2Member funcArr1[ 10 =  {NULL};

   
//  第二个方法是直接定义这个数组
    int  (TMyClass:: * funcArr2[ 10 ])( float char char =  {NULL};

   
//  赋于函数的地址 - 'DoIt' 和 'DoMore' 的定义请参照上面2.1-4
   funcArr1[ 0 =  funcArr2[ 1 =   & TMyClass::DoIt;
   funcArr1[
1 =  funcArr2[ 0 =   & TMyClass::DoMore;
   
/*  更多的赋值  */

   
//  使用一个索引来调用这些成员函数指针
   
//  注意: 要调用成员函数则需要一个TMyClass类的实例
   TMyClass instance;
   cout 
<<  (instance. * funcArr1[ 1 ])( 12 ' a ' ' b ' <<  endl;
   cout 
<<  (instance. * funcArr1[ 0 ])( 12 ' a ' ' b ' <<  endl;
   cout 
<<  (instance. * funcArr2[ 1 ])( 34 ' a ' ' b ' <<  endl;
   cout 
<<  (instance. * funcArr2[ 0 ])( 89 ' a ' ' b ' <<  endl;
}


  导引

  1. 函数指针简介

  2.  C C++ 函数指针语法

  3. C C++里怎么实现回调函数 ?

  4.  封装C C++函数指针的仿函数

  5. 相关链接

 

3.  怎么在 C C++中实现回调

   3.1  回调函数的概念

   3.2  怎么在C 中实现回调?

   3.3  使用qsort的例子

   3.4  怎么在C++ 中实现静态成员函数的回调?

   3.5 怎么在C++ 中实现静态成员函数的回调?

      A: 把类实例的指针作为附加参数进行传递

      B: 把类实例的指针存储在全局变量里

3.1  回调函数的概念

在回调函数的概念中当然少不了函数指针这东西. 如果你不知道怎么使用函数指针的话你可以去看一下函数指针简介 这一章. 我将使用著名的排序函数qsort来给大家解释回调函数的概念. 这个函数可以根据用户指定的排序方式来排列一个区域中的很多项目之间的顺序. 这个区域中包含的项目可以是任何类型; 它是通过void-类型的指针被传递到这个排序函数里. 另外该类型项目的大小和这个区域中项目的数量也要被传递到排序函数里.现在的问题是: 这个排序函数在不知道项目类型的任何信息的情况下怎么实现排序功能的呢? 这个答案非常简单: 就是这个函数要接收一个指向带有两个参数是void型项目指针的比较函数的函数指针, 这个比较函数则是来估算两个项目之间的顺序并返回一个int型的结果代码. 因此每一次这个排序算法需要决定两个项目之间的顺序时则仅仅是通过函数指针来调用这个比较函数即可: 这就是回调!

3.2  怎么在C里实现回调?

下面是函数 qsort 的声明:

   void qsort(void* field, size_t nElements, size_t sizeOfAnElement,
                    int(_USERENTRY *cmpFunc)(const void*, const void*));

field 指向要被排序的那个域中的第一个元素, nElements 是这个域里的项目数量, sizeOfAnElement 则是用字节表示的一个项目的大小并且 cmpFunc 指向比较函数的函数指针. 这个比较函数带有两个void-型指针的参数并返回一个型的返回值int. 在函数的定义里怎么把函数指针作为一个参数来使用的语法看起来有一些陌生. 只要看看, 怎么定义一个函数指针这一章你就能发现它完全就是相同的. 执行一个 回调就像普通函数被调用那样简单: 你只需要使用函数指针的名字而不是那个函数的名字. 就像下面显示的那样. 注意: 下面的代码中除了函数指针所有的参数都被省略了因为我们只是关注跟函数指针相关的事情.

 

     
     
    void  qsort(  ,  int (_USERENTRY  * cmpFunc)( const   void * const   void * ))
   {
      
/*  排序算法  - 注意: item1 和 item2 是 void-型的指针  */  

      
int  bigger = cmpFunc(item1, item2);   //  做一个回调 

      
/*  使用上面的结果  */
   }

3.3  qsort的使用例子

在下面的例子里排序 floats 型的项目.

 

     
     
  // -----------------------------------------------------------------------------------------
   
//  3.3 通过qsort排序函数的方法在C 中实现回调

   #include 
< stdlib.h >          //  due to:  qsort
   #include  < time.h >            //  randomize
   #include  < stdio.h >           //  printf

   
//  排序算法的比较函数
   
//  两个被比较的项目的类型都是 void-指针, 先作转换后作比较
    int  CmpFunc( const   void *  _a,  const   void *  _b)
   {
      
//  you've got to explicitly cast to the correct type
       const   float *  a  =  ( const   float * ) _a;
      
const   float *  b  =  ( const   float * ) _b;

      
if ( * >   * b)  return   1 ;               //  first item is bigger than the second one -> return 1
       else
         
if ( * ==   * b)  return    0 ;          //  equality -> return 0
          else           return   - 1 ;          //  second item is bigger than the first one -> return -1
   }

   
//  使用qsort()的例子
    void  QSortExample()
   {
      
float  field[ 100 ];

      ::randomize();                     
//  初始化随机数生成器
       for ( int  c = 0 ;c < 100 ;c ++ )              //  给域中的每个元素设定随机值
         field[c] = random( 99 );

      
//  用 qsort()进行排序
      qsort(( void * ) field,  /* 项目的数量 */   100 /* 一个项目的大小 */   sizeof (field[ 0 ]),
            
/* 比较函数 */  CmpFunc);

      
//  排完序后显示前10个元素
      printf( " The first ten elements of the sorted field are /n " );
      
for ( int  c = 0 ;c < 10 ;c ++ )
         printf(
" element #%d contains %.0f/n " , c + 1 , field[c]);
      printf(
" /n " );
   }

3.4  实现 C++ 静态成员函数的回调?

这跟在C里的实现是完全一样. 静态成员函数不需要调用类对象并就像C函数一样拥有相同标识,相同的调用规约,调用参数以及返回值.

3.5 实现 C++ 静态成员函数的回调?

封装方法

指向非静态成员的指针和普通的C指针是不一样的,因为它们需要传递一个类对象的this-指针. 而且普通的函数指针和非静态成员函数有些不同并存在不兼容的标识(signatures)! 如果你想要回调一个指定类的成员函数那你得把你的代码从普通的函数指针改成一个指向成员函数的指针. 但是如果你想要回调一个任意类的非静态成员函数那你能怎么做呢? 这有一点儿困难. 你需要写一个静态的成员函数作为包装函数. 一个静态成员函数拥有和C函数一样的标识! 然后你要把要调用的成员函数的类对象指针强转为void* 并作为附加参数或者全局变量传递到包装函数里. 有一点比较重要,如果你使用全局变量时你必须得保证这个指针总是指向一个正确的类对象! 当然你也得为成员函数传递那些调用参数. 这个包装函数把void指针强转为对应类的实例的指针并调用这个成员函数. 在后面你能看到两个例子.

A: 作为附加参数来传递类实例的指针

函数 DoItA 利用TClassA类(包含有一个回调)的对象作一些事情. 因此指向静态包装函数TClassA::Wrapper_To_Call_Display的指针要被传递到函数DoItA. 这个包装函数是一个回调函数. 你也能随便写一个类似于TClassA的类并在函数DoItA中使用当然前提是那些类立提供了包装函数. 注意:利用回调函数作为接口的设计方案会比下面使用全局变量的那个方案要有用的多.

    // -----------------------------------------------------------------------------------------
   
//  3.5 例A: 使用附加参数来回调成员函数
   
//  任务: 函数 'DoItA'中回调成员函数'Display'. 因此要使用包装函数
   
//        'Wrapper_To_Call_Display'.

   #include 
< iostream.h >     //  due to:   cout

   
class  TClassA
   {
   
public :

      
void  Display( const   char *  text) { cout  <<  text  <<  endl; };
      
static   void  Wrapper_To_Call_Display( void *  pt2Object,  char *  text);

      
/*  more of TClassA  */
   };

   
//  静态成员函数能回调成员函数Display()
    void  TClassA::Wrapper_To_Call_Display( void *  pt2Object,  char *   string )
   {
       
//  显示地强转为TClassA类的指针
       TClassA *  mySelf  =  (TClassA * ) pt2Object;

       
//  调用成员函数
       mySelf -> Display( string );
   }


   
//  隐含地做回调
   
//  注意: 当然这个函数也能作为成员函数
    void  DoItA( void *  pt2Object,  void  ( * pt2Function)( void *  pt2Object,  char *  text))
   {
      
/*  do something  */

      pt2Function(pt2Object, 
" hi, i'm calling back using a argument ;-) " );   //  make callback
   }


   
//  执行例程
    void  Callback_Using_Argument()
   {
      
//  1. TClassA类对象的初始化
      TClassA objA;

      
//  2. 为对象<objA>调用函数'DoItA' 
      DoItA(( void * & objA, TClassA::Wrapper_To_Call_Display);
   }

 

B: 把类实例的指针作为全局变量

函数DoItB利用TClassA类(包含有一个回调)的对象作一些事情. 因此指向静态包装函数TClassB::Wrapper_To_Call_Display的指针要被传递到函数DoItA. 这个包装函数是一个回调函数. 你也能随便写一个类似于TClassA的类并在函数DoItA中使用当然前提是那些类立提供了包装函数. 注意: 这个方案在已经存在的回调接口不会被改变的前提下才会比较有用,但因为使用全局变量可能会非常危险而且还可能到使严重错误所以这并不是一个很好的方案.

 

     
     
   // -----------------------------------------------------------------------------------------
   
//  3.5 例B: 使用全局变量回调成员函数
   
//  任务: 函数 'DoItB'中回调成员函数'Display'. 因此要使用包装函数
   
//        'Wrapper_To_Call_Display'..

   #include 
< iostream.h >     //  due to:   cout

   
void *  pt2Object;         //  一个可以指向任意类的全局变量

   
class  TClassB
   {
   
public :

      
void  Display( const   char *  text) { cout  <<  text  <<  endl; };
      
static   void  Wrapper_To_Call_Display( char *  text);

      
/*  more of TClassB  */
   };


   
//  静态成员函数能回调成员函数 Display()
    void  TClassB::Wrapper_To_Call_Display( char *   string )
   {
       
//  显示地将全局变量 <pt2Object> 强转成类TClassB的指针
       
//  警告: <pt2Object> 必须指向一个有效的类对象!
       TClassB *  mySelf  =  (TClassB * ) pt2Object;
 

       
//  call member
       mySelf -> Display( string );
   }


   
//  隐含地做回调
   
//  注意: 当然这个函数也能作为成员函数
    void  DoItB( void  ( * pt2Function)( char *  text))
   {
      
/*  do something  */

      pt2Function(
" hi, i'm calling back using a global ;-) " );    //  make callback
   }

   
//  execute example code
    void  Callback_Using_Global()
   {
      
//  1.  TClassB类对象的初始化
      TClassB objB;


      
//  2. 对在静态包装函数中要使用的全局变量进行赋值
      
//  重要: 一定不要忘记作这一步
      pt2Object  =  ( void * & objB;


      
//  3. call 'DoItB' for <objB>
      DoItB(TClassB::Wrapper_To_Call_Display);
   }

  导引

  1. 函数指针简介

  2.  C C++ 函数指针语法

  3. C C++里怎么实现回调函数 ?

   4.  封装C C++函数指针的仿函数

  5. 相关链接

4. 封装C C++函数指针的仿函数

   4.1  什么是仿函数 ?

   4.2  怎么实现仿函数 ?

   4.3  使用仿函数的例子

4.1  什么是仿函 ?

仿函是一个具有状态的函. C++ 你能看到它一个用一到个私有成员来储这状态类来,并利用重的操作符()来执行本身的操作. 仿函是利用模版(templates)和多(polymorphism)的概念C C++ 的函. 你能构建一个任意的成的指列表并通相同的接口来调用它,但不干扰它或者一个例的指的需求. 仅仅是要求所有的函要保持相同的返回型和参数. 候仿函通常称为闭(closures). 你也能用仿函数来实现.

4.2  怎么实现仿函数 ?

首先你需要一个提供了一个叫Call的虚函数的基类TFunctor或者一个能调用成员函数的虚拟重载操作符 ().是选择重载操作符还是函数Call当然是随便你自己的喜好了. 从这个基类你能派生一个模版 TSpecificFunctor ,在构造函数里用一个对象的指针和一个函数指针来初始化.这个派生类重载基类中的函数Call/或操作符(): 在这个重载的版本里,你能利用存储的函数指针和类对象的指针来调用成员函数. 如果你忘了怎么去使用函数指针你可以参考章节函数指针简介.

 

     
     
   // -----------------------------------------------------------------------------------------
   
//  4.2 怎么实现仿函数

   
//  抽象基类
    class  TFunctor
   {
   
public :

      
//  两个用来调用成员函数的虚函数
      
//  派生类将使用函数指针和类对象指针来实现函数调用
       virtual   void   operator ()( const   char *   string ) = 0 ;   //  call using operator
       virtual   void  Call( const   char *   string ) = 0 ;         //  call using function
   };

   
//  派生模版类
   template  < class  TClass >   class  TSpecificFunctor :  public  TFunctor
   {
   
private :
      
void  (TClass:: * fpt)( const   char * );    //  pointer to member function
      TClass *  pt2Object;                   //  pointer to object

   
public :

      
//  构造函数- 把类对象指针和函数指针
      
//  存储在两个私有变量中
      TSpecificFunctor(TClass *  _pt2Object,  void (TClass:: * _fpt)( const   char * ))
         { pt2Object 
=  _pt2Object;  fpt = _fpt; };

      
//  重载操作符 "()"
       virtual   void   operator ()( const   char *   string )
       { (
* pt2Object. * fpt)( string );};               //  execute member function

      
//  重载函数 "Call"
       virtual   void  Call( const   char *   string )
        { (
* pt2Object. * fpt)( string );};              //  execute member function
   };

4.3  仿函数的使用列

在下面的例子里我们有两个类,包括返回值是(void)和参数为(const char*)的函数Display的一个简单实现.这里我们创建两个TFunctor类的指针数组并且用封装了类对象指针和TClassA TClassB的成员函数指针的TSpecificFunctor类来初始化这个数组中的元素. 而后我们使用仿函数数组来分别调用这两个成员函数. 类对象并不直接调用函数但你得保证你的操作不能干扰这两个类的操作!

    // -----------------------------------------------------------------------------------------
   
//  4.3 仿函数的使用例子

   
//  类A
    class  TClassA{
   
public :

      TClassA(){};
      
void  Display( const   char *  text) { cout  <<  text  <<  endl; };

      
/*  more of TClassA  */
   };

   
//  类 B
    class  TClassB{
   
public :

      TClassB(){};
      
void  Display( const   char *  text) { cout  <<  text  <<  endl; };

      
/*  more of TClassB  */
   };

   
//  主程序
    int  main( int   /* argc */ char *   /* argv[] */ )
   {
      
//  1. TClassA 和TClassB的实例
      TClassA objA;
      TClassB objB;

      
//  2. 实例化TSpecificFunctor 对象 
      
//     a ) 封装指向TClassA类成员的函数指针的仿函数
      TSpecificFunctor < TClassA >  specFuncA( & objA,  & TClassA::Display);

      
//     b) 封装指向TClassB类成员的函数指针的仿函数
      TSpecificFunctor < TClassB >  specFuncB( & objB,  & TClassB::Display);
 

      
//  3. 声明基类TFunctor指针的数组, 并初始化
      TFunctor *  vTable[]  =  {  & specFuncA,  & specFuncB };


      
//  4. 不需要类对象就可以调用成员函数
      vTable[ 0 ] -> Call( " TClassA::Display called! " );         //  via function "Call"
      ( * vTable[ 1 ])   ( " TClassB::Display called! " );         //  via operator "()"


      
//  hit enter to terminate
      cout  <<  endl  <<   " Hit Enter to terminate! "   <<  endl;
      cin.
get ();

      
return   0 ;
   }
Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
各种安全相关思维导图整理收集。渗透步骤,web安全,CTF,业务安全,人工智能,区块链安全,数据安全,安全开发,无线安全,社会工程学,二进制安全,移动安全,红蓝对抗,运维安全,风控安全,linux安全.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值