函数指针教程

来源:互联网 发布:各数据库特点 编辑:程序博客网 时间:2024/04/28 00:58


原版: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个函数指针,分别是pt2Functionpt2Member 和 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(*a > *b) return 1;              // first item is bigger than the second one -> return 1      else         if(*a == *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;
   }


  导引

  1. 函数指针简介

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

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

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

  5. 相关链接

5.相关链接

   5.1  介绍函数指针

   5.2  回调和仿函数

   5.3  其他项

5.1  介绍函数指针

  • Using Member Function Pointers  一个borland社区的详细文章. 原本是为了给产品BC3.1提供支持但实际上它是平台独立的并且一直在维护更新.  [文章]
  • Classes having Pointers to Members 《C++注解》的第15章作者Frank Brokken.  [教程]
  • C++ FAQ-Lite  新闻组news.comp.lang.c++的FAQ. 该链接指向的是成员函数指针那部分.  [FAQ]
  • Pointing to Class Members  一个较短但很有用的文章,阐述了怎么使用成员函数指针.  [教程]
  • Declaring Function Pointers and Implementing Callbacks  关于C的函数指针的比较短小的文章. 里面也描述了函数标识的调用规约.  [教程]
  • Notes on Function Pointers   为了阐明通用编程(generic programming)的思想的一篇简要教程: 分割算法和数据. 容器类的使用函数指针的例子, 例如. 调用用户指定的函数为每一个容器元素. [教程]

5.2  回调和仿函数

  • Callbacks in C++  一个免费的回调库和一篇科学且实用的回调文章,阐述了几乎所有的回调方法. 为每一个步骤的描述中的源代码都完全开放. [站点]
  • Callbacks in C++ using Template Functors   Rich Hickey 写的一篇关于回调概念和模版仿函数的用法的详细的文章. 这个站点已经被关闭了,文章中的免费C++回调库可以再这里下载.   [文章]
  • Callbacks in C++ using Template Functors II  关于使用Rich Hickey的回调机制的一篇教程. 也解释了其他的普通方案为什么不可取的原因.  [教程]
  • Myelin: How to write Callbacks in C++ and COM  解释怎么使用纯虚类做成回调的短文章.  [文章]
  • Myelin: Implementing delegates in C++   怎么在C++中实现委派 ... [文章]
  • Callbacks in C++: The OO Way    [文章]
  • C++ Callback Solution   使用模版做成成员函数的回调. [文章]
  • Member Function Pointers and the Fastest Possible C++ Delegates  怎么在C++中实现委派... [文章]
  • Callbacks   关于回调和仿函数的简单说明.  [教程]
  • Comparison of Callback Mechanisms   讨论C++中实现回调机制的5个可选方法的可用性和性能方面的对比.  [文章]
  • libsigc++  用抽象回调连接一个类的方法,函数,或函数对象的一个完全的回调库.  [函数库]
  • Library Boost/Function   Doug Gregor写得函数对象包装器. 该文档能在 www.boost.org 里下载到 . [函数库]
  • Library Boost/Functional   Mark Rodgers写的加强函数对象适配器. 该文档能在 www.boost.org 里下载到. [函数库]

5.2  其他项

  • PC Assembly Tutorial   关于汇编的一篇很好的教程. 这里你能了解到一个函数调用是怎么工作的. 另外还有调用规约以及汇编和C代码的之间的交互. [教程]
  • The Virtual Function Mechanism  关于使用Microsoft's Visual C++汇编器的虚函数机制一篇简要介绍 [文章]

原创粉丝点击