C++继承——多继承导致的菱形继承

目录

单继承: 

多继承:

菱形继承:菱形继承是多继承的一种特殊情况。

三.菱形继承的两种解决方式区别:

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成员变量,关联成一体,一荣俱荣,一损俱损,也就不再会出现二义性的问题了!!!

        总结:一般不建议设计出多继承,这样可能会在不知不觉间设计出菱形继承,设计菱形继承,就相当于给自己挖了一个坑,在复杂度及性能上都有问题。 

猜你喜欢

转载自blog.csdn.net/weixin_69283129/article/details/132021343
今日推荐