虚拟函数与多态(Polymorphism)
来源:互联网 发布:地图填色软件 编辑:程序博客网 时间:2024/06/08 10:07
转自:侯杰《深入浅出MFC》
虚拟函数与多态(Polymorphism)
我曾经说过,前一个例子没有办法完成这样的动作:
CShape shapes[5];
... // 令5个shapes各为矩形、四方形、椭圆形、圆形、三角形
for (int i=0; i<5; i++) {
shapes[i].display; }
可是这种所谓对象操作的一般化动作在application framework中非常重要。作为
framework设计者的我,总是希望能够准备一个display函数,给我的使用者调用;不管
他根据我的这一大堆形状类别衍生出其它什么奇形怪状的类别,只要他想display,像下
面那么做就行。
CShape* pShape;
pShape->display();
Triangle
为了支持这种能力,C++提供了所谓的虚拟函数(virtual function)。
虚拟+函数?!听起来很恐怖的样子。如果你了解汽车的离合器踩下去代表汽车空档,空档表示失去引擎本身的牵制力,你就会了解「高速行驶间煞车绝不能踩离合器」的道理并矢志遵行。好,如果你真的了解为什么需要虚拟函数以及什么情况下需要它,你就能够掌握它的灵魂与内涵,真正了解它的设计原理,并且发现认为它非常人性。并且,真正知道怎么用它。
while loop
Ellipse
Rect
Circle
Circle
Circle
Square
Square
62
让我用另一个例子来展开我的说明。这个范例灵感得自Visual C++手册之一:Introdoction to C++。假设你的类别种类如下:
本图以Visual C++之「Class Info窗口」获得程序代码实作如下:
#0001 #include <string.h>#0002
#0003 //---------------------------------------------------------------
#0004 class CEmployee //职员
#0005 {
#0006 private:
#0007 char m_name[30];
#0008
#0009 public:
#0010 CEmployee();
#0011 CEmployee(const char* nm) { strcpy(m_name, nm); }
#0012 };
CEmployee
CEmployee
CManager
CManager
CWage
CWage
CSales
CSales
63
64
#0013#0014#0015#0016#0017#0018#0019#0020#0021#0022#0023#0024#0025#0026#0027#0028#0029#0030#0031#0032#0033#0034#0035#0036#0037#0038#0039#0040#0041#0042#0043#0044#0045#0046#0047#0048#0049#0050#0051#0052#0053#0054#0055
#0056#0057
//---------------------------------------------------------------
class CWage : public CEmployee{private :
float m_wage; float m_hours;
// 时薪职员是一种职员
public :
CWage(const char* nm) : CEmployee(nm) { m_wage = 250.0; m_hours = 40.0; }void setWage(float wg) { m_wage = wg; }
void setHours(float hrs) { m_hours = hrs; }
float computePay();
};//---------------------------------------------------------------
class CSales : public CWage{private :
float m_comm; float m_sale;
// 销售员是一种时薪职员
public :
CSales(const char* nm) : CWage(nm) { m_comm = m_sale = 0.0; }void setCommission(float comm) { m_comm = comm; }
void setSales(float sale) { m_sale = sale; }
float computePay();
};//---------------------------------------------------------------class CManager : public CEmployee //经理也是一种职员
{
private :
float m_salary;public :
CManager(const char* nm) : CEmployee(nm) { m_salary = 15000.0; }void setSalary(float salary) { m_salary = salary; }float computePay();
};//---------------------------------------------------------------voidmain()
{
CManager aManager("陈美静");
CSales aSales("侯俊杰");
CWage aWager("曾铭源");
}//---------------------------------------------------------------//虽然各类別的 computePay 函数都没有定义,但因为程序也没有调用之,所以无妨。
如此一来,CWage继承了CEmployee所有的成员(包括资料与函数),CSales又继承了CWage所有的成员(包括资料与函数)。在意义上, 相当于CSales拥有资料如下:
// private data of CEmployee char m_name[30];
// private data of CWage float m_wage; float m_hours;
// private data of CSales float m_comm; float m_sale;
以及函数如下:
void setWage(float wg);
void setHours(float hrs);
void setCommission(float comm);void setSale(float sales);void computePay();
从Visual C++的除错器中,我们可以看到,上例的main执行之后,程序拥有三个对象,内容(我是指成员变量)分别为:
65
从薪水说起
虚拟函数的故事要从薪水的计算说起。根据不同职员的计薪方式,我设计computePay函
数如下:
float CManager::computePay() {
return m_salary; //经理以「固定周薪」计薪。float CWage::computePay()
{
return (m_wage * m_hours); //时薪职员以「钟点费*每周工时」计薪。
}
float CSales::computePay()
{
// 销售员以「钟点费*每周工时」再加上「佣金*销售额」计薪。
return (m_wage * m_hours + m_comm * m_sale); //语法错误。但是CSales对象不能够直接取用CWage的m_wage和m_hours,因为它们是private
成员变量。所以是不是应该改为这样:
float CSales::computePay() {
return computePay() + m_comm * m_sale;
}
这也不好,我们应该指明函数中所调用的computePay究归谁属--编译器没有厉害到能
够自行判断而保证不出错。正确写法应该是:
float CSales::computePay() {
return CWage::computePay() + m_comm * m_sale;
}
这就合乎逻辑了:销售员是一般职员的一种,他的薪水应该是以时薪职员的计薪方式作为底薪,再加上额外的销售佣金。我们看看实际情况,如果有一个销售员:
CSales aSales("侯俊杰");
}
}
66
那么侯俊杰的底薪应该是:
aSales.CWage::computePay(); // 这是销售员的底薪。注意语法。
而侯俊杰的全薪应该是:
aSales.computePay(); // 这是销售员的全薪
结论是:要调用父类别的函数,你必须使用scope resolution operator(::)明白指出。接下来我要触及对象类型的转换,这关系到指针的运用,更直接关系到为什么需要虚拟函数。了解它,对于application framework 如MFC者的运用十分十分重要。
假设我们有两个对象:
CWage aWager;
CSales aSales("侯俊杰");销售员是时薪职员之一,因此这样做是合理的:
aWager = aSales; //合理,销售员必定是时薪职员。这样就不合理:
aSales = aWager; //错误,时薪职员未必是销售员。如果你一定要转换,必须使用指针,并且明显地做型别转换(cast)动作:
CWage* pWager;
CSales* pSales;
CSales aSales("侯俊杰");
pWager = &aSales; // 把一个「基础类别指针」指向衍生类别之对象,合理且自然。pSales = (CSales *)pWager; //强迫转型。语法上可以,但不符合现实生活。
真实世界中某些时候我们会以「一种动物」来总称猫啊、狗啊、兔子猴子等等。为了某种便利(这个便利稍后即可看到),我们也会想以「一个通用的指针」表示所有可能的职员类型。无论如何,销售员、时薪职员、经理,都是职员,所以下面动作合情合理:
67
CEmployee* pEmployee;
CWage aWager("曾铭源");
CSales aSales("侯俊杰");
CManager aManager("陈美静");
pEmpolyee = &aWager; // 合理,因为时薪职员必是职员pEmpolyee = &aSales; //合理,因为销售员必是职员pEmpolyee = &aManager; //合理,因为经理必是职员
也就是说,你可以把一个「职员指针」指向任何一种职员。这带来的好处是程序设计的巨大弹性,譬如说你设计一个串行(linked list),各个元素都是职员(哪一种职员都可以),你的add函数可能因此希望有一个「职员指针」作为参数:
add(CEmployee* pEmp); // pEmp可以指向任何一种职员晴天霹雳
我们渐渐接触问题的核心。上述C++性质使真实生活经验的确在计算机语言中仿真了出来,但是万里无云的日子里却出现了一个晴天霹雳:如果你以一个「基础类别之指针」指向一个「衍生类别之对象」,那么经由此指针,你就只能够调用基础类别(而不是衍生类别)所定义的函数。因此:
CSales aSales("侯俊杰");CSales* pSales;
CWage* pWager;
pSales = &aSales;
pWager = &aSales; // 以「基础类别之指针」指向「衍生类别之对象」
pWager->setSales(800.0); //错误(编译器会检测出来),
// 因为CWage并没有定义setSales函数。pSales->setSales(800.0); //正确,调用CSales::setSales函数。
虽然pSales和pWager指向同一个对象,但却因指针的原始类型而使两者之间有了差异。
延续此例,我们看另一种情况:
pWager->computePay(); // 调用CWage::computePay()pSales->computePay(); //调用CSales::computePay()
虽然pSales和pWager实际上都指向CSales对象,但是两者调用的computePay却不
68
相同。到底调用到哪个函数,必须视指针的原始类型而定,与指针实际所指之对象无关。
三个结论
我们得到了三个结论:
1.如果你以一个「基础类别之指针」指向「衍生类别之对象」,那么经由该指针你只能够调用基础类别所定义的函数。
CBase* pBase;
2.如果你以一个「衍生类别之指针」指向一个「基础类别之对象」,你必须先做明显的转型动作(explicit cast)。这种作法很危险,不符合真实生活经验,在程序设计上也会带给程序员困惑。
CDerived* pDeri;
3.如果基础类别和衍生类别都定义了「相同名称之成员函数」,那么透过对象指针调用成员函数时,到底调用到哪一个函数,必须视该指针的原始型别而定,而不是视指针实际所指之对象的型别而定。这与第1点其实意义相通。
class CBase
BaseFunc()
虽然我们可以令pBase 实际指向CD erived 对象,却因为pBase 的类型("一个CBase* 指针")使它只能够调用BaseFunc(),不能够调用D eriFunc()。
class CDerived
DeriFunc()
class CBase
CDerived *pDeri;CBase aBase("Jason");
pDeri=&aBase; //这种作法很危险,不符合真实生活经验,//在程序设计上也会带给程序员困惑。
BaseFunc()
class CDerived
DeriFunc()
69
class CBase
CBase* pBase; CDerived* pDeri;
不论你把这两个指针指向何方,由于它们的原始类型,使它们在调用同名的Com m Func() 时有着无可改变的宿命:• pBase->CommFunc()永远是指CBase::CommFunc
• pDeri->CommFunc() 永远是指CDerived::CommFunc
得到这些结论后,看看什么事情会困扰我们。前面我曾提到一个由职员组成的串行,如
果我想写一个printNames函数走访串行中的每一个元素并印出职员的名字,我们可以在
CEmployee(最基础类别)中多加一个getName函数,然后再设计一个while循环如下:int count = 0;
CEmployee* pEmp; ... while (pEmp = anIter.getNext()) {
count++;
cout << count << ' ' << pEmp->getName() << endl;}
你可以把anIter.getNext想象是一个可以走访串行的函数,它传回CEmPloyee*,也因此每一次获得的指针才可以调用定义于CEmployee中的getName。
BaseFunc()CommFunc()
class CDerived
DeriFunc()CommFunc()
计薪循环图
CEmployee* pEmp;
pEmp->getName(); pEmp->computePay();
while loop
經理
時薪職員
銷售員
時薪職員
時薪職員
銷售員
時薪職員
銷售員
70
但是,由于函数的调用是依赖指针的原始类型而不管它实际上指向何方(何种对象),因此如果上述while循环中调用的是pEmp->computePay,那么while循环所执行的将总是相同的运算,也就是CEmployee::computePay,这就糟了(销售员领到经理的薪水还不糟吗)。更糟的是,我们根本没有定义CEmployee::computePay,因为CEmployee只是个抽象概念(一个抽象类别)。指针必须落实到具象类型上如CWage或CManager或CSales,才有薪资计算公式。
虚拟函数与一般化
我想你可以体会,上述的while循环其实就是把动作「一般化」。「一般化」之所以重要,在于它可以把现在的、未来的情况统统纳入考量。将来即使有另一种名曰「顾问」的职员,上述计薪循环应该仍然能够正常运作。当然啦,「顾问」的computePay必须设计好。「一般化」是如此重要,解决上述问题因此也就迫切起来。我们需要的是什么呢?是能够「依旧以CEmpolyee指针代表每一种职员」,而又能够在「实际指向不同种类之职员」时,「调用到不同版本(不同类别中)之computePay」这种能力。
这种性质就是多态(polymorphism),靠虚拟函数来完成。再次看看那张计薪循环图:
■ 当pEmp指向经理,我希望pEmp->computePay是经理的薪水计算式,也就是CManager::computePay。
■ 当pEmp指向销售员,我希望pEmp->computePay是销售员的薪水计算式,也就是CSales::computePay。
■ 当pEmp指向时薪职员,我希望pEmp->computePay是时薪职员的薪水计算式,也就是CWage::computePay。
虚拟函数正是为了对「如果你以一个基础类别之指针指向一个衍生类别之对象,那么透 过该指针你就只能够调用基础类别所定义之成员函数」这条规则反其道而行的设计。
71
不必设计复杂的串行函数如add或getNext才能验证这件事,我们看看下面这个简单例子。如果我把职员一例中所有四个类别的computePay函数前面都加上virtual保留字,使它们成为虚拟函数,那么:
CEmployee* pEmp;
CWage aWager("曾銘源");CSales aSales("侯俊傑");CManager aManager("陳美靜");
pEmp = &aWager;
cout << pEmp->computePay(); //调用的是CWage::computePaypEmp = &aSales;
cout << pEmp->computePay(); //调用的是 CSales::computePaypEmp = &aManager;
cout << pEmp->computePay(); //调用的是CManager::computePay
现在重新回到Shape例子,我打算让display成为虚拟函数:
72
#0001#0002#0003#0004#0005#0006#0007#0008#0009#0010#0011#0012#0013#0014#0015#0016#0017#0018#0019#0020#0021#0022#0023#0024
#include <iostream.h>class CShape{
public:
virtualvoid display() { cout << "Shape \n"; }};
//------------------------------------------------class CEllipse : public CShape
{
public:
virtualvoid display() { cout << "Ellipse \n"; }};
//------------------------------------------------class CCircle : public CEllipse
{
public:
virtualvoid display() { cout << "Circle \n"; }};
//------------------------------------------------class CTriangle : public CShape
{
public:
virtualvoid display() { cout << "Triangle \n"; }};
#0025#0026#0027#0028#0029#0030#0031#0032#0033#0034#0035#0036#0037#0038#0039#0040#0041#0042#0043#0044#0045#0046#0047#0048#0049#0050#0051#0052#0053#0054#0055#0056
//------------------------------------------------class CRect : public CShape
{
public:
virtualvoid display() { cout << "Rectangle \n"; }};
//------------------------------------------------class CSquare : public CRect
{
public:
virtualvoid display() { cout << "Square \n"; }};
//------------------------------------------------void main()
{
CShape aShape;
CEllipse aEllipse;CCircle aCircle;CTriangle aTriangle;
CRect aRect;
CSquare aSquare;
CShape* pShape[6] = { &aShape,
&aEllipse,&aCircle,&aTriangle,&aRect,&aSquare };
for (int i=0; i< 6; i++) pShape[i]->display();
}//------------------------------------------------
得到的結果是:
Shape Ellipse Circle Triangle Rectangle Square
73
如果把所有类别中的virtual保留字拿掉,执行结果变成:
Shape Shape Shape Shape Shape Shape
综合Employee和Shape两例,第一个例子是:
pEmp = &aWager;
cout << pEmp->computePay();
pEmp = &aSales; pEmp = &aBoss;
cout << pEmp->computePay();
第二个例子是:
CShape* pShape[6]; for (int i=0; i< 6; i++)
cout << pEmp->computePay();
这三行程序码完全相同
74
pShape[i]->display(); //此进程序代码执行了6次。我们看到了一种奇特现象:程序代码完全一样(因为一般化了),执行结果却不相同。这
就是虚拟函数的妙用。
如果没有虚拟函数这种东西,你还是可以使用scope resolution operator(::)明白指出调用哪一个函数,但程序就不再那么优雅与弹性了。
从操作型定义来看,什么是虚拟函数呢?如果你预期衍生类别有可能重新定义某一个成员函数,那么你就在基础类别中把此函数设为virtual。MFC有两个十分十分重要的虚拟函数:与document有关的Serialize函数和与view有关的OnDraw函数。你应该在自己的CMyDoc和CMyView中改写这两个虚拟函数。
多态(Polymorphism)
你看,我们以相同的指令却唤起了不同的函数,这种性质称为Polymorphism,意思是"theability to assume many forms"(多态)。编译器无法在编译时期判断pEmp->computePay到底是调用哪一个函数,必须在执行时期才能评估之,这称为后期绑定late binding 或动态绑定dynamic binding。至于C函数或C++的non-virtual函数,在编译时期就转换为一个固定地址的调用了,这称为前期绑定early binding或静态绑定static binding。
Polymorphism的目的,就是要让处理「基础类别之对象」的程序代码,能够完全透通地继续适当处理「衍生类别之对象」。
可以说,虚拟函数是了解多态(Polymorphism)以及动态绑定的关键。同时,它也是了解如何使用MFC的关键。
让我再次提示你,当你设计一套类别,你并不知道使用者会衍生什么新的子类别出来。如果动物世界中出现了新品种名曰雅虎,类别使用者势必在CAnimal之下衍生一个CYahoo。饶是如此,身为基础类别设计者的你,可以利用虚拟函数的特性,将所有动物必定会有的行为(例如哮叫roar),规划为虚拟函数,并且规划一些一般化动作(例如「让每一种动物发出一声哮叫」)。那么,虽然,你在设计基础类别以及这个一般化动作时,无法掌握使用者自行衍生的子类别,但只要他改写了roar这个虚拟函数,你的一般化对象操作动作自然就可以调用到该函数。
再次回到前述的Shape例子。我们说CShape是抽象的,所以它根本不该有display这个动作。但为了在各具象衍生类别中绘图,我们又不得不在基础类别CShape加上display虚拟函数。你可以定义它什么也不做(空函数):
class CShape { public:
virtual void display() { } };
75
或只是给个消息:
class CShape { public:
virtual void display() { cout << "Shape \n"; } };
这两种作法都不高明,因为这个函数根本就不应该被调用(CShape是抽象的),我们根
本就不应该定义它。不定义但又必须保留一块空间(spaceholder)给它,于是C++提供
了所谓的纯虚拟函数:
class CShape { public:
virtual void display()= 0; //注意"= 0"};
纯虚拟函数不需定义其实际动作,它的存在只是为了在衍生类别中被重新定义,只是为了提供一个多态接口。只要是拥有纯虚拟函数的类别,就是一种抽象类别,它是不能够被具象化(instantiate)的,也就是说,你不能根据它产生一个对象(你怎能说一种形状为'Shape'的物体呢)。如果硬要强渡关山,会换来这样的编译消息:
error : illegal attempt to instantiate abstract class.
关于抽象类别,我还有一点补充。CCircle继承了CShape之后,如果没有改写CShape中的纯虚拟函数,那么CCircle本身也就成为一个拥有纯虚拟函数的类别,于是它也是一个抽象类别。
是对虚拟函数做结论的时候了:
■ 如果你期望衍生类别重新定义一个成员函数,那么你应该在基础类别中把此函
数设为virtual。
■ 以单一指令唤起不同函数,这种性质称为Polymorphism,意思是"the ability to
assume many forms",也就是多态。
■ 虚拟函数是C++ 语言的Polymorphism性质以及动态绑定的关键。
76
既然抽象类别中的虚拟函数不打算被调用,我们就不应该定义它,应该把它设为纯虚拟函数(在函数声明之后加上"=0"即可)。
我们可以说,拥有纯虚拟函数者为抽象类别(abstract Class),以别于所谓的具象类别(concrete class)。
抽象类别不能产生出对象实体,但是我们可以拥有指向抽象类别之指针,以便于操作抽象类别的各个衍生类别。
虚拟函数衍生下去仍为虚拟函数,而且可以省略virtual关键词。
类别与对象大解剖
你一定很想知道虚拟函数是怎么做出来的,对不对?
如果能够了解C++编译器对于虚拟函数的实现方式,我们就能够知道为什么虚拟函数可以做到动态绑定。
为了达到动态绑定(后期绑定)的目的,C++编译器透过某个表格,在执行时期「间接」调用实际上欲绑定的函数(注意「间接」这个字眼)。这样的表格称为虚拟函数表(常被称为vtable)。每一个「内含虚拟函数的类别」,编译器都会为它做出一个虚拟函数表,表中的每一笔元素都指向一个虚拟函数的地址。此外,编译器当然也会为类别加上一项
成员变量,是一个指向该虚拟函数表的指针(常被称为vptr)。举个例:class Class1 {
public : data1; data2; memfunc(); virtual vfunc1(); virtual vfunc2(); virtual vfunc3();
};
Class1对象实体在内存中占据这样的空间:
77
Class1对象实体
vtable
(*vfunc1)()
(*vfunc2)()
(*vfunc3)()
vptr
Class1::vfunc1()Class1::vfunc2()Class1::vfunc3()
Class1::memfunc()
class Class1{
public : m_data1;
78
}
m_data2;memfunc();virtual vfunc1();virtual vfunc2();virtual vfunc3();
m_data1
m_data2
C++ 类别的成员函数,你可以想象就是C语言中的函数。它只是被编译器改过名称,并增加一个参数(this指针),因而可以处理调用者(C++对象)中的成员变量。所以,你并没有在Class1对象的内存区块中看到任何与成员函数有关的任何东西。
每一个由此类别衍生出来的对象,都有这么一个vptr。当我们透过这个对象调用虚拟函数,事实上是透过vptr找到虚拟函数表,再找出虚拟函数的真正地址。
奥妙在于这个虚拟函数表以及这种间接调用方式。虚拟函数表的内容是依据类别中的虚拟函数声明次序,一一填入函数指针。衍生类别会继承基础类别的虚拟函数表(以及所有其它可以继承的成员),当我们在衍生类别中改写虚拟函数时,虚拟函数表就受了影响:表中元素所指的函数地址将不再是基础类别的函数地址,而是衍生类别的函数地址。看看这个例子:
class Class2 : public Class1 { public :
data3; memfunc(); virtual vfunc2();
};
class Class2 : public Class1{
public :
m_data3;memfunc();virtualvfunc2();
}
vtable
(*vfunc1)()
(*vfunc2)()
(*vfunc3)()
vptr
Class1::vfunc1()
Class2::vfunc2()
Class1::vfunc3()Class2::memfunc()
m_data1
m_data2
m_data3
Class2对象实体
于是,一个「指向Class1所生对象」的指针,所调用的vfunc2就是Class1::vfunc2,而
一个「指向Class2所生对象」的指针,所调用的vfunc2就是Class2::vfunc2。
动态绑定机制,在执行时期,根据虚拟函数表,做出了正确的选择。
我们解开了第二道神秘。
口说无凭,何不看点实际。观其地址,物焉C哉,下面是一个测试程序:
#0001 #include <iostream.h>
#0002 #include <stdio.h>
#0003
#0004 class ClassA
#0005 {
#0006 public:
#0007 int m_data1;
#0008 int m_data2;
#0009 void func1()
#0010 void func2()
#0011 virtual void
#0012 virtual void
#0013 };
#0014
#0015 class ClassB
#0016 {
#0017 public:
#0018 int m_data3;
#0019 void func2()
#0020 virtual void
#0021 };
#0022
#0023 class ClassC
#0024 {
{ }
{ }
vfunc1() { }vfunc2() { }
: public
{ }vfunc1()
: public
ClassA
{ }
ClassB
79
80
#0025 public:
#0026 int m_data1;
#0027 int m_data4;
#0028 void func2() { }
#0029 virtual void vfunc1() { }
#0030 };
#0031
#0032 void main()
#0033 {
#0034 cout << sizeof(ClassA) << endl;
#0035 cout << sizeof(ClassB) << endl;
#0036 cout << sizeof(ClassC) << endl;
#0037
#0038 ClassA a;
#0039 ClassB b;
#0040 ClassC c;
#0041
#0042 b.m_data1
#0043 b.m_data2
#0044 b.m_data3
#0045 c.m_data1
#0046 c.m_data2
#0047 c.m_data3
#0048 c.m_data4
#0049 c.ClassA::m_data1 = 111;
#0050
#0051 cout << b.m_data1 << endl;
#0052 cout << b.m_data2 << endl;
#0053 cout << b.m_data3 << endl;
#0054 cout << c.m_data1 << endl;
#0055 cout << c.m_data2 << endl;
#0056 cout << c.m_data3 << endl;
#0057 cout << c.m_data4 << endl;
#0058 cout << c.ClassA::m_data1 << endl;
#0059
#0060 cout << &b << endl;
#0061 cout << &(b.m_data1) << endl;
#0062 cout << &(b.m_data2) << endl;
#0063 cout << &(b.m_data3) << endl;
#0064 cout << &c << endl;
#0065 cout << &(c.m_data1) << endl;
#0066 cout << &(c.m_data2) << endl;
#0067 cout << &(c.m_data3) << endl;
#0068 cout << &(c.m_data4) << endl;
#0069 cout << &(c.ClassA::m_data1) << endl;
#0070 }
=1;=2;=3;= 11;= 22;= 33;= 44;
执行结果与分析如下:
执行結果 意义
12 Sizeof (ClassA)16 Sizeof (ClassB)24 Sizeof (ClassC)
1 b.m_data1的內容
2 b.m_data2的內容
3 b.m_data3的內容
11c.m_data1 的內容
22 c.m_data2 的內容
33 c.m_data3 的內容
44 c.m_data4 的內容
111 c.ClassA::m_data1 的內容0x0064FDCCb 对象的起始地址0x0064FDD0b.m_data1 的地址0x0064FDD4b.m_data2 的地址0x0064FDD8b.m_data3 的地址0x0064FDB0c 对象的起始地址0x0064FDC0c.m_data1 的地址0x0064FDB8c.m_data2 的地址0x0064FDBCc.m_data3 的地址0x0064FDC4c.m_data4 的地址0x0064FDB4c.ClassA::m_data1 的地址
说明
2 個 int加上一個 vptr
继承自 ClassA,再加上1 个int继承自ClassB,再加上2 个int
这个地址中的內容就是 vptr
这个地址中的內容就是 vptr
81
a、b、c对象的內容图示如下:
a (ClassA 的对象)
m_data1
m_data2
(*vfunc1)()
(*vfunc2)()
ClassA::func1()
ClassA::func2()
m_data1
m_data2
m_data3
0x0064FDCC0x0064FDD00x0064FDD40x0064FDD8
0x0064FDB00x0064FDB40x0064FDB80x0064FDBC0x0064FDC00x0064FDC4
b (ClassB 的对象)
c (ClassC 的对象)
vptr vtable
vptr vtable
vptr vtable
ClassA::vfunc1()ClassA::vfunc2()
ClassB::vfunc1()ClassA::vfunc2()
ClassB::func2()
ClassC::vfunc1()ClassA::vfunc2()
ClassC::func2()
(*vfunc1)()
(*vfunc2)()
ClassA::m_data1
m_data2
m_data3
m_data1
m_data4
(*vfunc1)()
(*vfunc2)()
Object slicing 与 虚 拟 函 数我要在这里说明虚拟函数另一个极重要的行为模式。假设有三个类别,阶层关系如下:
virtual void Serialize();
void func();
virtual void Serialize();
virtual void Serialize();
CObject
CObject
CDocument
CDocument
82
CMyDoc
CMyDoc
#0005 public:
#0006 virtual void Serialize() { cout << "CObject::Serialize() \n\n"; }
#0007 };
#0008
#0009 class CDocument : public CObject
#0010 {
#0011 public:
#0012 int m_data1;
#0013 void func() { cout << "CDocument::func()" << endl;
#0014 Serialize();
#0015 }
#0016
#0017 virtual void Serialize() { cout << "CDocument::Serialize() \n\n"; }
#0018 };
#0019
#0020 class CMyDoc : public CDocument
#0021 {
#0022 public:
#0023 int m_data2;
#0024 virtual void Serialize() { cout << "CMyDoc::Serialize() \n\n"; }
#0025 };
#0026 //---------------------------------------------------------------
#0027 void main()
#0028 {
#0029 CMyDoc mydoc;
#0030 CMyDoc* pmydoc = new CMyDoc;
#0031
#0032 cout << "#1 testing" << endl;
#0033 mydoc.func();
#0034
#0035 cout << "#2 testing" << endl;
#0036 ((CDocument*)(&mydoc))->func();
#0037
#0038 cout << "#3 testing" << endl;
#0039 pmydoc->func();
#0040
#0041 cout << "#4 testing" << endl;
#0042 ((CDocument)mydoc).func();
#0043 }
第2章 C++的重要性質
以程序表现如下:
#0001 #include <iostream.h>#0002
#0003 class CObject
#0004 {
83
第一篇 勿在浮砂築高台
- 虚拟函数与多态(Polymorphism)
- 学习笔记之深入浅出MFC 第8章 C++重要性质----虚拟函数与多态(Polymorphism)
- 虚函数(virtual)与多态(polymorphism)、动态绑定(dynamic binding)
- polymorphism(多态整理)
- java 中的多态polymorphism
- 类的多态 Polymorphism
- C++多态基础(polymorphism)
- What is the Polymorphism?(什么是多态)
- 外部多态(External-Polymorphism)
- Polymorphism in JavaScript(javascript中的多态)
- 外部多态(External-Polymorphism)
- java中的多态(Polymorphism)
- Cpp的多态Polymorphism-笔记
- 面向对象编程中的多态(polymorphism)
- Polymorphism
- Polymorphism
- polymorphism
- Polymorphism
- UVA Orchard Trees(计算几何)
- 资源的后台加载
- android 以追加形式写文件并把文件保存到SD卡中
- 非编程天才参与开源项目的14种方式
- 2012 Asia ChangChun Regional Contest(2013区域赛练习)
- 虚拟函数与多态(Polymorphism)
- 先锋商泰面试
- 卧槽,这就停课了
- 20131013-OrCAD设计重用的一些技巧-不断完善
- 《Windows核心编程系列》八谈谈用内核对象进行线程同步
- UBUNTU下挂在电脑外的存储设备
- matlab 2012b破解方法
- hdu 3622 Bomb Game
- sublime 快捷键