【C++】多重继承与虚继承

多重继承与虚继承

1.单继承和多重继承的区别

单继承:子类只有一个父类

多重继承:一个类如果具备了多个其它类的特点,就可以使用多重继承

情况一:子类继承了多个父类

圆桌: 继承圆和桌子

骡子: 继承马和驴

豹子: 继承猫科动物和哺乳动物

情况二:A派生出B,B派生出C C接着派生(重点)

直接父类:Cat就是波斯猫的直接父类

间接父类:Animal就是波斯猫的间接父类

Animal --》Cat --》波斯猫 --》其他猫

环状继承(菱形继承)

A动物

哺乳B C猫科

D豹子

产生的问题:

问题一:A被构建了多次,浪费了存储空间(希望A只构建一次)

问题二:二义性问题,通过D去调用A里面的方法有两条途径(一条通过B调用,还有一条通过C调用)

2.语法规则

cpp 复制代码
class  子类的名字:public 父类1,public 父类2  //公有继承
{

}
  • 子类大小: 所有父类大小之和+子类本身的大小,满足字节对齐
  • 构造函数调用顺序:多个父类从左到右调用
  • 析构函数调用顺序:多个父类从右到左调用
  • 指定调用父类的构造函数
cpp 复制代码
roundtable(int newr, int  w,int h):circle(newr),desk(w,h)
{

}

注意:指定的时候多个父类的顺序无所谓,最终父类构造函数调用的顺序以当初继承时候书写的左右顺序为准

示例代码:多重继承子类指定父类的构造
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

/*
    多重继承子类指定父类的构造
    
*/
class Catamount  //猫科动物
{
public:
    Catamount()
    {
        cout<<"猫科构造"<<endl;
    }
    Catamount(int n)
    {
        cout<<"猫科构造,带int参数: "<<n<<endl;
    }
    ~Catamount()
    {
        cout<<"猫科析构"<<endl;
    }
};

class Mammal //哺乳动物
{
public:
    Mammal()
    {
        cout<<"哺乳构造"<<endl;
    }
    Mammal(string name)
    {
        cout<<"哺乳构造,带string的参数: "<<name<<endl;
    }
    ~Mammal()
    {
        cout<<"哺乳析构"<<endl;
    }
};


class Leopard:public Mammal,public Catamount
{
public:
    //情况1:部分指定,指定了哺乳动物,没有指定的依然使用无参构造
    // Leopard():Mammal("草")
    // Leopard():Catamount(666)
    //情况2:完全指定
    //       完全指定的时候,指定父类构造函数的顺序无所谓
    Leopard():Catamount(888),Mammal("肉")
    // Leopard():Mammal("肉"),Catamount(888)
    {
        cout<<"豹子构造"<<endl;
    }
    ~Leopard()
    {
        cout<<"豹子析构"<<endl;
    }
};

int main(int argc,char **argv)
{
    Leopard l1;
    return 0;   
}

/*
输出结果:
情况1:
    哺乳构造,带string的参数: 草      /     哺乳构造
    猫科构造                        /     猫科构造,带int参数: 666
    豹子构造                        /     豹子构造
    豹子析构                        /     豹子析构
    猫科析构                        /     猫科析构
    哺乳析构                        /     哺乳析构
情况2:
    哺乳构造,带string的参数: 肉
    猫科构造,带int参数: 888
    豹子构造
    豹子析构
    猫科析构
    哺乳析构
*/
  • 子类出现跟父类同名的方法(子类隐藏了父类的同名方法)
cpp 复制代码
round.getarea();          //圆桌自己的getarea
round.Circle::getarea();  //圆桌对象调用父类Circle的getarea  
round.Desk::getarea();    //圆桌对象调用父类Desk的getarea  
示例代码:多重继承子类隐藏父类的同名方法
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

/*
    多重继承子类隐藏父类的同名方法
*/
class Catamount  //猫科动物
{
public:
    void eat()
    {
        cout<<"猫科动物吃"<<endl;
    }
};

class Mammal //哺乳动物
{
public:
    void eat()
    {
        cout<<"哺乳动物吃"<<endl;
    }
};


class Leopard:public Mammal,public Catamount
{
public:
    void eat()
    {
        cout<<"豹子吃"<<endl;
    }
};

int main(int argc,char **argv)
{
    Leopard l1;
    l1.eat(); //两个父类的eat都被隐藏
    
    //调用父类的eat
    l1.Mammal::eat();
    l1.Catamount::eat();
    return 0;   
}

/*
执行结果:
    豹子吃
    哺乳动物吃
    猫科动物吃
*/

3.虚继承解决多重继承遇到的bug

示例代码:环状继承引发的问题
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

