多重继承(MI),指的是派生类,同时继承两个或者多个类。
公有MI表示的是is-a关系,在继承的时候,需要用public来限制每一个基类。
私有和保护MI表示的has-a关系。MI和单继承在某些程度上很类似。
MI相对单继承来说,带来了很多问题,例如:
①从两个不同基类继承同名方法(例如基类A和基类B都有show()函数);
②从两个或更多的相关基类那里继承同一个类的多个实例(不懂)。
虚基类:
假如派生类D由两个基类B和C继承而来,而B和C都是A的派生类。那么D将同时包含2个A(B和C各包含一个A);
但是,由于我们只需要一个A(B和C重复了A的部分)。因此,不能简单的使用Class D:public B,public C 这样的方式进行继承。
针对这个问题,于是需要使用 虚基类,关键字为:virtual。
虚基类的意义在于,当MI的两个基类(B和C)都是基类,且其都是A的派生类时(A在此时作为B和C的虚基类,如class B :public virtual A),那么派生类D将只包含一个A,且在D的构造函数中,B和C将不传递值给其基类A,需要D额外调用基类A的构造函数。
例如:
类A的构造函数:
A();
A::A(const string & na, int m);
类B的构造函数:
B::B() :A(), b(0)
{
}
B::B(const string& na, int aa, int bb) : A(na, aa), b(bb)
{
}
类C的构造函数:
C::C() :A(), c(0)
{
}
C::C(const string& na, int A, int cc) : A(na, A), c(cc)
{
}
类D继承B和C而来,D的构造函数;
D::D():A(),B(),C()
{
}
D::D(const string&na, int aa, int bb, int cc) : A(na, aa), B(na, aa, bb), C(na, aa, cc)
{
}
在派生类类D的第二个构造函数中,其中第一个和第二个参数na和aa,作为基类B和基类C的参数,同时还调用了A的构造函数。
在非虚基类的情况下,这种方式是错误的,但是在遇见虚基类的情况下,这是唯一选择。
如代码:
类声明:
class A //由于有纯虚函数,所以A为抽象基类
{
string name;
int a;
protected:
virtual void get(); //设置名字和变量a
virtual void data(); //输出名字和变量a
public:
A();
A::A(const string & na, int m);
virtual ~A() = 0; //纯虚析构函数A
virtual void set() = 0; //设置名字
virtual void show() = 0; //输出
};
class B :public virtual A
{
int b;
protected:
void get(); //设置名字和变量a
void data(); //输出名字和变量a
public:
B();
B(const string& na, int aa, int bb);
void set(); //设置名字
void show(); //输出
};
class C :virtual public A
{
int c;
protected:
void get(); //设置名字和变量a
void data(); //输出名字和变量a
public:
C();
C(const string& na, int aa, int cc);
void set(); //设置名字
void show(); //输出
};
class D :public B, public C
{
protected:
void get();
void data();
public:
D();
D(const string&na, int aa, int bb, int cc);
void set();
void show();
};
类方法:
A::A()
{
name = "";
a = 0;
}
A::A(const string & na, int m)
{
name = na;
a = m;
}
void A::get()
{
cout << "请输入名字:";
getline(cin, name);
cout << "请输入a:";
cin >> a;
while (cin.get() != '\n')
continue; //直接放个分号也行呀,放continue是为了美观和看起来直接?
}
void A::data()
{
cout << "名字:" << name << endl;
cout << "a :" << a << endl;
}
B::B() :A(), b(0)
{
}
B::B(const string& na, int aa, int bb) : A(na, aa), b(bb)
{
}
void B::get()
{
cout << "请输入b:" << endl;
cin >> b;
while (cin.get() != '\n')
continue;
}
void B::data()
{
cout << "b :" << b << endl;
}
void B::set()
{
cout << "类B:" << endl;
A::get();
get();
}
void B::show()
{
cout << "类B:" << endl;
A::data();
cout << "b :" << b << endl;
}
C::C() :A(), c(0)
{
}
C::C(const string& na, int A, int cc) : A(na, A), c(cc)
{
}
void C::get()
{
cout << "请输入c:" << endl;
cin >> c;
while (cin.get() != '\n')
continue;
}
void C::data()
{
cout << "c :" << c << endl;
}
void C::set()
{
cout << "类C:" << endl;
A::get();
get();
}
void C::show()
{
cout << "类C:" << endl;
A::data();
cout << "c :" << c << endl;
}
D::D():A(),B(),C()
{
}
D::D(const string&na, int aa, int bb, int cc) : A(na, aa), B(na, aa, bb), C(na, aa, cc)
{
}
void D::get()
{
B::get();
C::get();
}
void D::data()
{
B::data();
C::data();
}
void D::set()
{
cout << "类D:" << endl;
A::get();
get();
}
void D::show()
{
cout << "类D:" << endl;
A::data();
data();
}
测试程序:
</pre><pre name="code" class="cpp">int main()
{
A* pr[4]; //声明一个A基类指针数组
for (int i = 0; i < 4; i++)
{
cout << "选择第"<<i+1<<"个使用的类:\nb.B类\tc.C类\td.D类" << endl;
char q;
cin >> q;
while (strchr("bcd", q) == nullptr)
{
cout << "输入错误,请输入b或者c或者d:";
cin >> q;
}
if (q == 'b')
pr[i] = new B;
else if (q == 'c')
pr[i] = new C;
else if (q == 'd')
pr[i] = new D;
cin.get();
pr[i]->set(); //指针调用函数使用->
}
for (int i = 0; i < 4; i++)
(*pr[i]).show();
system("pause");
return 0;
}
测试:
选择第1个使用的类:
b.B类 c.C类 d.D类
a
输入错误,请输入b或者c或者d:b
类B:
请输入名字:aa
请输入a:1
请输入b: 1
选择第2个使用的类:
b.B类 c.C类 d.D类
c
类C:
请输入名字:b b
请输入a:2
请输入c: 2
选择第3个使用的类:
b.B类 c.C类 d.D类
d
类D:
请输入名字:CC
请输入a:3
请输入b: 3
请输入c: 3
选择第4个使用的类:
b.B类 c.C类 d.D类
d
类D:
请输入名字:qqee
请输入a:4
请输入b: 4
请输入c: 4
类B:
名字:aa
a :1
b :1
类C:
名字:b b
a :2
c :2
类D:
名字:CC
a :3
b :3
c :3
类D:
名字:qqee
a :4
b :4
c :4
请按任意键继续. . .
总结:
①虚基类:
虚基类是可能涉及重复的基类,在这里A作为虚基类。因为在派生类D之中,基类B和C由A派生而来(因此多重继承时,会在D中产生重复部分),于是,在声明类B和类C时,A既是公有继承,又是作为虚基类继承。
具体形式则是在声明类B和类C时,让A作为虚基类;
声明类D时,B和C正常声明(但应分别声明为public,因为默认是private私有继承)
②另外,A不仅是虚基类,还是抽象基类(有纯虚函数),因此,指针是不能指向类A的(无法创建类A的对象)。
③方法strchr(字符串,字符) 的作用是检查字符在字符串中首次出现时的地址。
例如字符a在字符串"qqabb"中,首次出现的地址是字符串的地址+2个char的宽度。如果字符没有在字符串中出现,则返回nullptr。
因此,这个方法可以用来检测某字符是否是我们预想中的几个字符之一(如果不是,则返回null,用关系表达式可以作为判断条件)。
当基类和非基类混用时:
假如基类M分别派生出A、B、C、D四个派生类,其中:
M对于A和B,是用作虚基类;
M对于C和D,是用作非虚基类(即普通继承);
而派生类N,由A、B、C、D四个基类共同派生而来(普通继承)。
类M→(派生) | 类A(M是虚基类) | (MI)→类N |
类B(M虚) | ||
类C(M非虚) | ||
类D(M非虚) |
那么对于类N来说,从虚派生祖先(类A和类B)共同继承一个类M的子对象(如果还有更多虚派生祖先,例如E、F、G、H都是M作为虚基类并且派生出N,那么也一样是只继承一个);
从每一个非虚派生祖先(类C和类D)分别继承一个类M的子对象。
于是,类N有三个类M的子对象(A和B共同给一个,C和D分别给一个)。
虚基类和支配:
关于二义性:
对于普通MI来说,假如有两个基类有同名方法,派生类无同名方法,那么不使用作用域解析运算符的话,就导致二义性(因为编译器不知道你要调用哪一个基类的方法)。
对于虚基类来说,
①首先,派生类的同名方法优先于基类的同名方法。例如A是B的虚基类,都有方法show(),于是在B中用show()则指的是B的。B是C的基类,C中没有方法show(),在C中用show()则默认指B::show()。
②其次,在①的基础上,有类D是类A的派生类,且没有show()方法(于是D中的show()是A::show())。
然后E是D和C的派生类(于是同时出现C类中的B::show()和D类中的A::show()),
因为类B是由类A派生而来的,因此类B中的show()将优先于类A中的show()。
也就是说:在有 虚基类 存在的 前提下 ,在一个派生类中,如果有两个同名方法,且一个方法所在的类(假设为类B)是由另一个方法所在的类(假设为类A,且应是作为B的虚基类)派生而来的,那么派生出来的那个类的方法,将优于其基类的方法。——不懂
那么假如类A不是虚基类,但类B是C的虚基类,其结果有所变化么?
实验测试表明:
假如A派生B,B(作为虚基类)派生C;
A派生D;
C和D派生E;
同名方法存在与A和B之中,那么依然会导致二义性(通过E调用同名方法)。
只有当A作为虚基类分别派生出B和D时(且方法存在于A和B之中),以上流程才不会导致二义性。
像①无虚基类存在;②B是虚基类;③C和D中一个或者两个作为虚基类;都会导致二义性。
推测(应该是没问题的):
当虚基类和其派生类中存在同名方法时,派生类的同名方法优先于虚基类且不会导致二义性,否则会导致二义性。
确认:
在使用非虚基类的情况下,必须使用作用域解析运算符来区分同名方法,否则会引起二义性(除非该派生类重新定义了同名方法,则自动调用该派生类的同名方法)。
总结:
①有间接虚基类的派生类包含直接调用间接基类构造函数的构造函数,这对于间接非虚基类来说是非法的;
(这个是指A是B和C的虚基类,B和C是D的基类,于是对于D来说,他的构造函数在调用B和C的构造函数时,还必须同时调用A的构造函数)
(但如果A不是B和C的虚基类,那么这样做是非法的。注意,如果A是B的虚基类但不是C的虚基类,也是非法的)
②在面对虚基类时,通过优先规则来解决二义性。
总的来说,MI不是很明白,书上讲的略简单了一些,我觉得基础的方法用用还行。除非遇见复杂点的情况,然后仔细研究,才能搞懂MI了。