C++面向对象程序设计 多态
多态
多态性是不同的对象调用相同名称的函数,并可导致完全不同的行为的现象。“同一接口,多种方法”。
多态包括编译时多态(通过函数重载或运算符重载实现)和运行时多态(通过类的继承关系和虚函数实现)。
在基类中用关键字virtual修饰的成员函数称为虚成员函数,可以从基类继承。
如果虚函数在类声明之外定义,关键字virtual仅在函数声明时需要,不需再函数定义中使用该关键字。顶层函数不能为虚函数。
多态条件:
-必须存在一个继承体系结构。
-继承体系结构中的一些类必须具有同名的虚函数成员(当声明了基类的一个成员函数为虚函数后,即使该成员函数没有在派生类中被显式地声明为虚函数,但它在所有派生类中也将自动成为虚函数)。
-至少有一个基类类型的指针或基类类型的引用。这个指针或引用可用来对虚成员函数进行调用。(基类类型的指针可以指向任何基类对象或派生类对象)
C++使用vtable(虚成员函数表)来实现虚成员函数的运行期绑定。
构造函数不能使虚成员函数,析构函数可以使虚成员函数。
只有非静态成员函数(对象成员函数)才可以是虚成员函数。
重载、覆盖、遮蔽
重载
在同一个命名空间中,两个函数同名但参数个数或类型不同,即函数名相同,函数签名不同。这两个函数既可以是虚函数也可以是一般函数。编译期绑定(重载函数编译期绑定,虚函数运行期绑定)。
覆盖
函数名相同,函数签名也相同,且运行期绑定。
基类B中有一个虚成员函数m,派生类D也有一个具有相同函数签名的成员函数m,称D::m覆盖B::m。
如果成员函数不是虚函数,那么任何调用均为编译期绑定(遮蔽)。
遮蔽(隐藏)
函数名相同,函数签名可相同可不相同,且编译期绑定。
基类B中有一个非虚成员函数m,派生类D也有一个具有相同函数名的成员函数m,称D::m遮蔽B::m。
函数和非虚函数一样都可能产生名字遮蔽,实际情况是一旦派生类的虚函数不能覆盖基类的虚函数,就会产生虚函数的遮蔽。
(1)函数重载发生在同一个类或顶层函数中,同名的函数具有不同的参数列表
(2)函数覆盖发生在继承层次中,该函数在父类中必须是virtual,而子类的该函数必须与父类具有相同的参数列表
(3)函数遮蔽(隐藏)发生在继承层次中,父类和子类同名的函数中,不属于函数覆盖的都属于函数遮蔽
覆盖(重写)的效果:
1.在子类中重写了父类的虚函数,那么子类对象调用该重写函数,调用到的是子类内部重写的虚函数,而并不是从父类继承下来的虚函数;(这其实就是动态多态的实现);
2.在子类中重写了父类的虚函数,如果用一个父类的指针(或引用)指向(或引用)子类对象,那么这个父类的指针或用引用调用该重写的虚函数,调用的是子类的虚函数;相反,如果用一个父类的指针(或引用)指向(或引用)父类的对象,那么这个父类的指针或用引用调用该重写的虚函数,调用的是父类的虚函数
隐藏(重定义)的效果:
无论在子类的内部或者外部(通过子类成员)访问该成员;全都是访问子类的同名成员; 如果在子类内部或者外部(通过子类成员)访问同名的成员函数,则调用子类的同名成员;否则调用失败;
名字共享
需要共享函数名的几种情况:
重载函数名的顶层函数;
重载构造函数;
非构造函数但是同一个类中名字相同的成员函数;
继承层次中的同名函数(特别是虚函数,多态)。
小结
抽象基类
使用抽象基类的原因:有些应用中派生类必须覆盖父类的虚函数;需指定派生类的公共接口(公共接口:一个成员函数的集合,任何支持该接口的类必须定义该集合中的所有函数);无法定义基类中虚函数的具体操作,取决于不同的派生类。
如果一个类至少有一个纯虚函数,那么这个类被称为抽象类。抽象基类不能被实例化,而且必须至少有一个纯虚成员函数,通常只有public成员函数。
由于纯虚函数无函数体,所以在派生类中没有重新定义纯虚函数之前是不能调用这种函数的。将函数名赋值为0的含义是将指向函数体的指针赋初值0.
虽然不能声明抽象类的实例也不能用抽象类作为参数类型、函数返回类型或显式转换类型,但可以声明抽象类的指针或引用,当用这种基类指针指向其派生类的对象时必须在派生类中重载纯虚函数。派生类必须重新定义其父类的每一个纯虚函数或者把这些函数继续声明为纯虚函数。
运行期类型识别
运行期类型识别提供以下功能:
在运行期对类型转换操作进行检查;
在运行期确定对象的类型;
扩展C++提供的RTTI。
使用dynamic_cast操作符可用来在运行期对可疑的转型操作进行测试。
通常来说,向上转型可以成功,向下转型不能成功。除了void *之外,无关类型之间的dynamin_cast也不会成功。
typeid操作符
操作符typeid用来确定某个表达式的类型,要使用这个操作符必须包含头文件typeinfo。