首先来了解下二义性:
其实说白了就是解决多重多级继承造成的二义性问题。例如有基类B,从B派生出C和D,然后类F又同时继承了C和D,现在类F的一个对象里面包含了两个基类B的对象,如果F访问自己的从基类B那里继承过来的的数据成员或者函数成员那么编译器就不知道你指的到底是从C那里继承过来的B对象呢还是从D那里继承过来的B对象。
于是虚基类诞生了,将C和D的继承方式改为虚继承,那么F访问自己从B那里继承过来的成员就不会有二义性问题了,也就是将F对象里的B对象统一为一个,只有一个基类B对象。 二义性的两种情况:
第一种情况是一个类由多个基类派生时,不同基类中的数据成员或成员函数同名,在派生类中使用不同基类中的同名成员时产生二义性。
第二种情况是当一个派生类由多个基类派生,而这些基类又有一个共同的基类,当对该基类中说明的成员进行访问时,可能出现二义性。
eg.
#include <iostream>
using namespace std;
class Base1 { //定义基类Base1
public:
int var;
void fun() { cout << "Member of Base1" << endl; }
};
class Base2 { //定义基类Base2
public:
int var;
void fun() { cout << "Member of Base2" << endl; }
};
class Derived: public Base1, public Base2 { //定义派生类Derived
};
int main() {
Derived d;
Derived *p = &d;
//d.var = 1; //对象名.成员名标识 错误 成员名具有二义性
//d.fun(); //访问Derived类成员 错误 成员名具有二义性
d.Base1::var = 2; //作用域分辨符标识
d.Base1::fun(); //访问Base1基类成员
p->Base2::var = 3; //作用域分辨符标识
p->Base2::fun(); //访问Base2基类成员
return 0;
}
解决二义性问题主要有四种方法:
(1)通过作用域运算符(::)明确指出访问的是哪一个基类中的成员。使用作用域运算符进行限定的一般格式是:
<对象名>.<基类名>::<成员名> //数据成员
<对象名>.<基类名>::<成员名>(<参数表>) //成员函数
(2)在子类中定义同名成员(如上两例中c类定义的a和disp函数)。与基类成员同名的派生类成员将屏蔽对基类成员的直接访问(同名隐藏),这就是支配原则。
(3)虚基类。虚基类保证了一个类不能从同一个类中直接继承一次以上。
(4)各基类中的成员各不相同,当然,这不是一个很好的解决办法。
下面重点说明虚基类:
虚基类是对派生类而言,所以,虚基类本身的定义同基类一样,在定义派生类时声明该基类为虚基类即可,就是冠以关键字virtual。
虚基类的语法格式为:
class <类名> : virtual <继承方式> <基类名>
{ ... }
eg.
class Base1: virtual public Base0 { //定义派生类Base1
public: //新增外部接口
int var1;
};
关键字virtual告诉编译器,该基类不管经过多少次派生,在其派生类中只能有该基类的一个拷贝。----虚基类的作用
虚基类及其派生类构造函数小结:
l 建立对象时所指定的类称为最(远)派生类。
l 虚基类的成员是由最(远)派生类的构造函数通过调用虚基类的构造函数进行初始化的。
l 在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用。如果未列出,则表示调用该虚基类的默认构造函数。
l在建立对象时,只有最派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略。
l 在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数的调用。
9899



被折叠的 条评论
为什么被折叠?