/*
    环状继承引发的问题?
       问题1:Animal被构建多次
              浪费了存储空间,C++希望Animal只构建一次
       问题2:豹子调用eat方法提示二义性
       
*/
class Animal
{
public:
    Animal()
    {
        cout<<"Animal无参构造了"<<endl;
    }
    void eat()
    {
        cout<<"Animal eat"<<endl;
    }
};

class Catamount:public Animal  //猫科动物
{
public:
    Catamount()
    {
        cout<<"Catamount无参构造"<<endl;
    }
};

class Mammal:public Animal //哺乳动物
{
public:
    Mammal()
    {
        cout<<"Mammal无参构造"<<endl;
    }
};


class Leopard:public Mammal,public Catamount
{
public:
    Leopard()
    {
        cout<<"Leopard无参构造"<<endl;
    }
};

int main(int argc,char **argv)
{
    Leopard l1;
    //编译报错:提示eat有二义性
    // l1.eat();  // error: request for member 'eat' is ambiguous
    
    //解决问题2:但是无法解决问题1
    l1.Mammal::eat();
    l1.Catamount::eat();
        
    return 0;   
}

/*
执行结果:
问题1: 可以看出Animal类被创建的2次
    Animal无参构造了
    Mammal无参构造
    Animal无参构造了
    Catamount无参构造
    Leopard无参构造

问题2: 可以解决报错,但无法解决问题1
    Animal无参构造了
    Mammal无参构造
    Animal无参构造了
    Catamount无参构造
    Leopard无参构造
    Animal eat
    Animal eat
*/

3.1 虚基类:

\quad 虚基类使得从多个类(B和C)它们的基类相同,共同的虚基类是(A)派生出(D)的对象只继承一个基类对象(通俗的讲就是子类使用virtual继承了父类,这个父类就是虚基类)

3.2 语法规则:

cpp 复制代码
class  子类:virtual public  父类
class  子类:public virtual 父类
{

};
virtual和public的次序无关紧要

3.3 总结:普通继承跟虚继承的区别

  • 虚继承可以解决二义性和A被构建多次这两个问题,普通继承不能解决;虚继承通过增加一个指针(浪费了一点存储空间),换取了更高的效率
  • 只要一个类虚继承了其它类,那么该类所有的对象中都会新增一个指针,该指针专门用来指向系统中虚基类表的首地址

3.4 虚基类表:

C++中专门用来存放虚基类地址的一种数据结构

底层原理如下:

示例代码:虚继承跟普通继承(没有使用virtual)的区别
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

/*
    虚继承的底层原理(虚继承跟普通继承(没有使用virtual)的区别):
    1.如果一个子类虚继承了另外一个父类,子类的地址空间中会多出一个指针
    2.指针的作用:用来指向虚基类表的首地址
*/
class Animal
{
public:
    Animal()
    {
        cout<<"Animal无参构造了"<<endl;
    }
    void eat()
    {
        cout<<"Animal eat"<<endl;
    }
};

class Catamount:virtual public Animal  //猫科动物
{
public:
    Catamount()
    {
        cout<<"Catamount无参构造"<<endl;
    }

private:
    int m_age;
};

class Mammal:virtual public Animal //哺乳动物
{
public:
    Mammal()
    {
        cout<<"Mammal无参构造"<<endl;
    }
};


class Leopard:public Mammal,public Catamount
{
public:
    Leopard()
    {
        cout<<"Leopard无参构造"<<endl;
    }
};

int main(int argc,char **argv)
{
    cout<<"猫科类的大小:"<<sizeof(Catamount)<<endl;  // 猫科类的大小:16
    cout<<"哺乳类的大小:"<<sizeof(Mammal)<<endl;     // 哺乳类的大小:8
    return 0;   
}

/*
执行结果:
    Leopard l1;
    l1.eat(); 时,输出:
    Animal无参构造了
    Mammal无参构造
    Catamount无参构造
    Leopard无参构造
    Animal eat
*/
相关推荐
新知图书1 小时前
R语言ICU患者死亡率预测实战
开发语言·r语言
yxc_inspire2 小时前
基于Qt的app开发第十四天
前端·c++·qt·app·面向对象·qss
wennieFan2 小时前
python基础面试练习题
开发语言·python
阿福不是狗2 小时前
Python使用总结之Linux部署python3环境
linux·开发语言·python
枣伊吕波2 小时前
第十三节:第七部分:Stream流的中间方法、Stream流的终结方法
java·开发语言
一点也不想取名2 小时前
解决 Java 与 JavaScript 之间特殊字符传递问题的终极方案
java·开发语言·javascript
im_AMBER3 小时前
java复习 11
java·开发语言
Cai junhao3 小时前
【Qt】工具介绍和信号与槽机制
开发语言·c++·qt·qt6.3
黑牛先生3 小时前
【Qt】信号与槽
开发语言·qt
橙子199110164 小时前
Kotlin 中的 Object
android·开发语言·kotlin