【C++】多继承与多级继承

多级继承

多级继承 中,一个类继承另一个类,而这个类又继承另一个类,形成继承链

在这里插入图片描述

#include <iostream>
using namespace std;

class A
{
public:
    void showA(){ cout<<"A类"<<endl;}
};

class B:public A
{
public:
    void showB(){ cout<<"B类"<<endl;}
};

class C : public B
{  // C 同时继承 A 和 B
    public:
        void showC() { cout << "C类" << endl; }
};

int main()
{
    C obj;
    obj.showA();  // ✅ 来自 A
    obj.showB();  // ✅ 来自 B
    obj.showC();  // ✅ C 自己的成员
}
class  A //基类  
class  B : public  A   //派生类   
class  C : public  B   //派派生类  
 
 构造执行顺序: A  ->    B  ->  C   
 析构执行顺序: ~C ->   ~B ->  ~A

多继承

多继承 中,一个类可以同时继承多个基类,从多个类获取特性和功能。

在这里插入图片描述

语法

 class  子类的名字:public 父类1,public 父类2  //公有继承
{

}
#include <iostream>
using namespace std;

class A {
public:
    void showA() { cout << "A 类" << endl; }
};

class B {
public:
    void showB() { cout << "B 类" << endl; }
};

class C : public A, public B {  // C 同时继承 A 和 B
public:
    void showC() { cout << "C 类" << endl; }
};

int main() {
    C obj;
    obj.showA();  // ✅ 来自 A
    obj.showB();  // ✅ 来自 B
    obj.showC();  // ✅ C 自己的成员
}

多继承的构造/析构函数调用

构造函数调用顺序:多个父类从左到右调用
析构函数调用顺序:多个父类从右到左调用
----------------------------------------
class C : public A, public B{}   //💥💥先调用a后调用b的构造函数
class C : public B, public A{}	 //💥💥先调用b后调用a的构造函数

多继承+带参数构造函数

正常来说是系统调用默认的构造函数,如果基类构造函数需要参数,应该在派生类的初始化列表中显式调用它们。

#include <iostream>
using namespace std;

class A
{
public:
    A(int x) {cout << "A的构造函数,x = " << x <<endl;}
    A() {cout << "A的无参构造函数" << endl;}
    ~A() {cout << "A的析构函数" << endl;}
};

class B
{
public:
    B(int y) {cout << "B的构造函数,y = " << y <<endl;}
    B() {cout << "B的无参构造函数" << endl;}
    ~B() {cout << "B的析构函数" << endl;}
};

class C : public A, public B
{
public:
    //✅写法1:部分指定,部分默认
    //C():A(1){cout << "C的无参构造函数" << endl;}//c无参构造指定A的构造函数,B的使用默认的
    //C():B(2){cout << "C的无参构造函数" << endl;}//c无参构造指定B的构造函数,A的使用默认的
    //✅写法2:全部指定,顺序随便写,最终编译器按照继承的时候顺序从左到右调用
    C():A(1),B(2){cout << "C的无参构造函数" << endl;}
    ~C() {cout << "C的析构函数" << endl;}
};

int main()
{
    C c;
    return 0;
}

多继承参数列表初始化

#include <iostream>
using namespace std;

class A
{
public:
    A(int _x):a(_x) {cout << "x = " << x <<endl;}
private:
    int x;
};

class B
{
public:
    B(int _y):y(_y) {cout << "y = " << y <<endl;}
private:
    int y;
};

class C : public A, public B
{
public:
    C(int _x,int _y,int _z):A(_x),B(_y),z(_z){cout << "z = " << z << endl;}
private:
    int z;
};

int main()
{
    C c(1,2,3);
    return 0;
}

多继承子类和父类同名方法

#include <iostream>
using namespace std;

class A
{
public:
    void show(){cout << "A" << endl;}
   
};

class B
{
public:
    void show(){cout << "B" << endl;}
};

class C : public A, public B
{
public:
   void show(){cout << "C" << endl;}
};

int main()
{
    C c;
    c.show(); // ✅由于和父类方法同名了,因此会隐藏父类方法,改成调用自己的方法
    c.A::show(); // ✅调用父类A的方法
    c.B::show(); // ✅调用父类B的方法
    return 0;
}

多继承的问题

菱形继承结构

菱形继承(Diamond Inheritance) 是 C++ 多继承 时遇到的经典问题。当一个类 同时从两个基类继承,而这两个基类又继承自同一个祖先类 时,就会形成 菱形结构,导致 数据冗余和二义性

在这里插入图片描述

菱形继承的问题

问题 1:🚨二义性(Ambiguity)

在这里插入图片描述

