zoukankan      html  css  js  c++  java
  • 如何使用指向类的成员函数的指针(详解!)

    我们首先复习一下"指向函数的指针"如何使用?

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. void print()  
    2. {  
    3. }  
    4. void (*pfun)(); //声明一个指向函数的指针,函数的参数是 void,函数的返回值是 void  
    5. pfun = print;   //赋值一个指向函数的指针  
    6. (*pfun)();    //使用一个指向函数的指针  

    微笑吐舌头比较简单,不是吗?为什么*pfun需要用()扩起来呢?

    微笑吐舌头因为*的运算符优先级比()低,如果不用()就成了*(pfun()).

    微笑吐舌头指向类的成员函数的指针不过多了一个类的限定而已!

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. class A  
    2. {  
    3. void speak(char *, const char *);   
    4. };  
    5.   
    6. void main()  
    7. {  
    8. A a;  
    9. void (A::*pmf)(char *, const char *);//指针的声明  
    10. pmf = &A::speak; //指针的赋值  
    11. }  

    微笑吐舌头一个指向类A 成员函数的指针声明为:

    微笑吐舌头void (A::*pmf)(char *, const char *);

    微笑吐舌头声明的解释是:pmf是一个指向A成员函数的指针,返回无类型值,函数带有二个参数,参数的类型分别是char *和const char *。除了在星号前增加A::,与声明外部函数指针的方法一样。一种更加高明的方法是使用类型定义:例如,下面的语句定义了PMA是一个指向类A成成员函数的指针,函数返回无类型值,函数参数类型为char *和const char *:

    委屈快哭了typedef void(A::*PMA)(char *,const char *);

    委屈快哭了PMA pmf= &A::strcat;//pmf是 PMF类型(类A成员指针)的变量

    委屈快哭了下面请看关于指向类的成员函数的使用示例:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. class Person  
    5. {  
    6. public:  
    7.     /*这里稍稍注意一下,我将speak()函数设置为普通的成员函数,而hello()函数设置为虚函数*/  
    8.     int value;  
    9.     void speak()      
    10.     {  
    11.         cout << "I am a person!" << endl;  
    12.         printf ("%d ", &Person::speak); /*在这里验证一下,输出一下地址就知道了!*/  
    13.     }  
    14.     virtual void hello()  
    15.     {  
    16.         cout << "Person say "Hello"" << endl;  
    17.     }  
    18.     Person()  
    19.     {  
    20.         value = 1;  
    21.     }  
    22.   
    23. };  
    24.   
    25.   
    26.   
    27. class Baizhantang: public Person  
    28. {  
    29. public:  
    30.     void speak()  
    31.     {  
    32.         cout << "I am 白展堂!" << endl;  
    33.     }  
    34.     virtual void hello()  
    35.     {  
    36.         cout << "白展堂 say "hello!"" << endl;  
    37.     }  
    38.     Baizhantang()  
    39.     {  
    40.         value = 2;  
    41.     }  
    42. };  
    43.   
    44. typedef void (Person::*p)();//定义指向Person类无参数无返回值的成员函数的指针  
    45. typedef void (Baizhantang::*q)();//定义指向Baizhantang类的无参数无返回值的指针  
    46.   
    47. int main()  
    48. {  
    49.     Person pe;  
    50.     int i = 1;  
    51.     p ip;  
    52.     ip = &Person::speak;    //ip指向Person类speak函数  
    53.     (pe.*ip)();     //这个是正确的写法!  
    54.   
    55.     //--------------------------------------------  
    56.     //  result : I am a Person!   
    57.     //           XXXXXXXXXX(表示一段地址)  
    58.     //--------------------------------------------  
    59.   
    60.     /* 
    61.     *下面是几种错误的写法,要注意! 
    62.     *       pe.*ip(); 
    63.     *       pe.(*ip)(); 
    64.     *       (pe.(*ip))(); 
    65.     */  
    66.   
    67.     Baizhantang bzt;  
    68.       
    69.     q iq = (void (Baizhantang::*)())ip; //强制转换  
    70.     (bzt.*iq)();  
    71.   
    72.     //--------------------------------------------  
    73.     //  result : I am a Person!  
    74.     //           XXXXXXXXXX(表示一段地址)  
    75.     //--------------------------------------------  
    76.   
    77.     /*  有人可能会问了:ip明明被强制转换成了Baizhantang类的成员函数的指针,为什么输出结果还是: 
    78.     * I am a Person!在C++里面,类的非虚函数都是采用静态绑定,也就是说类的非虚函数在编译前就已经 
    79.     *确定了函数地址!ip之前就是指向Person::speak函数的地址,强制转换之后,只是指针类型变了,里面 
    80.     *的值并没有改变,所以调用的还是Person.speak函数,细心的家伙会发现,输出的地址都是一致的. 
    81.     *这里要强调一下:对于类的非静态成员函数,c++编译器会给每个函数的参数添加上一个该类的指针this,这也 
    82.     *就是为什么我们在非静态类成员函数里面可以使用this指针的原因,当然,这个过程你看不见!而对于静态成员 
    83.     *函数,编译器不会添加这样一个this。 
    84.     */  
    85.       
    86.     iq = &Baizhantang::speak;   /*iq指向了Baizhantang类的speak函数*/  
    87.     ip = (void (Person::*)())iq;    /*ip接收强制转换之后的iq指针*/  
    88.     (bzt.*ip)();  
    89.   
    90.     //--------------------------------------------  
    91.     //  result : I am 白展堂!  
    92.     //--------------------------------------------  
    93.   
    94.     (bzt.*iq)();//这里我强调一下,使用了动态联编,也就是说函数在运行是才确定函数地址!  
    95.   
    96.     //--------------------------------------------  
    97.     //  result : I am 白展堂!  
    98.     //--------------------------------------------  
    99.   
    100.     /*这一部分就没有什么好讲的了,很明白了!由于speak函数是普通的成员函数,在编译时就知道 
    101.     *到了Baizhantang::speak的地址,因此(bzt.*ip)()会输出“I am 白展堂!”,即使iq被强制转换 
    102.     *成(void (Person::*)())类型的ip,但是其值亦未改变,(bzt.*iq)()依然调用iq指向处的函数 
    103.     *即Baizhantang::speak. 
    104.     */  
    105.   
    106.   
    107.     /*好了,上面讲完了普通成员函数,我们现在来玩一点好玩的,现在来聊虚函数*/  
    108.     ip = &Person::hello;    /*让ip指向Person::hello函数*/  
    109.     (pe.*ip)();  
    110.   
    111.     //--------------------------------------------  
    112.     //  result : Person say "Hello"  
    113.     //--------------------------------------------  
    114.   
    115.     (bzt.*ip)();  
    116.   
    117.     //--------------------------------------------  
    118.     //  result : 白展堂 say "Hello"  
    119.     //--------------------------------------------  
    120.   
    121.     /*咦,这就奇怪了,为何与上面的调用结果不类似?为什么两个调用结果不一致?伙伴们注意了: 
    122.     *speak函数是一个虚函数,前面说过虚函数并不是采用静态绑定的,而是采用动态绑定,所谓动态 
    123.     *绑定,就是函数地址得等到运行的时候才确定,对于有虚函数的类,编译器会给我们添加一个指针 
    124.     *vptr,指向一个虚函数表vptl,vptl里面存放着虚函数的地址,子类继承父类的时候,也会继承这样 
    125.     *一个指针,如果子类复写了虚函数,那么该表中该虚函数地址将会由父类的虚函数地址替换成子类虚 
    126.     *函数地址,编译器会把(pe.*ip)()转化成为(pe.vptr[1])(pe),加上动态绑定,结果会输出: 
    127.     *       Person say "Hello"    
    128.     *(bzt.*ip)()会被转换成(bzt.vptr[1])(pe),自然会输出: 
    129.     *       白展堂 say "Hello" 
    130.     *ps:这里我没法讲得更详细,因为解释起来肯定是很长很长的,感兴趣的话,我推荐两本书你去看一看: 
    131.     *   第一本是侯捷老师的<深入浅出MFC>,里面关于c++的虚函数特性讲的比较清楚; 
    132.     *   第二本是侯捷老师翻译的<深度探索C++对象模型>,一听名字就知道,讲这个就更详细了; 
    133.     *当然,不感兴趣的同学这段解释可以省略,对与使用没有影响! 
    134.     */  
    135.   
    136.     iq = (void (Baizhantang::*)())ip;  
    137.     (bzt.*iq)();  
    138.   
    139.     //--------------------------------------------  
    140.     //  result : 白展堂 say "Hello"  
    141.     //--------------------------------------------  
    142.       
    143.     system("pause");  
    144.     return 0;  
    145. }  
  • 相关阅读:
    shell变量解析
    visual studio code(vscode)使用
    linux虚拟机安装
    算法总结系列之八:复读机的故事散列表及其在.NET中的应用浅析(上集)
    对改善Dictionary时间性能的思考及一个线程安全的Dictionary实现
    算法总结系列之八:复读机的故事 散列表.NET应用的研究(下集)
    使用WiX打包你的应用程序之二向WiX脚本传递信息(属性)的几种方式
    当心Dictionary带来的一种隐式内存泄漏
    从DWG到XAML (II) DWFx格式解析及其和XPS的关系
    从DWG到XAML (I) 浅谈DWG历史,现状及方向
  • 原文地址:https://www.cnblogs.com/lidabo/p/6639363.html
Copyright © 2011-2022 走看看