virtual
的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。通过基类的引用(或指针)调用虚函数时,发生动态绑定
。引用(或指针)既可以指向基类对象也可以指向派生类对象
,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。virtual
的目的是启用动态绑定。成员默认为非虚函数
,对非虚函数的调用在编译时确定。除了构造函数之外,任意非static成员函数都可以是虚函数
。虚函数的声明必须与基类中的定义方式完全匹配
,
但有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。
虚函数的成员函数才能进行动态绑定
基类类型的引用或指针进行函数调用
编译器都将它当作基类类型对象
。静态类型
(在编译时可知的引用类型或指针类型)和动态类型
(指针或引用所绑定的对象的类型这是仅在运行时可知的)可能不同。覆盖虚函数机制
虚函数与默认实参
编译时确定
。类型定义,与对象的动态类型无关
。通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值
。
为什么会希望覆盖虚函数机制?
最常见的理由是为了派生类虚函数调用基类中的版本。在这种情况下,基类版本可以完成继承层次中所有类型的公共任务,而每个派生类型只添加自己的特殊工作。
例如,可以定义一个具有虚操作的Camera类层次。Camera类中的display函数可以显示所有的公共信息,派生类(如PerspectiveCamera)可能既需要显示公共信息又需要显示自己的独特信息。可以显式调用Camera版本以显示公共信息,而不是在PerspectiveCamera的display实现中复制Camera的操作。 在这种情况下,已经确切知道调用哪个实例,因此,不需要通过虚函数机制。
基类指针
,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为。
要保证运行适当的析构函数,基类中的析构函数必须为虚函数
。[Code2]无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。
在复制控制成员中,只有析构函数应定义为虚函数,构造函数不能定义为虚函数。
构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。派生类中的赋值操作符有一个与类本身类型相同的形参
,该类型必须不同于继承层次中任意其他类的赋值操作符的形参类型。尽量不要在构造函数和析构函数中调用虚函数
,因为构造或析构期间的对象类型对虚函数的绑定有影响。
基类构造函数或析构函数中
,将派生类对象当作基类类型对象
对待。
构造函数或析构函数自身类型定义的版本
。假定找到了名字,编译器就检查实参是否与形参匹配。
虚函数必须在基类和派生类中拥有同一原型
。如果基类成员与派生类成员接受的实参不同,就没有办法通过基类类型的引用或指针调用派生类函数。[Code3]名字查找与继承
一个或多个纯虚函数
的类是抽象基类。除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象
。在函数形参表后面写上
= 0
,double net_price(std::size_t)
const = 0;
共享的基类子对象
。共享的基类子对象称为虚基类。[Code5]引起更少的二义性问题
Item_base *baseP = &derived; //calls version from the base class regardless of the dynamic type of baseP double d = baseP->Item_base::net_price(42);
/*如果析构函数为虚函数,那么通过指针调用时,运行哪个析构函数将因指针所指对象类型的不同而不同:*/ Item_base *itemP = new Item_base; // same static and dynamic type delete itemP; // ok: destructor for Item_base called itemP = new Bulk_item; // ok: static and dynamic types differ delete itemP; // ok: destructor for Bulk_item called
class Base { public: virtual int fcn(); }; class D1 : public Base { public: // hides fcn in the base; this fcn is not virtual int fcn(int); // parameter list differs from fcn in Base // D1 inherits definition of Base::fcn() }; class D2 : public D1 { public: int fcn(int); // nonvirtual function hides D1::fcn(int) int fcn(); // redefines virtual fcn from Base }; /*D1 中的 fcn 版本没有重定义 Base 的虚函数 fcn,相反,它屏蔽了基类的 fcn。结果 D1 有两个名 为 fcn 的函数:类从 Base 继承了一个名为 fcn 的虚 函数,类又定义了自己的名为 fcn 的非虚成员函 数,该函数接受一个 int 形参。 但是,从 Base 继承的虚函数不能通过 D1 对象(或 D1 的引用或指针) 调用, 因为该函数被 fcn(int) 的定义屏蔽了。 类 D2 重定义了它继承的两个函数,它重定义了 Base 中定义的 fcn 的原始版本并重定义了 D1 中定义 的非虚版本。*/
Base bobj; D1 d1obj; D2 d2obj; Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj; bp1->fcn(); bp2->fcn(); bp3->fcn(); // ok: virtual call, will call Base::fcnat run time // ok: virtual call, will call Base::fcnat run time // ok: virtual call, will call D2::fcnat run time 三个指针都是基类类型的指针,因此通过在 Base 中查找 fcn 来确定这三 个调用,所以这些调用是合法 的。另外,因为 fcn 是虚函数,所以编译器会生成代码,在运行时基于引用指针所绑定的对象的实际类型进 行调用。在 bp2 的情况,基本对象是 D1 类的,D1 类没有重定义不接受实参的虚函数版本,通过 bp2 的 函数调用(在运行时)调用 Base 中定义的版本。
/*每个 IO 库类都继承了一个共同的抽象基类,那个抽象基类管理流的条件状态并保存流所读写的缓冲区。 istream 和 ostream 类直接继承这个公共基类,库定义了另一个名为 iostream 的类,它同时继承 istream 和 ostream,iostream 类既可以对流进行读又可以对流进行写。 我们知道,多重继承的类从它的每个父类继承状态和动作,如果 IO 类 型使用常规继承,则每个 iostream 对象可能包含两个 ios 子对象:一个包含 在它的 istream 子对象中,另一个包含在它的 ostream 子对 象中,从设计角度讲,这个实现正是错误的:iostream 类想要对单个缓冲区进行读和写,它希望跨越输入和输 出操作符共享条件状态。如果有两个单独的 ios 对象,这种共享是不可能的。 在 C++ 中,通过使用虚继承解决这类问题。*/ class istream : public virtual ios { ... }; class ostream : virtual public ios { ... }; // iostream inherits only one copy of its ios base class class iostream: public istream, public ostream { ... };
C++笔记:面向对象编程(Visual),布布扣,bubuko.com
原文:http://blog.csdn.net/liufei_learning/article/details/22708639