为什么?

回顾之前的知识点,子类继承父类时,在子类的地址空间会有父类的地址空间副本,多重继承也是如此。

在这里插入图片描述

解决方法

显示指定使用哪个副本

Leo l1;
//l1.eat();// ❌ 编译错误,x 存在二义性!
l1.Catkind::eat();  //✅我要使用猫科动物创建的Animal副本来调用eat
l1.Manmual::eat();  //✅我要使用哺乳动物创建的Animal副本来调用eat

问题 2:🚨 数据冗余

观察构造函数调用的情况

#include <iostream>
using namespace std;

class Animal
{
public:
	Animal()
	{
		cout<<"Animal无参构造"<<endl;
	}
	void eat()
	{
		cout<<"Animal动物吃"<<endl;
	}
};
class Catkind:public Animal
{
public:
	Catkind()
	{
		cout<<"猫科动物无参构造"<<endl;
	}
private:
	int age;
};

class Manmual:public Animal
{
public:
	Manmual()
	{
		cout<<"哺乳动物无参构造"<<endl;
	}
private:
	double weight;
};

class Leo:public Catkind,public Manmual
{
public:
	Leo()
	{
		cout<<"豹子无参构造"<<endl;
	}
};

int main()
{
	//观察构造函数的调用情况
	Leo l1;
	return 0;
}

在这里插入图片描述

如何解决? 使用虚继承

⭐(重点)virtual 虚方法

1.当C++ 使用了virtual 虚方法时,系统就会产生一个虚表,用于存储所有的虚方法  (虚函数,虚继承)。
2.当虚表中含有相同的方法时,会产生一个覆盖的特性。
3.当一个类中包含一个虚函数时,会产生一个虚表指针,指向系统的虚表。  
4.虚表指针永远在类,最开始的地址!  

虚继承

功能:解决菱形继承中的二义性与数据冗余问题!

语法

 class  子类:virtual public  父类
 class  子类:public virtual  父类
 {
    
 };
virtualpublic的次序无关紧要

示例:使用虚继承彻底解决菱形继承的问题

#include <iostream>
using namespace std;
//虚基类
class Animal
{
public:
	Animal()
	{
		cout<<"Animal无参构造"<<endl;
	}
	void eat()
	{
		cout<<"Animal动物吃"<<endl;
	}
};
//猫科动物虚继承动物
class Catkind:virtual public Animal
{
public:
	Catkind()
	{
		cout<<"猫科动物无参构造"<<endl;
	}
private:
	int age;
};
//哺乳动物虚继承动物
class Manmual:virtual public Animal
{
public:
	Manmual()
	{
		cout<<"哺乳动物无参构造"<<endl;
	}
private:
	double weight;
};

class Leo:public Catkind,public Manmual
{
public:
	Leo()
	{
		cout<<"豹子无参构造"<<endl;
	}
};

int main()
{
	//观察构造函数的调用情况
	Leo l1;
	//调用间接父类Animal的eat方法
	l1.eat(); 
	
	return 0;
}

在这里插入图片描述

虚基类表

C++ 虚继承 中,编译器使用虚基类表(VBT,Virtual Base Table)”和“虚基类指针(VBP,Virtual Base Pointer)来管理虚基类的共享实例

虚基类指针

虚继承的类中,每个对象内部会多出一个 虚基类指针(VBP),用于指向虚基类表(VBT)

示例:证明虚继承后子类地址空间多出一个指针,专门用于指向虚基类表

#include <iostream>
using namespace std;
class Animal
{
public:
	Animal()
	{
		cout<<"Animal无参构造"<<endl;
	}
	void eat()
	{
		cout<<"Animal动物吃"<<endl;
	}
};
//猫科动物虚继承动物
class Catkind:virtual public Animal
{
public:
	Catkind()
	{
		cout<<"猫科动物无参构造"<<endl;
	}
private:
	int age;
};
//哺乳动物虚继承动物
class Manmual:virtual public Animal
{
public:
	Manmual()
	{
		cout<<"哺乳动物无参构造"<<endl;
	}
private:
	double weight;
};

class Leo:public Catkind,public Manmual
{
public:
	Leo()
	{
		cout<<"豹子无参构造"<<endl;
	}
};

int main()
{
	//求子类的大小--》证明子类的地址空间中多了一个指针
	cout<<"猫科的大小: "<<sizeof(Catkind)<<endl;
	cout<<"哺乳的大小: "<<sizeof(Manmual)<<endl;
	cout<<"豹子的大小: "<<sizeof(Leo)<<endl;
	return 0;
}

在这里插入图片描述

虚继承的的底层原理

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值