• C++多态深入分析!


    以下分析是基于VS2010的。以后会使用G++分析看看G++如何处理多态!

     1 // polymorphic_test.cpp : 定义控制台应用程序的入口点。
     2 //
     3 
     4 /**
     5 特别注意:实现C++多态,除了基类相关函数要声明 virtual关键字,还需要派生类的该函数签名和基类完全一致!两个条件缺一不可,否则:
     6 (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
     7 关键字,基类的函数将被隐藏(注意别与重载混淆)。
     8 (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
     9 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
    10 */
    11 
    12 #include "stdafx.h"
    13 #include<iostream>
    14 using namespace std;
    15 
    16 class A
    17 {
    18 public:
    19     A():a(0){;}
    20     void foo()          {    std::cout << "BaseMember	"; }
    21     virtual void vfun()  {    std::cout << "BaseVirtual	"; }
    22 //private:    
    23     int a;
    24 };
    25 
    26 class B : public A
    27 {
    28 public:
    29     B():b(1){;}
    30     void foo()    {       std::cout << "DerivedMember	"; }
    31     void vfun()    {       std::cout << "DerivedVirtual	";    }
    32 //private:
    33     int b;
    34 };
    35 
    36 void test()
    37 {
    38     A a;
    39     B b;
    40     A *pa = NULL;
    41     B *pb = NULL;
    42 
    43     while(true){
    44         pa = &a;
    45         sizeof(a);  sizeof(b);  
    46         &a.a;   &b.b; &b.a;
    47         (int)&a.a ; (int)&b.b ;
    48         int tt = &b - &a;
    49         int t = (int)&b - (int)&a;     
    50         pa->foo();    // 非虚拟函数是编译器绑定,指针类型是什么,就调用该类型的成员函数!因此这里输出 BaseMember
    51         pa->vfun();   // 运行期绑定,调用实际的类型函数!输出 BaseVirtual	
    52 
    53         pa = &b;      
    54         pa->foo();    // 非虚拟函数是编译器绑定,指针类型是什么,就调用该类型的成员函数!由于指针类型是A*,因此调用A的成员函数。
    55                       // 因此输出 BaseMember
    56         pa->vfun();   // 运行期绑定,调用实际类型的成员函数。因此输出 DerivedVirtual	
    57         //return 0;
    58         std::cout << std::endl;
    59 
    60 
    61         pb = &b;
    62         pb->foo(); pb->vfun();    // 这里输出 DerivedMember	, DerivedVirtual	 .好理解。
    63 
    64         pb = (B*)&a;              
    65         pb->foo();               // 这里注意:非虚拟函数编译期间绑定, pb的类型是B*, 因此调用B的成员函数。输出 DerivedMember	
    66         pb->vfun();              // 虚拟函数,调用实际类型的函数,现在pb指向的&a, 因此调用A的成员函数,输出 BaseVirtual	
    67 
    68         std::cout << "
    " << std::endl;
    69     }
    70 
    71 }
    72 
    73 int _tmain(int argc, _TCHAR* argv[])
    74 {
    75     test();
    76     return 0;
    77 }

    根据调试信息,观察到的对象内存地址!

    执行pa=&a之后:

    指向pa=&b之后:

    根据内存地址,汇出的对象内存布局. (不太会用word,画的太乱了。抱歉!)

     

    根据该内存布局,我们可以总结如下:

    1. 派生类对象也有基类继承成员的独立拷贝,并非如《深入C++对象模型》所说的“派生类的继承自基类的成员是依附于基类对象“

    2. 每个对象都在栈地址上分配(没有使用new)。对象间是从高地址到低地址分配,所以a对象的起始地址大于b对象的;而在对象内部数据成员之间,是从低地址到高地址开始分配,所以b.a地址要高于b.b地址。

    3. 假如存在vptr,那么vptr在对象的最开始处分配(vptr是对象内存布局的第一个成员).

    4. 程序运行过程中,假如ptr指向不同的对象。那么调用虚函数时,会通过vptr+offset找到虚函数的入口地址。(比如,pa指向&a时,那么pa->vfun(),就是通过a对象的vptr找到vfun的地址,所以是调用A::vfun() ;当pa = &pb执行后,pa此时指向&pb,然后据此得到b对象的vptr+offset调用B::vfun()).

    5. 非虚函数,是编译期间绑定,指针实际类型是什么,就调用该类型的成员函数,与运行期所指向对象无关。因为,因为pa的类型是A*,所以pa->foo()总是调用A::foo(),同理pb->foo()总是调用B::foo().

    程序运行结果:

     

     

  • 相关阅读:
    FIREDAC(DELPHI10 or 10.1)提交数据给ORACLE数据库的一个不是BUG的BUG
    分布式系统的软肋——数据一致性
    原子操作
    Android---观察者模式的简单实现demo
    Android -- 获取网络数据并将数据存到本地数据库中
    加密模式
    Vue.js——vue-resource全攻略
    VUE---Missing space before function parentheses
    css:子元素div 上下左右居中方法总结
    扒取网站的源代码
  • 原文地址:https://www.cnblogs.com/bitpeng/p/4783033.html
Copyright © 2020-2023  润新知