




已阅读5页,还剩73页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第8章 虚函数与多态性,主要内容,8.1 向上类型转换,替代原则在C+中通过向上类型转换实现,可以将一个公有派生类的对象或地址作为基类对象或地址来处理。 派生类向上转换为基类类型,这在逻辑上是合理的,在物理上也是安全的。 在逻辑上方面,派生类继承基类的公共接口,能够发送给基类对象的消息也能够发送给派生类对象。派生类是特殊的基类类型,派生类对象(或地址)可以作为基类的实例(或地址),替代基类对象(或地址)使用。 在物理方面,基类的成员被派生类继承,在派生类对象中封装着一个无名的基类子对象,派生类对象的存储空间中从首地址开始存放的的这个基类子对象。因此,进行向上类型转换时,通过派生类对象切片,能够提供足够的基类信息,得到基类对象。而使用基类指针(或引用)指向派生类对象时也不会破坏指针(或引用)的指向规则。,面向基类编写程序,使用继承和替代原则对于改善代码的结构意义非凡。相对于各种特殊的派生类而言,基类更加抽象,更具一般性。相对于继承层次中比较稳定的上层类,低层派生类更容易发生变更,比如增加新派生类。面向基类在更高的抽象层次上编写程序,程序代码就不会依赖于特殊类型,更加稳定、健壮,具有更好的可扩展性。,一组employee类/通用payroll()函数,class employee void salary() ; class manager : public employee public: void salary()/*经理工资的计算和发放*/ ; class programmer : public employee public: void salary()/*程序员工资的计算和发放*/ ; class parttime : public employee public: void salary()/*兼职人员工资的计算和发放*/ ; void payroll(employee ,代码中类层次如下图所示,payroll()函数具有很强的适应性,程序中的payroll()是面向基类进行处理的函数,这使得payroll()函数具有很强的适应性,它能够处理任意特定类型的employee对象,包括manager、programmer及parttime。隐含的另一个优点是payroll()码的可扩展性:如果需要对类层次进行修改,比如添加新员工类型tester(测试人员),那么只要将tester作为employee的另一个派生类即可,不用修改payroll()函数,它对新增类型仍然适用。 programmer Ron; manager Harry; parttime Lily; payroll(Harry); /OK, manager转换为employee payroll(Ron);/OK, programmer转换为employee payroll(Lily);/OK, parttime转换为employee tester Albus; payroll(Albus);/OK, tester转换为employee,问题,为了避免对象切片现象,这里没有使用对象的向上类型转换。但是,即使引用或指针的向上类型转换仍然会损失源类型的信息。上面的各种员工实例经过转换会丢失自身的类型信息,编译器只知道re是employee类型的,并不知道re实际引用对象的真正类型。因而payroll函数中通过re调用的总是employee版本的salary(),而无法调用到各个派生类中重新定义的salary()操作。这样的替代显然不满足我们所设想的目标。,8.2 虚函数,向上类型转换会损失类型信息,不能真正实现替代原则。这个问题与C+默认的函数调用绑定方式有关。,8.2.1 函数调用绑定,把函数体和函数调用相联系称为绑定(binding,也译作捆绑或编联)。C+中,默认的函数调用绑定方式是早绑定。早绑定又称为静态绑定,即在程序运行之前,由编译器和连接器实现。 void payroll(employee 在payroll()函数中,对salary()的调用实施早绑定。编译器只知道re是employee类型的引用,所以将re.salary()的调用和employee类的salary()函数体联系在一起。这段程序在执行时就不可能调用到其他类的salary()函数代码。,晚绑定,解决这个问题的方法是使用晚绑定,将绑定推迟到程序运行时。在程序运行时,可以获知实际接收消息的对象的类型,根据这时的类型信息绑定函数调用。晚绑定又称为动态绑定或运行时绑定,必须有某种机制来确定运行时对象的类型并调用合适的成员函数。 /如果使用晚绑定,运行下面的代码之前re.salary()没有和任何函数体联系 manager Harry; programmer Ron; /真正执行re.salary()时进行动态函数绑定 /re指向Harry, 将re.salary()动态绑定manager:salary() payroll(Harry); /re指向Ron, 将re.salary()动态绑定programmer:salary payroll(Ron); 可以看到,利用动态绑定能够实现多态性同样的消息发送给不同对象时执行不同的操作。在C+中,只对虚函数实施晚绑定。,8.2.2 虚函数,为了对特定成员函数进行晚绑定,C+要求在基类中将该成员函数声明为虚函数。虚函数的声明语法是在成员函数前加virtual关键字。晚绑定只对虚函数起作用,并且只有在使用含有虚函数的基类地址(指针或引用)时发生。 虚函数在基类中用virtual关键字声明。在所有派生类中,即使不再重复声明,它也是虚函数。派生类中可以重定义基类的虚函数,这称为覆盖或改写(override)。,上面的代码可以修改为如下形式,class employee public: virtual void salary() /基类中声明的虚函数 ; /下面的派生类中重写salary()都是虚函数 class manager : public employee public: void salary() /*经理工资的计算和发放*/ / ;,上面的代码可以修改为如下形式,class programmer : public employee public: void salary() /*程序员工资的计算和发放*/ ; class parttime : public employee public: void salary() /*兼职人员工资的计算和发放*/ ;,上面的代码可以修改为如下形式,/payroll函数 void payroll(employee /根据实际类型调用parttime:salary() ,多态性,可以看到,将基类中的salary()声明为虚函数,程序的运行效果发生了很大改变。payroll()函数中对salary()的调用使用的是晚绑定机制,根据re实际指向的对象类型来实施函数调用。同样是通过基类引用发送相同的salary()消息,却产生了不同的效果,即多态性。多态性可以为程序设计带来更大的灵活性和可扩展性。 例如,有如下一组图形类,它们都实现了计算面积的操作area(),如果想要计算一组图形的面积之和,使用虚函数的多态性调用就很方便,图形类计算面积,class shape public: virtual double area() const return 0; / ; class rectangle: public shape public: double area() const return height * width; private: double height, width; ; class circle: public shape public: double area() const return PI* radius * radius; private: double radius; ;,图形类计算面积,/计算一组图形的面积之和 shape* pN; for( int i =0; i area(); /area()会根据pi实际指向的对象类型多态调用 在这个例子中,我们可以用统一的方式来操纵一组图形对象,而不考虑它们具体是哪种类型。而且,如果要在其中增加一个新的图形类,例如椭圆形,只要这个新类型是从shape派生的,计算面积和的代码就不需要任何修改也能满足新的需求。这为我们扩展系统提供了很大的方便,从而使增量式开发成为可能。,8.2.3 虚函数的相关规则,只有非静态成员函数可以声明为虚函数。静态成员函数没有this指针,其调用实际上只与类相关。而虚函数调用的绑定是根据对象类型确定的。 在基类中声明的虚函数,在派生类中仍然是虚函数。派生类可以根据自己的需要覆盖基类中的虚函数实现。如果没有覆盖,那么派生类将继承基类的虚函数。 为了保持虚函数的特性,派生类中覆盖基类的虚函数时要用相同的参数表和返回类型,否则: 若派生类中重定义的虚函数参数表与基类中不同,则被视为是定义了另一个同名函数,在派生类中将会隐藏基类的虚函数,因而不能再进行多态调用。 若派生类中重定义基类的虚函数时保持函数名和参数表相同,但是返回类型不相同,则编译器报告“返回类型不一致”错误。,例子,#include #include using namespace std; class Base public: virtual int f() const cout “Base:f()n“; return 1; virtual void f(string) const virtual void g() const ;,例子,class Derived1 : public Base public: /覆盖虚函数g(),继承了虚函数f()和f(string) void g() const ; class Derived2 : public Base public: /覆盖虚函数f(),隐藏了f(string),继承了g() int f() const cout “Derived2:f()n“; return 2; ;,例子,class Derived3 : public Base public: / 编译错误:不能修改虚函数的返回类型: void f() const cout “Derived3:f()n“; ; class Derived4 : public Base public: / 改变虚函数的参数表,隐藏了f()和f(string);继承了g() int f(int) const cout “Derived4:f()n“; return 4; ;,例子,int main() string s(“hello“); Derived1 d1; int x = d1.f();/ Base:f() d1.f(s); / Base:f(string) Derived2 d2; x = d2.f(); /Derived2:f() d2.f(s); / 错误: f(string)被隐藏 Derived4 d4; x = d4.f(1); /Derived4:f(int) x = d4.f(); / 错误:f() 被隐藏 d4.f(s); /错误: f(string)被隐藏 Base / Base:f(string) ,虚函数和构造/析构函数,在构造函数和析构函数中调用虚函数时,被调用的只是这个虚函数的本地版本。也就是说,虚函数机制在构造函数和析构函数中不起作用。 构造函数不能是虚函数。析构函数可以是虚函数。基类的析构函数如果声明为虚函数,其派生类的析构函数也是虚函数,即使不加virtual。 析构函数最好声明为虚函数。,为什么析构函数最好声明为虚函数,#include using namespace std; class Base1 public: Base1() cout “Base1()n“; ; class Derived1 : public Base1 public: Derived1() cout “Derived1()n“; ; class Base2 public: virtual Base2() cout “Base2()n“; ; class Derived2 : public Base2 public: Derived2() cout “Derived2()n“; ;,为什么析构函数最好声明为虚函数,int main() Base1* bp = new Derived1; /向上类型转换 delete bp;/会调用哪些析构函数?正确吗? Base2* b2p = new Derived2; /向上类型转换 delete b2p;/会调用哪些析构函数?正确吗? 这段程序的运行结果: Base1() Derived2() Base2() 运行这段程序会发现, delete bp只调用基类的析构函数,没有调用派生类的析构函数。而delete b2p会根据指向的对象的类型调用析构函数,即调用Derived2的析构函数,随后引起Base2的析构函数调用,这正是我们期望的行为。,8.2.4 实现多态性的步骤,使用虚函数实现多态性的一般步骤是: 在基类中将需要多态调用的成员函数声明为virtual; 在派生类中覆盖基类的虚函数,实现各自需要的功能; 基类的指针或引用指向派生类对象,通过指针或引用调用虚函数。 这样,会根据基类的指针或引用实际指向的(派生类)对象来调用派生类中的虚函数版本。如果派生类没有覆盖基类的虚函数,则基类中的虚函数被调用。,8.2.4 实现多态性的步骤,需要注意的是,只有通过基类的指针或引用才能实现虚函数的多态调用,通过对象调用虚函数不会引起多态调用。C+ 为了减少程序运行时的开销,提高效率,编译时能确定的信息不会推迟到运行时处理。对虚函数的绑定是根据实施调用的对象类型确定的,如果使用指针或引用调用虚函数,那么要到运行时才知道它们指向的实际对象类型,因而会进行晚绑定。如果直接使用对象调用虚函数,对象的类型在编译时就知道了,不会进行晚绑定。,例子,#include using namespace std; class Base public: virtual void vfunc() cout“Base:vfunc()“endl; ; class Derived1 : public Base public: void vfunc() cout“Derived1:vfunc()“endl; ; class Derived2 : public Base public: void vfunc() cout“Derived2:vfunc()“endl; ;,例子,int main() Base b; Derived1 d1; Derived2 d2; b.vfunc(); /已知b是Base类型的对象,编译时绑定到Base:vfunc() d1.vfunc();/同上, 编译时绑定到 Derived1:vfunc() d2.vfunc();/同上, 编译绑定到Derived2:vfunc() b = d1; /对象向上类型转换,发生d1对象切片成为Base类型 b.vfunc(); /对象调用,没有多态性,仍然是编译时绑定到Base:vfunc(),例子,/基类指针pb可能指向基类和派生类对象 /所以pb调用的虚函数运行时才绑定 Base *pb = /运行时绑定到pb这时指向d2所属类型Derived2的vfunc() /引用的虚函数调用与指针情况相同 ,程序的运行结果如下,程序的运行结果如下: Base:vfunc() Derived1:vfunc() Derived2:vfunc() Base:vfunc() Base:vfunc() Derived1:vfunc() Derived2:vfunc(),注意事项,初学者使用基类指针或引用调用虚函数时的一个常见问题是忘记了指针或引用本身的类型。C+ 是一种强类型的语言,虽然对虚函数调用的解析是由指针(引用)指向的对象类型决定的,但是通过指针(引用)能够调用哪些操作却是由指针(引用)所属类型的接口决定的。也就是说,基类指针(引用)即使在指向派生类对象时,也只能调用基类接口中出现的成员函数,不能调用派生类中增加的成员函数,即使是虚函数。,注意事项,class Base public: virtual void f() ; class Derived : public Base public: void f() virtual void g() /派生类增加的成员函数 ; int main() Base b; b.f(); /!b.g(); 这个错误我们容易发现,Base中没有g() Derived d; d.f(); /OK d.g(); /OK Base* pb = /错误 ,8.2.5 动态绑定的实现,一个类中如果包含虚函数,会影响其对象的布局。例如,下面一段程序的运行结果是什么呢? #include using namespace std; class B1 int m; public: void f() ; class B2 int m; public: virtual void f() ; class D1: public B1 int n; public: void f() ;,8.2.5 动态绑定的实现,class D2: public B2 int n; public: void f() ; class B3 int n; public: virtual void g() virtual void h() ;,8.2.5 动态绑定的实现,int main() cout“sizeof(B1)=“sizeof(B1)endl; cout“sizeof(B2)=“sizeof(B2)endl; cout“sizeof(D1)=“sizeof(D1)endl; cout“sizeof(D2)=“sizeof(D2)endl; cout“sizeof(B3)=“sizeof(B3)endl; 程序的运行结果: sizeof(B1)=4 sizeof(B2)=8 sizeof(D1)=8 sizeof(D2)=12 sizeof(B3)=8,对象的布局,对象的这种布局与C+中晚绑定的实现机制相关。当编译器看到虚函数的声明时,会自动进行一系列的幕后工作,安装必要的晚绑定机制。 B1和B2两个类的唯一差别在于B2中的f()是虚函数,但B2类对象所占内存大小比B1多出了4个字节。这4个字节从何而来呢? 如果一个类中包含虚函数,编译器会为该类创建一个虚函数表VTABLE,表中保存该类的虚函数的地址。同时,编译器还在这个类中放置一个秘密的指针成员VPTR,VPTR指向该类的VTABLE。这就是B2比B1多出来的4个字节。,对象的布局,VTABLE,每当创建一个含有虚函数的类,或从含有虚函数的类派生一个类时,编译器会为这个类创建一个唯一的VTABLE。在这个VTABLE中放置该类中所有虚函数的地址。如果在派生类中没有重新定义基类中的虚函数,编译器就使用基类虚函数的地址。然后编译器在这个类中放置VPTR。创建对象时,构造函数会初始化对象中VPTR,让它指向所属类的VTABLE的起始地址。,VTABLE例子,class shape public: virtual double area() const return 0; virtual void draw() / ; class rectangle: public shape public: double area() const return height * width; void draw() protected: double height, width; ;,VTABLE例子,class square: public rectangle public: void draw() ; class circle: public shape public: double area() const return PI* radius * radius; void draw() private: double radius; ; shape* sa = new circle, new rectangle, new rectangle, new square;,编译器创建的VTABLE和VPTR,实现动态绑定,当通过基类指针调用一个虚函数时,例如上面的 sa0,它指向circle 对象的起始地址。编译器从sa0指向的对象中取出VPTR,根据VPTR 找到相应的虚函数表 VTABLE。再根据函数在VTABLE 中的偏移,找到到适当的函数。 因此不是根据指针的类型shape*决定调用shape:area(),而是调用“ VPTR+偏移量 ”处的函数。因为获取VPTR 和确定实际的函数地址发生在运行时, 所以就实现了晚绑定。,派生类中的虚函数,派生类如果重定义了基类中的虚函数,就在派生类的VTABLE中保存新版本虚函数的地址,没有重定义的仍使用基类虚函数的地址。同一个虚函数在派生类和基类的VTABLE中处于相同的位置。如果派生类增加了新的虚函数,则编译器先将基类的虚函数准确地映射到派生类的VTABLE中,再加入新增加的虚函数地址。只存在于派生类中的虚函数不能通过基类指针调用。,回顾构造函数的行为,如果一个类包含虚函数,虽然我们在编写构造函数的代码时,并不会涉及任何虚指针的初始化操作,但编译器会自动插入相关的代码。编译器自动生成的缺省构造函数也不是真的什么都不做,初始化虚指针这些都是缺省构造函数要处理的。由此可以看到,构造函数中有很多隐藏起来的行为,特别是处于继承层次中和带有虚函数的类,因此,这些类的构造函数一般不作为inline函数。,8.3 抽象类,面向对象设计中经常需要对一组具体类的共性进行抽象,自下而上形成更一般的基类,描述这组类的公共接口。 在这种向上抽象的过程中,我们发现,越上层的基类其抽象程度越高,有时甚至难以对它们的某些操作给出具体描述。这些基类存在的目的已经不再是用来创建实例,而只是描述类层次中派生类的共同特性,为这些派生类提供一个公共接口。这样的类是“抽象”的,与之相对的概念是“具体类”。例如,汽车、火车、轮船都是具体类,它们都有实例存在,都可以“驾驶”;可以用“交通工具”类描述汽车、火车、轮船等类的共性,可以说“驾驶交通工具”,但是难以描述这个“驾驶”操作到底是怎样的,因而这个操作是抽象操作,而这个交通工具类是抽象类。,8.3 抽象类与纯虚函数,UML类图中抽象类和抽象操作的名字使用斜体表示。,抽象类两个有主要用途,支持一般性的通用概念,如图形、交通工具等,这些概念自己没有实例,只是使用它们的具体派生类实例。 为一组派生类提供公共接口,而接口的实现由各个派生类提供。我们如果知道飞机一种交通工具,就知道它可以驾驶,但如何驾驶则要“飞机”类自己实现了。,纯虚函数,C+中用纯虚函数定义类中的抽象操作,纯虚函数没有实现。包含至少一个纯虚函数的类称为抽象类。例如,可以将交通工具类中的“驾驶”操作定义为纯虚函数,而交通工具类就成为了抽象类。抽象类只能用作其他类的基类,因此被称为抽象基类。不能创建抽象类的实例,但可以创建其具体派生类的实例。 定义纯虚函数的语法如下: virtual 返回类型 函数名 (参数表) = 0;,Shape类,以下图的Shape 类层次为例:在现实中,根本不存在Shape类的实例,计算面积的area()和计算周长的perimeter()操作对一个不确定的图形来说也是无法实现的。图中的Shape被表示为抽象类,area()和perimeter()被表示为抽象操作。在C+ 中如下定义Shape类将其操作: class Shape public: virtual double area() = 0; /纯虚函数 virtual double perimeter() = 0; ;/Shape中包含纯虚函数,是抽象类,Shape类层次,使用抽象类要注意以下几个方面,(1)如果一个类中包含至少一个纯虚函数,这个类就是抽象类。如果一个抽象类中的所有成员函数都是纯虚函数,这个类称为纯抽象类。上面的Shape是一个抽象类,而且是一个纯抽象类。,使用抽象类要注意以下几个方面,(2)当继承一个抽象类时, 必须在派生类中实现 (覆盖) 所有的纯虚函数,否则派生类也被看作是一个抽象类。例如,Shape 类的派生类必须覆盖area()和perimeter() 操作,否则会被作为抽象类因为继承得到了未实现的纯虚函数。 class Rectangle: public Shape/具体派生类 public: /覆盖抽象基类的纯虚函数 double area() return width*height; double perimeter() return (width+height)*2; private: double width, height; ;,使用抽象类要注意以下几个方面,(3)不能创建抽象类的实例,但可以创建由抽象类派生的具体子类的实例,也可以定义象类的指针或引用,它们指向具体派生类的对象。事实上,我们正是通过抽象基类的指针或引用来实现虚函数的多态调用的。例如: int main() Shape s; /编译器报告错误 Rectangle r(4,5); Shape *ps = ,使用抽象类要注意以下几个方面,(4)抽象类也可以包含普通成员函数, 在普通成员函数中可以调用纯虚函数,因为纯虚函数被推迟到某个具体派生类中实现,由于虚函数的晚捆绑,在派生对象实施这个调用时会体现为具体操作对具体操作的调用。如: #include using namespace std; class abstract /抽象基类 public: virtual void pf()=0; /纯虚函数 void f() /普通成员函数 cout“In abstract:f()“endl; pf(); /由调用f()的对象类型确定调用哪个类的 pf()实现 ;,使用抽象类要注意以下几个方面,class concrete : public abstract public: /实现基类接口中的纯虚函数 void pf() cout“concrete:pf()“endl; ; int main() concrete c; c.f(); /OK 程序的运行结果为: In abstract:f() concrete:pf(),使用抽象类要注意以下几个方面,(5)抽象基类和具体派生类的关系是一般类和特殊类之间的关系,是一种继承和被继承的关系。在确定抽象类的接口时,应该确保接口中的操作是同类对象共同行为的抽取。否则,会为继承这个抽象类的整个类层次带来不利影响,抽象类中的任何改动都将关系到整个类层次的改变,所以抽象类中的信息应该尽可能简单,尽可能和研究对象的本质相关。,8.4 RTTI,RTTI(运行时刻类型识别)允许使用基类指针或引用来操纵对象的程序能获得这些指针或引用实际所指对象的类型。在C+ 中,为了支持RTTI提供了两个运算符: dynamic_cast运算符 typeid运算符 对于要获得的派生类类型信息,dynamic_cast和typeid运算符的操作数的类型必须是带有一个或多个虚函数的类类型。也就是说,对于有虚函数的类而言,RTTI是运行时刻的操作,而对于其他类而言,它只是编译时刻的事件。,8.4.1 dynamic_cast与向下类型转换,既然存在向上类型转换,那么是不是也可以进行向下的类型转换呢?向上类型转换总是安全的,但向下类型转换的安全性比较难以保证。例如: class Base.; class Derived: public Base.; / Base b, *pb; Derived d, *pd; pb = /同样的向下类型转换,安全吗?,可以看到,同样的基类指针到派生类指针的类型转换,其安全性不同。当基类指针指向派生类对象时,向下类型转换是安全的;如果基类指针指向基类对象或者不同派生类的对象,那么这种向下类型转换就是危险的。指针指向的对象到底是什么类型只有在程序运行期间可以获知,因而需要运行时刻的类型信息才能判断是否可以安全地转换,并真正实施转换。,向下类型转换是危险的,显式类型转换dynamic_cast可以用来把一个类类型对象的指针转换成同一类层次结构中的其他类的指针,同时也可以把一个类类型对象的左值转换为同一类层次结构中其他类的引用。和其他显式转换不同的是,dynamic_cast是在运行时刻执行的。如果指针或左值操作数不能被安全地转换为目标类型,则dynamic_cast将失败。如果是对指针类型的dynamic_cast失败,则dynamic_cast的结果是空指针,即0。如果针对引用类型的dynamic_cast失败,则dynamic_cast会抛出一个bad_cast类型的异常。,dynamic_cast,/一组表示公司不同雇员的类,其中包含一些计算工资的操作 class employee public: virtual int salary(); ; class manager : public employee public: int salary(); ; class programmer : public employee public: int salary(); ; void company:payroll( employee *pe ) /公司发放工资的操作 pe - salary(); ,dynamic_cast的应用示例,利用类层次和虚函数salary()提供的多态性,payroll()能够实现对不同类型雇员的工资发放。 假设公司在年终时根据工作业绩,决定向所有的程序员发奖金,并且用一个bonus()操作计算和发放每个程序员的奖金。显然,bonus()作为programmer的成员函数比较合适。可引起的问题是,如何在payroll中调用bonus()呢?,问题,一种解决方案是将bonus()作为基类employee的虚函数,并提供一个默认实现,然后在programmer中根据奖金的发放办法重写bonus(),其他类只要继承基类中的实现即可。这样在payroll()就可以调用bonus()了。 这个方案看似简单直接,实际上存在几个问题:第一,修改员工类层次中的基类employee会影响整个类层次,这在面向对象设计中是应该尽量避免的。第二,向基类增加的虚函数实际上只对programmer的,将它放在基类接口中会被很多不需要它的派生类继承。第三,对基类的修改会引起整个类层次代码的重新编译。如果因为某些原因不能得到类层次的源代码,比如这是公司购买的系统,那么这个方案将是不可行的。,一种解决方案,另一个方案是不增加虚函数,而将bonus()操作放在需要它的programmer类中。即使没有programmer的源代码,我们也可以在programmer类定义的头文件中增加bonus()成员函数,然后增加一个源文件来定义这个成员函数: /programmer的头文件 class programmer : public employee public: int salary(); /增加操作 int bonus(); ; /用一个源文件实现programmer:bonus()操作 /#include programmer类的头文件 int programmer:bonus(),另一种解决方案,但是,这引起一个新问题:payroll()需要一个基类指针作参数的,通过这个指针只能调用基类接口中的操作,不能直接调用programmer类中增加bonus()操作,即使指针实际上是指向一个programmer对象。 这时,可以使用dynamic_cast进行向下类型转换,将基类指针转换为派生类的指针,并用这个指针调用成员函数bonus()。,一个新问题 dynamic_cast,void company:payroll( employee *pe ) programmer *pm = dynamic_cast( pe ); /如果pe指向的是一个programmer类型的对象 /则转换成功 /pm指向programmer对象的起始地址 if (pm) /用pm调用programmer的成员函数bonus发奖金 pm - bonus(); /如果pe指向的不是programmer类型的对象 /则转换失败,pm的值为0 else /使用employee的成员,正常发放员工的工资 pe - salary(); ,dynamic_cast进
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 校园电力安全知识培训
- 校园消防知识培训方案课件
- 防洪考试题及答案
- 立业理论考试题及答案
- 销售核算面试题及答案
- 车体安全测试题及答案
- 乡村全科考试试题及答案
- 申论助教面试题及答案
- 2025年福建省泉州技师学院招聘合同教师考试笔试试题(含答案)
- 物业保安培训考试试题及答案
- 选择测试题大全及答案
- 陕西西安工业投资集团有限公司招聘笔试题库2025
- 头疗会所员工合同范本
- 废旧船买卖合同协议书
- 2025年4月自考04184线性代数(经管类)试题及答案含评分标准
- 2024年全国工会财务知识大赛备赛试题库500(含答案)
- 公共管理监督体系构建
- 私人代客炒股协议合同
- 医疗服务质量评价体系-全面剖析
- 传统出版业数字化转型的策略与实践
- 2025年安徽合肥东部新城建设投资有限公司招聘笔试参考题库含答案解析
评论
0/150
提交评论