函数指针与回调函数基础

转自 http://apps.hi.baidu.com/share/detail/30555651

  在高性能网络服务器程序的开发过程中,我们不能使用同步阻塞,同步非阻塞, 异步阻塞这几种IO模型。这几种IO模型只能应对吞吐量较小或一般的应用。面对巨大吞吐量的应用程序,如大型在线MMOG,游戏服务器,登录服务器等的设 计通常都要采用异步非阻塞IO模型,即事件驱动的设计方法。事件驱动的设计中,函数指针和回调函数是必不可少的编程技术,函数指针是最为基础的知识,本文 先详细描述函数指针的相关知识,打好基础,为后续的开发做好准备。另一篇文章中讨论回调函数。
第一部分   函数指针

函 数指针提供了一些非常有趣,高效和优雅的编程技术。你可以使用它们来替换switch/if语句,以实现自己的延迟绑定或者实现自己的回调函数。但是,需 要付出一些代价,因为函数指针的语法相对复杂。在很多计算机书籍和文档中,对函数指针的描述都停留在表面上。函数指针使用起来不像普通指针那样容易出错, 因为我们不会用函数指针去分配和释放内存。要使用函数指针,我们只需要理解什么是函数指针,熟悉函数指针的语法既可。

什么是函数指针?
         函数指针就是一个指针变量,用来指向函数地址。对于函数地址,我们可能有些陌生,不太容易理解。为此,我们需要明白,正在运行的程序(也叫进程)在内存中 占据一定的空间。进程包括编译好的程序代码和需要使用的变量。于是,程序代码中的函数就是一些字符域,要得到一个函数地址,也就是得到这些字符域的起始地 址。唯一重要的是,你(说编译器或处理器更恰当一些)如何解释指针指向的内存。

下页是用来说明函数指针如何替换if/switch语句要用到的几个例子函数:
// The four arithmetic operations ... one of these functions is selected
// at runtime with a swicth or a function  pointer 
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; }

1. 函数指针的三种形式:
    指向普通C函数的函数指针        --- C语言中的函数指针
    指向C++类静态成员函数的函数指针    --- C++
    指向C++类非静态成员函数的函数指针   --- C++

    前两种函数指针在本质上是一样的,那么,什么是函数指针的本质?指针的本质又是什么?我相信谁都知道,指针的本质就是地址。那么,函数指针的本质自然也就 是函    数地址。我们定义函数指针的目的就是要用它来存放某个函数的地址。问题在这里,类成员函数的地址表示和普通C函数的地址是不一样的。要表示一个类例的成员 函    数,光有函数地址还不够,因为每个类实例拥有一个非静态成员函数,而每个实例在内存中都要占据一定的空间,所以,非静态类成员函数的地址由两部分组成,一 个是    实例地址,一个是函数地址。实例地址通过隐含的this指针来传递。函数地址就是函数在实例所占内存中的偏移地址,只不过这个地址通过函数名取得,我们不 需要关心其偏移具体是多少.

    静态成员函数和非静态成员函数的区别是,静态成员函数并不是第个实例都拥有一个这样的函数。所以,静态成员函数的地址并不需要实例地址,也就是说,没有this指    针。其地址实质上和普通的C函数地址是一样的,只不过在定义函数指针时会有所不同。

    说到这里,几者的区别已经很清楚了。第一种和第三种的区别,两者的区别是:要得到一个类实例的成员函数指向非静态成员函数的函数指针需要一个隐含参数:指 向类    实例的this指针。而指向普通C函数的函数指针只需要得到函数地址既可。 这两种类型的函数指针是不兼容的,即,不可互相赋值。

    这两种类型的指针分别是怎么定义的呢?
    指向普通函数的指针:
    int (*pt2Function)(float, char, char) = NULL;                       // C

    指向类成员函数的指针:
    int (TMyClass::*pt2Member)(float, char, char) = NULL;               // C++
    int (TMyClass::*pt2ConstMember)(float, char,char) const = NULL;      // C++

    todo : 指向静态成员函数的函数指针定义

2.    调用约定(Calling Convention)
    The calling convention tells the compiler things like how to pass the arguments or how to generate the name of a function.
    下页是几种调用约定:
    __cdecal : 默认
    __stdcall
    __pascal
    __fastcall

    上面四个约定都是属于函数签名的,因此,function and functions pointers with different calling convention are incompatible with each other! For  Borland and     Microsoft compilers you specify a specific calling conventions between the return type and the function's for function pointer's name. For GNU GCC you use the     __attribute__ keyword: write the function definitions followed by the keyword __attrubute__ and then state the calling convention in double parentheses.

    // define the calling convention

    void __cdecl DoIt(float a, char b, char c);     // Borland and Microsoft compile
    void   DoIt(float a, char b, char c) __attribute__((cdecl)); // GNU GCC

