虚拟函数与多态(Polymorphism)

来源:互联网 发布:地图填色软件 编辑:程序博客网 时间:2024/06/08 10:07

转自:侯杰《深入浅出MFC》


虚拟函数与多态(Polymorphism)

  我曾经说过,前一个例子没有办法完成这样的动作:

CShape shapes[5];
... //
5shapes各为矩形、四方形、椭圆形、圆形、三角形

         for (int i=0; i<5; i++)         {
            shapes[i].display;         }

可是这种所谓对象操作的一般化动作在application framework中非常重要。作为

framework设计者的我,总是希望能够准备一个display函数,给我的使用者调用;不管

他根据我的这一大堆形状类别衍生出其它什么奇形怪状的类别,只要他想display,像下

面那么做就行。

           CShape* pShape;
                        pShape->display();

Triangle

为了支持这种能力,C++提供了所谓的虚拟函数(virtual function)。

虚拟+函数?!听起来很恐怖的样子。如果你了解汽车的离合器踩下去代表汽车空档,空档表示失去引擎本身的牵制力,你就会了解「高速行驶间煞车绝不能踩离合器」的道理并矢志遵行。好,如果你真的了解为什么需要虚拟函数以及什么情况下需要它,你就能够掌握它的灵魂与内涵,真正了解它的设计原理,并且发现认为它非常人性。并且,真正知道怎么用它。

page127image9520

while loop

page127image30592page127image31016page127image31176page127image31336page127image31496page127image31656page127image31816page127image31976page127image32568page127image32728page127image32888page127image33048page127image33208page127image33368page127image33528page127image33688page127image33848page127image34008page127image34168page127image34328page127image34488page127image34648page127image34808page127image34968page127image35128page127image35288page127image35448page127image35608page127image35768page127image35928page127image36088page127image36248page127image36408page127image36568page127image36728page127image36888page127image37048page127image37208page127image37368page127image37528page127image37688page127image37848page127image38008page127image38168page127image38328page127image38488page127image38648page127image38808page127image38968page127image39128page127image39288page127image39448page127image39608page127image39768page127image39928page127image40088page127image40248page127image40408page127image40568page127image40728page127image40888page127image41048page127image41208page127image41368page127image41528page127image41688page127image41848page127image42008page127image42168page127image42328page127image42488page127image42648page127image42808page127image42968page127image43128page127image43288page127image43448page127image43608page127image43768page127image43928page127image44088page127image44248page127image44408page127image44568page127image44728page127image44888page127image45048page127image45208page127image45368page127image45528page127image45688page127image45848page127image46008page127image46168page127image46328page127image46488page127image46648page127image46808page127image46968page127image47128page127image47288page127image47448page127image47608page127image47768page127image47928page127image48088page127image48248page127image48408page127image48568page127image48728page127image48888page127image49048page127image49208page127image49368page127image49528page127image49688page127image49848page127image50008page127image50168page127image50328page127image50488page127image50912page127image51072page127image51664page127image51824page127image51984page127image52144page127image52304page127image52464page127image52624page127image52784page127image52944page127image53104page127image53264page127image53424page127image53584page127image53744page127image53904page127image54064page127image54224page127image54384page127image54544page127image54704page127image54864page127image55024page127image55184page127image55776page127image55936page127image56096page127image56256page127image56416page127image63560page127image63720page127image63880page127image64040page127image64200page127image64360page127image64520page127image64680page127image64840page127image65000page127image65160page127image66088page127image66848page127image67008page127image67168page127image67328page127image67488page127image67648page127image67808page127image67968page127image68128page127image68288page127image68712page127image68872page127image69464page127image69624page127image69784page127image69944page127image70104page127image70528page127image70688page127image70848page127image71008page127image71168page127image71328page127image71488page127image71648page127image71808page127image71968page127image72392page127image72984page127image73144page127image73304page127image73464page127image73624page127image73784page127image73944page127image74368page127image74528page127image74688page127image74848page127image75008page127image75168page127image75328page127image75488page127image75648page127image76240page127image76400page127image76560page127image76720page127image76880page127image77472page127image77632page127image78056page127image78216page127image78376page127image78536page127image78696page127image78856page127image79448page127image80040page127image80200page127image80360page127image80520page127image80680page127image80840page127image81000page127image81160page127image81320page127image81744page127image81904page127image82064page127image82224page127image82384page127image82544page127image82968page127image83128page127image83288page127image83448page127image83872page127image84032page127image84192page127image84352page127image84512page127image84672page127image84832page127image85256page127image85416page127image85576page127image85736page127image85896page127image86056page127image86216page127image86376page127image86800page127image86960page127image87120page127image87280page127image87872page127image88032page127image88192page127image88352page127image88512page127image88672page127image89096page127image89256page127image89416page127image89576page127image89736page127image90160page127image90320page127image90480page127image90640page127image90800page127image90960page127image91120page127image91280page127image91440page127image91600page127image92024page127image92184page127image92344page127image92504page127image92664page127image92824page127image93248page127image93408page127image93968

