目录
单继承:
多继承:
菱形继承:菱形继承是多继承的一种特殊情况。
三.菱形继承的两种解决方式区别:
3.1采用作用域解决的菱形继承:
检测器运行图:
反汇编运行图:
3.1菱形虚继承:
检测器运行图:
反汇编的运行图:
继承分为单继承和多继承。
单继承:
class A{
public:
A(int a) {}
protected:
int _a;
};
class B :public A {
public:
B(int a,int b)
:A(a)
,_b(b){ }
protected:
int _b;
};
class C :public B {
public:
C(int a,int b,int c)
:B(a,b)
,_c(c) {}
protected:
int _c;
};
多继承:
class A {
public:
A(int a = 10)
:_a(a) {
cout << "A()的构造函数" << endl;
}
protected:
int _a;
};
class B {
public:
B( int b=20)
: _b(b) {
cout << "B()的构造函数" << endl;
}
protected:
int _b;
};
//子类C同时继承了父类A,父类B
class C :public A,public B {
public:
C(int a=9, int b=8, int c=7)
:B(b)
,A(a)
, _c(c) {
cout << "C()的构造函数" << endl;
}
void Show() {
cout << "_a的值为:"<<_a << endl;
cout << "_b的值为:" << _b << endl;
cout << "_c的值为:" << _c << endl;
}
protected:
int _c;
};
int main() {
C c1(1,2,3);
c1.Show();
return 0;
}
多继承大大增强了代码的复用性,可以让一个简单的类去使用多个父类的成员,减少了代码的冗余性,一个指向多个基类的子类可以调用多个基类的不同方法,调用多个基类的成员变量。
菱形继承:菱形继承是多继承的一种特殊情况。
A类和B类继承了S类,而C类又同时继承了A类和B类,这种多继承会导致二义性和数据冗余!这是C++设计多继承所导致的一个大坑!!!
举个例子:
class Person {
public:
void Sleep() {
cout << "睡觉技能" << endl;
}
void Eat() {
cout << "吃饭技能" << endl;
}
};
//子类1:
class Author:public Person {
public:
void Write_book() {
cout << "写作技能" << endl;
}
};
//子类2:
class Programmer :public Person {
public:
void Programming() {
cout << "编程技能" << endl;
}
};
//孙子类
class Pro_Writer :public Author, public Programmer {
public:
void skill() {
cout << "既能编程又能写文章" << endl;
}
};
案例测试:
int main() {
Pro_Writer pw;
pw.Write_book(); //pw调用父类Author的方法
pw.Programming(); //pw调用父类Programmer的方法
pw.Sleep();
pw.Eat();
return 0;
}
在上方案例测试中,孙子类创建了一个对象,该对象调用了从父类Author继承的Write_book函数和Programmer继承的Programming函数,都挺好,增加了代码复用性;紧接着,该对象又调用了从父类继承得来的Sleep和Eat函数,这时就出现了分歧,因为这两个成员函数是Author类和Programmer类以及Person类都有的成员函数,编译器压根不知道程序员到底想调用哪个!!!
于是菱形继承的弊端就出来了,二义性体现的淋漓尽致。
有人会提到:在调用前指定类作用域不就行了吗?那样编译器就知道该调哪个类的Sleep函数了。
这种方法可以解决菱形继承带来的弊端
//解决方法1:——加作用域
pw.Author::Sleep();
pw.Programmer::Eat();
//这样,编译器就能精准的调用
其实还有一种更为官方的解决方式,C++官方为了解决了菱形继承带来的弊端,发明了virtual关键字,在菱形继承中为中间两个类的声明上加上virtual关键字,便可以解决二义性!
解决形式:
classA{ };
class B: virtual public A{ }; class C: virtual public A{ };
class D: public B,public C{ };
class Person {
public:
void Sleep() {
cout << "睡觉技能" << endl;
}
void Eat() {
cout << "吃饭技能" << endl;
}
};
//子类
//虚继承virtual
class Author : virtual public Person {
public:
void Write_book() {
cout << "写作技能" << endl;
}
};
//虚继承
class Programmer :virtual public Person {
public:
void Programming() {
cout << "编程技能" << endl;
}
};
class Pro_Writer :public Author, public Programmer {
void skill() {
cout << "既能编程又能写文章" << endl;
}
};
int main() {
Pro_Writer pw;
pw.Write_book(); //pw调用父类Author的方法
pw.Programming(); //pw调用父类Programmer的方法
pw.Sleep();
pw.Eat();
return 0;
}
代码解析:
在使用virtual前,两个子类Author和Programmer的Sleep,Eat函数都各家是各家的,各有各的函数地址,只不过名字相同。在不指定作用域前,编译器就是靠名字去找函数。
对中间两个子类使用虚继承virtual后,Author和Programmer两个子类的Sleep,Eat函数的地址是一样的了,即使不用作用域,编译器会将这两个类的这俩函数看成同一份函数,就好比公共资源一般,只要是Author和Programmer两个子类从Person继承过来的成员变量或者是成员函数,都是一体的,不分你我。
运行结果:
三.菱形继承的两种解决方式区别:
3.1采用作用域解决的菱形继承:
class A{
public:
int _a;
};
class B : public A{
public:
int _b;
};
class C : public A{
public:
int _c;
};
class D : public B, public C{
public:
int _d;
};
int main() {
D d;
//d._a=5; //指代不明确——二义
d.A::_a = 1;
d._b = 2;
d._c = 3;
d._d = 4;
d.B::_a = 5;
d.C::_a = 6;
return 0;
}
检测器运行图:
反汇编运行图:
从上图就可知:在指定作用域去修改_a变量的寄存器所存地址对于这三个类并不是完全相同的。
3.1菱形虚继承:
class A{
public:
int _a;
};
class B : virtual public A{
public:
int _b;
};
class C : virtual public A{
public:
int _c;
};
class D : public B, public C{
public:
int _d;
};
int main() {
D d;
d._a = 1;
d._b = 2;
d._c = 3;
d._d = 4;
d._a = 5;
d._a = 6;
return 0;
}
检测器运行图:
在检查器运行图中,基类A的_a变量独立显示出来了。
反汇编的运行图:
B,C类作为子类,继承了A类的成员变量_a,D作为子类,又同时继承了B,C类。通过调试会发现,对象d在给_a赋值为1时,D类中的_a值为1,并且B类中的_a,C类中的_a也赋值为1
这就是因为有virtual,使得编译器对3个不同类中的_a成员变量,关联成一体,一荣俱荣,一损俱损,也就不再会出现二义性的问题了!!!
总结:一般不建议设计出多继承,这样可能会在不知不觉间设计出菱形继承,设计菱形继承,就相当于给自己挖了一个坑,在复杂度及性能上都有问题。