3. 将函数地址赋给函数指针
    将函数地址赋给函数指针比较简单,直接将有效函数名(包括类成员函数名)作为右值赋给函数指针即可。但是,为了程序可移植性,应该在函数名前加上取地址操 作符&, 即便这是可选的。对于类成员函数,其地址必须包括类名和范围操作符(::).而且必须保证在赋值发生的地方这些函数是可见的。

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

    pt2Function = DoIt;  // short form, ommit the address operator
    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 ;}
    };

    pt2ConstMember = &TMyClass::DoMore; // correct assignment using address operator
    pt2Member = &TMyClass::DoIt; // note: pt2Member may also legally point to &DoMore

    注意,指向成员函数的指针的定义时,需要指定类名:
    int (TMyClass::*pt2Member)(float, char, char) = NULL;
    使用时不用类名
    pt2ConstMember = &TMyClass::DoMore;
   
    另一个注意点是,定义时成员函数指针时,指针符号*是在成员函数名之前;而取函数地址时,取地址符号是在类名前。
   
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) // check if initialized
        cout << "Pointer points to TMyClass::DoMore" << endl;
5. 通过函数指针调用函数
    在C语言中,通过*操作符来从函数指针得到函数地址,从而调用函数,或者直接使用函数指针代替函数名字来调用函数。在c++中,使用两操作符 .*和->* 与类的实例一起来调用实例成员函数(非静态成员函数);
    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’);     // 如果this指针有效(如在内中的其他成员内调用)
   
    TMyClass * instance2 = new TMyClass;
    int result4 = (instance2->*pt2Member)(12, ‘a’, ‘b’);  // C++中通过对象指针调用非静态成员函数
    delete instance2;

6. 如何将函数指针作为参数传递
    这是回调函数的关键,因为回调函数中就需要将函数指针作为参数传递。请看下页的例子。
   
    值得注意的是,如何将一个函数参数声明为函数指针。这和在外面定义函数指针语法是一样的。将函数DoIt地址作为实参传递给函数PassPtr的形参时,
    也是在函数名DoIt的面前加上取地址符号&.

    void PassPtr(int (*pt2Func)(float, char, char))
    {
        int result = (*pt2Func)(12, ‘a’, ‘b’);
        cout << result << endl;
    }

    void Pass_A_Function_Pointer()
    {
        cout << endl << “Executing ‘Pass_A_Function_Pointer’” << endl;
        PassPtr(&DoIt);
    }


7. 如何返回一个函数指针
    先把要用到的函数定义拷贝到这里,以便对照函数指针对他们的引用格式。
    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; }
   
    问题:如何定义一个函数,使之返回一个函数指针,并且该指针所指函数的返回类型是fload,参数为两个float。
    解答:有两种方式。一种是直接在函数定义语句中定义该函数要返回的函数指针。另一种是先用typedef定义要返回的函数指针类型,再
    用该类型定义作为函数的返回类型。

    // 直接返回函数值: GetPtr1是一个函数,它需要一个char参数,并且返回一个函数指针。该指针指向的函数有两个float参数,人    返回值类型是float.通过参数opCode确定该返回哪个函数。下面这个定义需要注意的是,第一个float定义的是函数指针所指函数的返    回类型。这个定义格式与平时所见到的函数定义语法有点不一样。返回函数指针的函数定义与普通函数的定义在形式上有些不同,需要习    惯它。用正式语言描述下面这个定义: 定义了一个名为GetPtr1的函数,其返回类型为一个函数指针,该指针所指的函数有两个float类    型参数,返回类型为float. 再说一遍,这个float是所指函数的返回类型。
    float (*GetPtr1(const char opCode))(float, float)
    {
        if(opCode == ‘+’)
            return &Plus;
        else
            return &Minus;
    }

    // 使用typedef: 定义一个函数指针指向一个返回类型是float,并且有两个float类型参数的函数。
    typedef float(*pt2Func)(float, float);
   
    注意,上面这行语句定义了一个类型名pt2Func,而不是一个普通变量。
   
    pt2Func GetPtr2(const char opCode)
    {
        if (opCode == ‘+’ )
            return &Plus;
        else
            return &Minus;
    }

    注意GetPtr2函数中,返回函数地址的方式也是直接在函数名前加取地址操作符。

    //执行示例代码
    void Return_A_Function_Pointer()
    {
        cout << “Execute ‘Return_A_Function_Pointer’” << endl;
        float (*pt2Function)(float, float) = NULL;
        pt2Function=GetPtr1(‘+’ );
        cout << (*pt2Function)(2, 4) << endl;

        pt2Function=GetPtr2(‘-’ );
        cout << (*pt2Function)(2,4);
    }   

8. 如何使用函数指针数组
   
    // C 语言
    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};
   
        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++
    typedef int(TMyClass::*pt2Member(float, char, char);
   
    void Array_Of_Member_Function_pointer()
    {
        cout << endl << “Executing ‘Array_Of_Member_function_Pointer’ << endl;
        // 方法1: 使用typedef 先定义一个指向类成员函数的函数指针类型,再用该类型来定义数组。
        pt2Member funcArr1[10] = {NULL};

        // 方法2: 直接定义数组
        int (TMyClass::*funcArr2[10](float, char, char) = {NULL};

        funcArr1[0] = funcArr2[1] = &TMyClass::DoIt;
        funcArr1[1] = funcArr2[0] = &TMyClass::DoMore;

        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](35, ‘a’, ’b’) << endl;
    }
 

posted @ 2011-10-28 15:11  OpLusX  阅读(1168)  评论(0编辑  收藏  举报