Ellipse

page127image94864page127image95024page127image95184page127image95760

Rect



page127image97256

Circle

page127image98152page127image98312page127image98872

Circle

page127image99768page127image100328

Circle

page127image101224page127image101384page127image101544page127image101704page127image101864page127image102288page127image102712page127image103136page127image103560page127image103984page127image104408page127image104832page127image105256page127image105416page127image105576page127image105736page127image105896page127image106216

Square

Square

page127image107272page127image107432page127image107592

62

page127image108200page127image108360page127image108520page127image108680
page128image576page128image1000page128image1424page128image1848page128image2272

让我用另一个例子来展开我的说明。这个范例灵感得自Visual C++手册之一:Introdoction to C++。假设你的类别种类如下:

本图以Visual C++之「Class Info窗口」获得程序代码实作如下:

#0001 #include <string.h>#0002

  1. #0003  //---------------------------------------------------------------

  2. #0004  class CEmployee //职员

  3. #0005  {

  4. #0006  private:

  5. #0007  char m_name[30];

#0008

  1. #0009  public:

  2. #0010  CEmployee();

  3. #0011  CEmployee(const char* nm) { strcpy(m_name, nm); }

  4. #0012  };

page128image7544page128image7712

CEmployee

CEmployee

page128image9032page128image9192page128image9352page128image9776

CManager

CManager

CWage

CWage

page128image11992page128image12152

CSales

CSales

63

page128image13920page128image14344page128image14768page128image15192
page129image480page129image640page129image800page129image960page129image1120

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 函数都没有定义,但因为程序也没有调用之,所以无妨。

page129image21848page129image22008page129image22168page129image22328
page130image464page130image888page130image1312page130image1736page130image2160

如此一来,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执行之后,程序拥有三个对象,内容(我是指成员变量)分别为:

page130image7288

65

page130image7904page130image8328page130image8488page130image8648page130image9072
page131image472page131image896page131image1320page131image1744page131image2168

从薪水说起

虚拟函数的故事要从薪水的计算说起。根据不同职员的计薪方式,我设计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对象不能够直接取用CWagem_wagem_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

page131image11888page131image12312page131image12472page131image12632page131image13056
page132image456page132image880page132image1304page132image1728page132image2152

那么侯俊杰的底薪应该是:
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

page132image8624page132image9048page132image9208page132image9368page132image9792
page133image456page133image880page133image1304page133image1728page133image2152

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函数。

虽然pSalespWager指向同一个对象,但却因指针的原始类型而使两者之间有了差异。

延续此例,我们看另一种情况:
pWager->computePay(); // 调用CWage::computePay()pSales->computePay(); //调用CSales::computePay()

虽然pSalespWager实际上都指向CSales对象,但是两者调用的computePay却不

68

page133image10248page133image10672page133image10832page133image10992page133image11416
page134image1080page134image1504page134image1928page134image2352page134image2776
  相同。到底调用到哪个函数,必须视指针的原始类型而定,与指针实际所指之对象无关。

三个结论

我们得到了三个结论:

1.如果你以一个「基础类别之指针」指向「衍生类别之对象」,那么经由该指针你只能够调用基础类别所定义的函数。

                                 CBase* pBase;

2.如果你以一个「衍生类别之指针」指向一个「基础类别之对象」,你必须先做明显的转型动作(explicit cast)。这种作法很危险,不符合真实生活经验,在程序设计上也会带给程序员困惑。

                                  CDerived* pDeri;

