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\t"; }
21     virtual void vfun()  {    std::cout << "BaseVirtual\t"; }
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\t"; }
31     void vfun()    {       std::cout << "DerivedVirtual\t";    }
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\t
52
53         pa = &b;
54         pa->foo();    // 非虚拟函数是编译器绑定,指针类型是什么,就调用该类型的成员函数!由于指针类型是A*,因此调用A的成员函数。
55                       // 因此输出 BaseMember
56         pa->vfun();   // 运行期绑定,调用实际类型的成员函数。因此输出 DerivedVirtual\t
57         //return 0;
58         std::cout << std::endl;
59
60
61         pb = &b;
62         pb->foo(); pb->vfun();    // 这里输出 DerivedMember\t, DerivedVirtual\t .好理解。
63
64         pb = (B*)&a;
65         pb->foo();               // 这里注意:非虚拟函数编译期间绑定, pb的类型是B*, 因此调用B的成员函数。输出 DerivedMember\t
66         pb->vfun();              // 虚拟函数,调用实际类型的函数,现在pb指向的&a, 因此调用A的成员函数,输出 BaseVirtual\t
67
68         std::cout << "\n" << 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().

程序运行结果:

时间: 2024-08-10 17:00:23

C++多态深入分析!的相关文章

C#多态

通过继承,一个类可以用作多种类型:可以用作它自己的类型.任何基类型,或者在实现接口时用作任何接口类型.这称为多态性.C# 中的每种类型都是多态的.类型可用作它们自己的类型或用作 Object 实例,因为任何类型都自动将 Object 当作基类型. 多态性不仅对派生类很重要,对基类也很重要.任何情况下,使用基类实际上都可能是在使用已强制转换为基类类型的派生类对象.基类的设计者可以预测到其基类中可能会在派生类中发生更改的方面.例如,表示汽车的基类可能包含这样的行为:当考虑的汽车为小型货车或敞篷汽车时

Java基础(八):多态

一.多态的理解: 多态是同一个行为具有多个不同表现形式或形态的能力. 多态就是同一个接口,使用不同的实例而执行不同操作,如图所示: 多态性是对象多种表现形式的体现:现实中,比如我们按下 F1 键这个动作:如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档:如果当前在 Word 下弹出的就是 Word 帮助:在 Windows 下弹出的就是 Windows 帮助和支持:同一个事件发生在不同的对象上会产生不同的结果. 二.多态的优点和必要条件: 多态的优点:1. 消除类型之间的耦合关系2

当this指针成为指向之类的基类指针时,也能形成多态

this指针: 1)对象中没有函数,只有成员变量 2)对象调用函数,通过this指针告诉函数是哪个对象自己谁. 1 #include<iostream> 2 using namespace std; 3 class Shape 4 { 5 public: 6 //void cal_display(Shape* this) 7 void cal_display(){ 8 display(); 9 this->display(); 10 } 11 private: 12 virtual vo

Java多态

多态不是方法的重载,不是方法名一样方法的参数不一样,不是一个参数有多种态度就称之为多态,那是不正确的,如果这就是多态的话那么何必有方法的重载?直接改名多态就行了.父类 a = 子类对象 就是子类对象可以披上父类的衣服,只要穿上了父类的衣服就装扮成了父类 可以做父类的一些事情灵活性强.多态最重要的目的就是为了让子类转换成父类. 面向对象编程之上还有一种叫做面向功能编程,面向功能编程还可以转换成面向父类编程.比如:现实生活中,有小宝.大宝 大宝是小宝的父亲.有一天大宝不在家,小宝接到打给大宝的电话

C++中多态的实现原理

1. 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数. 2. 存在虚函数的类都有一个一维的虚函数表叫做虚表.类的对象有一个指向虚表开始的虚指针.虚表是和类对应的,虚表指针是和对象对应的. 3. 多态性是一个接口多种实现,是面向对象的核心.分为类的多态性和函数的多态性. 4. 多态用虚函数来实现,结合动态绑定. 5. 纯虚函数是虚函数再加上= 0. 6. 抽象类是指包括至少一个纯虚函数的类. 纯虚函数:virtual void breathe()= 0:即抽象类!必须在子类实

OC多态

多态:不同对象以自己的方式响应相同的消息的能力叫做多态. 由于每个类都属于该类的名字空间,这使得多态称为可能.类定义中的名字和类定义外的名字并不会冲突.类的实例变量和类方法有如下特点:和C语言中结构体中的数据成员一样,类的实例变量也位于该类独有的名字空间.类方法也同样位于该类独有的名字空间.与C语言中的方法名不同,类的方法名并不是一个全局符号.一个类中的方法名不会和其他类中同样的方法名冲突.两个完全不同的类可以实现同一个方法.方法名是对象接口的一部分.对象收到的消息的名字就是调用的方法的名字.因

多态的内存分析-转载

java运行时,在内存里分四个部分.栈,堆,数据区和代码区..举个例子String str=new String("AAA");str就放在栈里,字符串"AAA"放在堆里.所有的方法代码都放在了代码区. public class A{public void show(){System.out.println("A");}} public class B extends A{public void show(){System.out.println

多态and接口

一.多态 1.什么是多态? 解析:不同的对象对于同一个操作,做出的响应不同 具有表现多种形态的能力的特征 2.使用多态的优点 解析:为了实现统一调用 一个小例子:<父类类型作为参数> 父类(Pet) 子类(Gog,Penguin) 主人类(Master)测试类(Test) Pet public abstract class Pet { public abstract void eat(); } Dog public class Dog extends Pet{ @Override public

初始继承和多态

一.子类与父类 1.子类:父类 例如: Dog(子类):Anomal(父类) 子类(派生类)父类(基类和超类) 2.子类可以继承父类那些成员 (非私有成员,但是从技术角度,可以认为是父类的所有成员) 软件系统中的两个类符合is a时可以使用继承 例如: student is a person se is a employee 鸵鸟(ostrish)is a bird(错误结论!!!) ☆:继承模式下子类构造背后到底发生了什么? 如果我们想构建一个子类对象 //在Animal父类中 class A