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().

程序运行结果:

 

 

原文地址:https://www.cnblogs.com/bitpeng/p/4783033.html