3.如果基础类别和衍生类别都定义了「相同名称之成员函数」,那么透过对象指调用成员函数时,到底调用到哪一个函数,必须视该指针的原始型别而定,而不是视指针实际所指之对象的型别而定。这与第1点其实意义相通。

page134image6176page134image6336

class CBase

page134image7248page134image7408page134image7568page134image7728page134image7888page134image8048page134image8208page134image8368page134image8528page134image8688

BaseFunc()

page134image9296page134image9456page134image9616page134image9776page134image9936page134image10096page134image10256

虽然我们可以令pBase 实际指向CD erived 对象,却因为pBase 的类型("一个CBase* 指针")使它只能够调用BaseFunc(),不能够调用D eriFunc()。

page134image11392page134image11552page134image11712page134image11872page134image12032page134image12192page134image12352page134image12512page134image12672page134image12832page134image12992page134image13152page134image13312page134image13472page134image13632
class CDerived

DeriFunc()

page134image15840page134image16000

class CBase

CDerived *pDeri;CBase aBase("Jason");

pDeri=&aBase; //这种作法很危险,不符合真实生活经验,//在程序设计上也会带给程序员困惑。

page134image17800page134image18392page134image18552page134image18712page134image18872page134image19032page134image19192page134image19352page134image19512page134image19672

BaseFunc()

page134image20280page134image20440page134image20600page134image20760page134image20920page134image21080page134image21240page134image21400page134image21560page134image21720page134image21880page134image22040page134image22200page134image22360page134image22520page134image22680page134image22840page134image23000page134image23160page134image23320page134image23480page134image23640
class CDerived

DeriFunc()

page134image25416page134image25576page134image25736

69

page134image26608page134image27032page134image27456page134image27880
page135image3240page135image3400page135image3560page135image3720page135image3880page135image4040

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

page135image9824
BaseFunc()CommFunc()
page135image10576
class CDerived
DeriFunc()CommFunc()

计薪循环图

CEmployee* pEmp;
             pEmp->getName();             pEmp->computePay();

while loop

page135image14224page135image14648page135image15072page135image15232page135image15392page135image15552page135image15712page135image15872page135image16032page135image16192page135image16352page135image16512page135image16672page135image16832page135image16992page135image17152page135image17312page135image17472page135image17632page135image17792page135image17952page135image18112page135image18272page135image18432page135image18592page135image18752page135image18912page135image19072page135image19232page135image19392page135image19552page135image19712page135image19872page135image20032page135image20192page135image20352page135image20512page135image20672page135image20832page135image20992page135image21152page135image21312page135image21472page135image21632page135image21792page135image21952page135image22112page135image22272page135image22432page135image22592page135image22752page135image22912page135image23072page135image23232page135image23392page135image23552page135image23712page135image23872page135image24032page135image24192page135image24352page135image24512page135image24672page135image24832page135image24992page135image25152page135image25312page135image25472page135image25632page135image25792page135image25952page135image26112page135image26272page135image26432page135image26592page135image26752page135image27344page135image27504page135image27664page135image27824page135image27984page135image28144page135image28304page135image28464page135image28624page135image28784page135image28944page135image29104page135image29264page135image29424page135image29584page135image29744page135image29904page135image30496page135image30656page135image30816page135image30976page135image31136page135image31296page135image31720page135image32312page135image32904page135image33064page135image33656page135image33816page135image34408page135image34568page135image34728page135image34888page135image35048page135image35208page135image35368page135image35528page135image35688page135image35848page135image36008page135image36168page135image36328page135image36488page135image36648page135image36808page135image36968page135image37128page135image37288page135image37880page135image38040page135image38632page135image39224page135image39648page135image39808page135image39968page135image40560page135image41152page135image41312page135image41472page135image41632page135image41792page135image41952page135image42544page135image42704page135image42864page135image43456page135image43616page135image43776page135image43936page135image44096page135image44256page135image44848page135image52664page135image52824page135image52984page135image53144page135image53304page135image53464page135image53624page135image53784page135image53944page135image54104page135image54696page135image55288page135image55448page135image56040page135image56200page135image56360page135image56952page135image57544page135image57704page135image58296page135image58456page135image58616page135image59208page135image59368page135image59960page135image60552page135image61144page135image61304page135image61896page135image62056page135image62648page135image62808page135image62968page135image63128page135image63720page135image63880page135image64040page135image64200page135image64624page135image64784page135image64944page135image65104page135image65264page135image65424page135image65584page135image65744page135image66336page135image66928page135image67088page135image67248page135image67408page135image67568page135image67728page135image68320page135image68480page135image69072page135image69232page135image69824page135image70416page135image70576page135image70736page135image71160page135image71320page135image71912page135image72072page135image72232page135image72392page135image72552page135image72712page135image72872page135image73032page135image73192page135image73352page135image73512page135image73672page135image73832page135image73992page135image74152page135image74312page135image74472page135image74632page135image75056page135image75216page135image75376page135image75536page135image75960page135image76120page135image76544page135image76968page135image77128page135image77720page135image78144page135image78304page135image78464page135image78888page135image79048page135image79208page135image79632page135image79792page135image79952page135image80112page135image80272page135image80432page135image80592page135image80752page135image80912page135image81072page135image81232page135image81392page135image81552page135image82144page135image82304page135image82464page135image82888page135image83048page135image83208page135image83632page135image83792page135image83952page135image84112page135image84272page135image84432page135image84592page135image84752page135image84912page135image85072page135image85232page135image85656page135image86248page135image86408page135image86568page135image86728page135image86888page135image87048page135image87208page135image87632page135image87792page135image87952page135image88112page135image88272page135image88696page135image88856page135image89016page135image89176page135image89336page135image89496page135image89656page135image89816page135image89976page135image90136page135image90560page135image90720page135image90880page135image91040page135image91464page135image91624page135image91784page135image91944page135image92104page135image92264page135image92424page135image92584page135image93008page135image93168page135image93328page135image93488page135image93912page135image94072page135image94232page135image94392page135image94984page135image95144page135image95304page135image95464page135image96024

