多重继承(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了。