二义性
- 在多继承时,基类与派生类之间,或基类之间出现同名成员时,将出现访问时的二义性(不确定性)——采用虚函数(参见第8章)或同名隐藏规则来解决。
- 当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性——采用虚基类来解决。
解决方法一:用类名来限定 c1.A::f() 或 c1.B::f()
解决方法二:同名隐藏
在C 中声明一个同名成员函数f(),f()再根据需要调用 A::f() 或 B::f()
菱形继承
class B {
public:
int b;
}
class B1: public B {
private:
int b1;
};
class B2: public B {
private:
int b2;
};
class C : public B1,public B2 {
public:
int f();
private:
int d;
}
有二义性
C c ;
c.b;
c.B::b
无二义性
c.B1::b
c.B2::b
虚基类
ios 抽象类 == 虚基类
is os
iostream
虚基类的引入
用于有共同基类的场合
- 声明
以virtual修饰说明基类 例:class B1:virtual public B - 作用
主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题.
为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝 - 注意:
在第一级继承时就要将共同基类设计为虚基类。
class B { public: int b; };
class B1: virtual public B { public: int b1; };
class B2: virtual public B { public: int b2; };
class C: public B1, public B2 { public: float d; };
下面的访问是正确的:
C cobj;
cobj.b;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uj9GvIaK-1621340906897)(C:\Users\hjkl\AppData\Roaming\Typora\typora-user-images\image-20210512153035080.png)]
UML 的画图
统一建模语言(与编程语言无关)
虚函数及其派生类的构造函数
- 建立对象时所指定的类称为最(远)派生类。
- 虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。
- 在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用。如果未列出,则表示调用该虚基类的默认构造函数。
- 在建立对象时,只有最派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略。
组合与继承
-
组合与继承:通过已有类来构造新类的两种基本方式
*** 组合:B类中存在一个A类型的内嵌对象*
(只是一部分,没有紧密的关联)有一个(has-a)关系:表明每个B类型对象“有一个” A类型对象
A类型对象与B类型对象是部分与整体关系
B类型的接口不会直接作为A类型的接口
公有继承:A类是B类的公有基类
是一个(is-a)关系:表明每个B类型对象“是一个” A类型对象
A类型对象与B类型对象是一般与特殊关系
回顾类的兼容性原则:在需要基类对象的任何地方,都可以使用公有派生类的对象来替代
B类型对象包括A类型的全部接口
派生类对象的内存布局
派生类对象的内存布局
因编译器而异
内存布局应使类型兼容规则便于实现
一个基类指针,无论其指向基类对象,还是派生类对象,通过它来访问一个基类中定义的数据成员,都可以用相同的步骤
不同情况下的内存布局
单继承:基类数据在前,派生类新增数据在后
多继承:各基类数据按顺序在前,派生类新增数据在后
虚继承:需要增加指针,间接访虚基类数据
指针在对象转换中的应用
class Base0 { … };
class Base1: virtual public Base0 { … };
class Base2: virtual public Base0 { … };
class Derived: public Base1, public Base2 { … };
Derived *pd = new Derived();
Base1 *pb1 = pd;
Base2 *pb2 = pd;
Base0 *pb0 = pb1;
基类向派生的转换
基类向派生类的转换
基类指针可以转换为派生类指针
基类引用可以转换为派生类引用
需要用static_cast显式转换
例:
Base *pb = new Derived();
Derived *pd = static_cast<Derived *>(pd);
Derived d;
Base &rb = d;
Derived &rb = static_cast<Derived &>(rb);
** ** 注意事项一** **
- 基类对象一般无法被显式转换为派生类对象
- 对象到对象的转换,需要调用构造函数创建新的对象
- 派生类的拷贝构造函数无法接受基类对象作为参数
- 执行基类向派生类的转换时,一定要确保被转换的指针和引用所指向或引用的对象符合转换的目的类型:
对于Derived *pd = static_cast<Derived *>(pb); - 一定要保证pb所指向的对象具有Derived类型,或者是Derived类型的派生类。
类型转换时的注意事项(2)
-
如果A类型是B类型的虚拟基类,A类型指针无法通过static_cast隐含转换为B类型的指针
可以结合虚继承情况下的对象内存布局,思考为什么不允许这种转换
void指针参加的转换,可能导致不可预期的后果:
例:(Base2是Derived的第二个公共基类)
Derived *pd = new Derived();
void *pv = pd; //将Derived指针转换为void指针
Base2 *pb = static_cast<Base2 *>(pv);
转换后pb与pd有相同的地址,而正常的转换下应有一个偏移量结论:有void指针参与的转换,兼容性规则不适用