經理

page135image97456

時薪職員

page135image99176

銷售員

page135image100752

時薪職員

page135image102472

時薪職員

page135image104192

銷售員

page135image105768

時薪職員

page135image107488

銷售員

page135image108664page135image109088page135image109512page135image109936page135image110360page135image110784page135image111208page135image111632page135image112056page135image112216page135image112376

70

page135image112984page135image113144page135image113304page135image113464
page136image464page136image888page136image1312page136image1736page136image2160

但是,由于函数的调用是依赖指针的原始类型而不管它实际上指向何方(何种对象),因此如果上述while循环中调用的是pEmp->computePay,那么while循环所执行的将总是相同的运算,也就是CEmployee::computePay,这就糟了(销售员领到经理的薪水还不糟吗)。更糟的是,我们根本没有定义CEmployee::computePay,因为CEmployee只是个抽象概念(一个抽象类别)。指针必须落实到具象类型上如CWageCManagerCSales,才有薪资计算公式。

虚拟函数与一般化

我想你可以体会,上述的while循环其实就是把动作「一般化」。「一般化」之所以重要,在于它可以把现在的、未来的情况统统纳入考量。将来即使有另一种名曰「顾问」的职员,上述计薪循环应该仍然能够正常运作。当然啦,「顾问」的computePay必须设计好。「一般化」是如此重要,解决上述问题因此也就迫切起来。我们需要的是什么呢?是能够「依旧以CEmpolyee指针代表每一种职员」,而又能够在「实际指向不同种类之职员」时,「调用到不同版本(不同类别中)之computePay」这种能力。

这种性质就是多态(polymorphism),靠虚拟函数来完成。再次看看那张计薪循环图:

■ 当pEmp指向经理,我希望pEmp->computePay是经理的薪水计算式,也就是CManager::computePay

■ 当pEmp指向销售员,我希望pEmp->computePay是销售员的薪水计算式,也就是CSales::computePay

■ 当pEmp指向时薪职员,我希望pEmp->computePay是时薪职员的薪水计算式,也就是CWage::computePay

  虚拟函数正是为了对「如果你以一个基础类别之指针指向一个衍生类别之对象,那么透  过该指针你就只能够调用基础类别所定义之成员函数」这条规则反其道而行的设计。
page136image7544page136image7704

71

page136image8312page136image8736page136image9160page136image9584
page137image480page137image640page137image800page137image960page137image1120

不必设计复杂的串行函数如addgetNext才能验证这件事,我们看看下面这个简单例子。如果我把职员一例中所有四个类别的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"; }};

page137image16824page137image16984page137image17144page137image17304
page138image488page138image912page138image1336page138image1760page138image2184
#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

page138image18176page138image18600page138image18760page138image18920page138image19344
page139image528page139image688page139image848page139image1008page139image1168

如果把所有类别中的virtual保留字拿掉,执行结果变成:

      Shape      Shape      Shape      Shape      Shape      Shape

综合EmployeeShape两例,第一个例子是:

 pEmp = &aWager;
 cout << pEmp->computePay();
 pEmp = &aSales; pEmp = &aBoss;
 cout << pEmp->computePay();

第二个例子是:

 CShape* pShape[6]; for (int i=0; i< 6; i++)

page139image6296page139image7392page139image7552
cout << pEmp->computePay();

这三行程序码完全相同

74

pShape[i]->display(); //此进程序代码执行了6次。我们看到了一种奇特现象:程序代码完全一样(因为一般化了),执行结果却不相同。这

就是虚拟函数的妙用。

如果没有虚拟函数这种东西,你还是可以使用scope resolution operator(::)明白指出调用哪一个函数,但程序就不再那么优雅与弹性了。

从操作型定义来看,什么是虚拟函数呢?如果你预期衍生类别有可能重新定义某一个成员函数,那么你就在基础类别中把此函数设为virtualMFC有两个十分十分重要的虚拟函数:与document有关的Serialize函数和与view有关的OnDraw函数。你应该在自己的CMyDocCMyView中改写这两个虚拟函数。 


多态(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

page140image8528page140image8952page140image9112page140image9272page140image9696
page141image456page141image880page141image1304page141image1728page141image2152

或只是给个消息:

    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性质以及动态绑定的关键。

page141image9704

76

page141image10312page141image10736page141image11160page141image11584
page142image456page142image880page142image1304page142image1728page142image2152
  • 既然抽象类别中的虚拟函数不打算被调用,我们就不应该定义它,应该把它设为纯虚拟函数(在函数声明之后加上"=0"即可)。

  • 我们可以说,拥有纯虚拟函数者为抽象类别(abstract Class),以别于所谓的具象类别(concrete class)

  • 抽象类别不能产生出对象实体,但是我们可以拥有指向抽象类别之指针,以便于操作抽象类别的各个衍生类别。

  • 虚拟函数衍生下去仍为虚拟函数,而且可以省略virtual关键词。

    类别与对象大解剖

      你一定很想知道虚拟函数是怎么做出来的,对不对?

    如果能够了解C++编译器对于虚拟函数的实现方式,我们就能够知道为什么虚拟函数可以做到动态绑定。

    为了达到动态绑定(后期绑定)的目的,C++编译器透过某个表格,在执行时期「间接」调用实际上欲绑定的函数(注意「间接」这个字眼)。这样的表格称为虚拟函数表(常被称为vtable)。每一个「内含虚拟函数的类别」,编译器都会为它做出一个虚拟函数表,表中的每一笔元素都指向一个虚拟函数的地址。此外,编译器当然也会为类别加上一项

    成员变量,是一个指向该虚拟函数表的指针(常被称为vptr)。举个例:class Class1 {

                   public :               data1;               data2;               memfunc();               virtual vfunc1();               virtual vfunc2();               virtual vfunc3();

    };

    Class1对象实体在内存中占据这样的空间:

77

page142image8720page142image9144page142image9304page142image9464page142image9888
page143image680page143image840page143image1000page143image1160page143image1320

Class1对象实体

vtable

page143image2320

(*vfunc1)()

(*vfunc2)()

(*vfunc3)()

vptr

Class1::vfunc1()Class1::vfunc2()Class1::vfunc3()

Class1::memfunc()

page143image6232page143image6656page143image7080page143image7240
class Class1{
  public :     m_data1;
page143image8472page143image8896

78

}

m_data2;memfunc();virtual vfunc1();virtual vfunc2();virtual vfunc3();

m_data1

page143image11808

m_data2

page143image12416page143image12576page143image12736page143image12896

C++ 类别的成员函数,你可以想象就是C语言中的函数。它只是被编译器改过名称,并增加一个参数(this指针),因而可以处理调用者(C++对象)中的成员变量。所以,你并没有在Class1对象的内存区块中看到任何与成员函数有关的任何东西。

page143image13992page143image14152page143image14312page143image14472page143image14632page143image14792page143image15384

每一个由此类别衍生出来的对象,都有这么一个vptr。当我们透过这个对象调用虚拟函,事实上是透过vptr找到虚拟函数表,再找出虚拟函数的真正地址。

奥妙在于这个虚拟函数表以及这种间接调用方式。虚拟函数表的内容是依据类别中的虚拟函数声明次序,一一填入函数指针。衍生类别会继承基础类别的虚拟函数表(以及所有其它可以继承的成员),当我们在衍生类别中改写虚拟函数时,虚拟函数表就受了影响:表中元素所指的函数地址将不再是基础类别的函数地址,而是衍生类别的函数地址。看看这个例子:
    class Class2 : public Class1 {      public :
        data3;        memfunc();        virtual vfunc2();

};

page143image18488page143image18648page143image18808page143image18968
page144image616page144image1040page144image1464page144image1888page144image2312page144image2472

class Class2 : public Class1{

public :
m_data3;memfunc();
virtualvfunc2();

}

vtable

page144image4856

(*vfunc1)()

(*vfunc2)()

(*vfunc3)()

vptr

Class1::vfunc1()

Class2::vfunc2()

Class1::vfunc3()Class2::memfunc()

page144image8880page144image9304page144image9888

m_data1

page144image10496page144image10920

m_data2

page144image11528page144image11952

m_data3

Class2对象实体
于是,一个「指向Class1所生对象」的指针,所调用的vfunc2就是Class1::vfunc2,而

一个「指向Class2所生对象」的指针,所调用的vfunc2就是Class2::vfunc2

动态绑定机制,在执行时期,根据虚拟函数表,做出了正确的选择。

我们解开了第二道神秘。

口说无凭,何不看点实际。观其地址,物焉C哉,下面是一个测试程序:

  1. #0001  #include <iostream.h>

  2. #0002  #include <stdio.h>

#0003
#0004 class ClassA

  1. #0005  {

  2. #0006  public:

  3. #0007  int m_data1;

  4. #0008  int m_data2;

  5. #0009  void func1()

  6. #0010  void func2()

  7. #0011  virtual void

  8. #0012  virtual void

  9. #0013  };

#0014

  1. #0015  class ClassB

  2. #0016  {

  3. #0017  public:

  4. #0018  int m_data3;

  5. #0019  void func2()

  6. #0020  virtual void

  7. #0021  };

#0022

  1. #0023  class ClassC

  2. #0024  {

{ }
{ }
vfunc1() { }vfunc2() { }

: public

{ }vfunc1()

: public

ClassA

{ }

ClassB

79

page144image25600page144image25760page144image25920page144image26344page144image26768
page145image456page145image616page145image776page145image936page145image1096

80

  1. #0025  public:

  2. #0026  int m_data1;

  3. #0027  int m_data4;

  4. #0028  void func2() { }

  5. #0029  virtual void vfunc1() { }

  6. #0030  };

#0031

  1. #0032  void main()

  2. #0033  {

  3. #0034  cout << sizeof(ClassA) << endl;

  4. #0035  cout << sizeof(ClassB) << endl;

  5. #0036  cout << sizeof(ClassC) << endl;

#0037

  1. #0038  ClassA a;

  2. #0039  ClassB b;

  3. #0040  ClassC c;

#0041

  1. #0042  b.m_data1

  2. #0043  b.m_data2

  3. #0044  b.m_data3

  4. #0045  c.m_data1

  5. #0046  c.m_data2

  6. #0047  c.m_data3

  7. #0048  c.m_data4

  8. #0049  c.ClassA::m_data1 = 111;

#0050

  1. #0051  cout << b.m_data1 << endl;

  2. #0052  cout << b.m_data2 << endl;

  3. #0053  cout << b.m_data3 << endl;

  4. #0054  cout << c.m_data1 << endl;

  5. #0055  cout << c.m_data2 << endl;

  6. #0056  cout << c.m_data3 << endl;

  7. #0057  cout << c.m_data4 << endl;

  8. #0058  cout << c.ClassA::m_data1 << endl;

#0059

  1. #0060  cout << &b << endl;

  2. #0061  cout << &(b.m_data1) << endl;

  3. #0062  cout << &(b.m_data2) << endl;

  4. #0063  cout << &(b.m_data3) << endl;

  5. #0064  cout << &c << endl;

  6. #0065  cout << &(c.m_data1) << endl;

  7. #0066  cout << &(c.m_data2) << endl;

  8. #0067  cout << &(c.m_data3) << endl;

  9. #0068  cout << &(c.m_data4) << endl;

  10. #0069  cout << &(c.ClassA::m_data1) << endl;

  11. #0070  }

=1;=2;=3;= 11;= 22;= 33;= 44;

page145image20744page145image20904page145image21064page145image21224
page146image488page146image912page146image1336page146image1760page146image2184

执行结果与分析如下:

执行結果 意义

12 Sizeof (ClassA)16 Sizeof (ClassB)24 Sizeof (ClassC)

  1. 1  b.m_data1的內容

  2. 2  b.m_data2的內容

  3. 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

page146image11928

81

page146image12536page146image12960page146image13384page146image13808
page147image696page147image856page147image1016page147image1176page147image1336

abc对象的內容图示如下:
a (ClassA 的对象)

page147image2616

m_data1

m_data2

page147image4232

(*vfunc1)()

(*vfunc2)()

page147image6232page147image6656

ClassA::func1()

ClassA::func2()

page147image9072

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

page147image16832

(*vfunc1)()

(*vfunc2)()

page147image18832page147image19256page147image20272

ClassA::m_data1

m_data2

m_data3

m_data1

m_data4

page147image24072

(*vfunc1)()

(*vfunc2)()

page147image26072page147image26496

Object slicing 与 虚 拟 函 数我要在这里说明虚拟函数另一个极重要的行为模式。假设有三个类别,阶层关系如下:

virtual void Serialize();

void func();
virtual void Serialize();

virtual void Serialize();

page147image29104

CObject

CObject

page147image30424page147image30584

CDocument

CDocument

page147image31904page147image32064

82

CMyDoc

CMyDoc

page147image33832page147image33992page147image34152page147image34312
page148image472page148image896page148image1320page148image1744
  1. #0005  public:

  2. #0006  virtual void Serialize() { cout << "CObject::Serialize() \n\n"; }

  3. #0007  };

#0008

  1. #0009  class CDocument : public CObject

  2. #0010  {

  3. #0011  public:

  4. #0012  int m_data1;

  5. #0013  void func() { cout << "CDocument::func()" << endl;

  6. #0014  Serialize();

  7. #0015  }

#0016

  1. #0017  virtual void Serialize() { cout << "CDocument::Serialize() \n\n"; }

  2. #0018  };

#0019

  1. #0020  class CMyDoc : public CDocument

  2. #0021  {

  3. #0022  public:

  4. #0023  int m_data2;

  5. #0024  virtual void Serialize() { cout << "CMyDoc::Serialize() \n\n"; }

  6. #0025  };

  7. #0026  //---------------------------------------------------------------

  8. #0027  void main()

  9. #0028  {

  10. #0029  CMyDoc mydoc;

  11. #0030  CMyDoc* pmydoc = new CMyDoc;

#0031

  1. #0032  cout << "#1 testing" << endl;

  2. #0033  mydoc.func();

#0034

  1. #0035  cout << "#2 testing" << endl;

  2. #0036  ((CDocument*)(&mydoc))->func();

#0037

  1. #0038  cout << "#3 testing" << endl;

  2. #0039  pmydoc->func();

#0040

  1. #0041  cout << "#4 testing" << endl;

  2. #0042  ((CDocument)mydoc).func();

  3. #0043  }

第2章 C++的重要性質

page148image16872

以程序表现如下:

#0001 #include <iostream.h>#0002

  1. #0003  class CObject

  2. #0004  {

83

page148image19040page148image19464page148image19624page148image19784page148image20208
page149image528page149image688page149image848page149image1008

第一篇 勿在浮砂築高台 


原创粉丝点击