C++的多种继承方式

本文详细介绍了C++中的继承方式,包括单继承、多继承、菱形继承、虚拟继承和菱形虚拟继承,探讨了各种继承类型的特性、内存布局以及解决的二义性问题。并总结了继承权限的作用和在不同继承方式下的使用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++类的继承

继承的意义在于代码可以复用,在原有的基础上添加新功能就变成了新一代产品了(就像移动电话发展史一样,从大哥大到现在的智能手机,代代相传添砖加瓦,单车变摩托,平房变别墅。究其本质是不变的),继承也是为了多态服务的。

class的继承分为单继承,多继承,菱形继承,虚拟继承,菱形虚拟继承。

单继承:

单继承就是子类Derived(派生类)继承自一个基类Base (父类),其实就是子承父业。其在C++中的语法是:class Derived:[继承类型] Base继承类型有:public,protect, private。单继承计算派生类大小方式是:sizeof(Base)+派生类新增成员的大小

这三种继承权限的区别是:

  • public,以这种权限继承并没有改变基类的任何权限。
  • protected,以它继承,基类的public权限将会被改变为protect。也就是说基类的成员只能在基类或派生类内部访问,不能在外部调用。protect与private在同一类中的作用是一样的(体现封装性),它们的区别是在继承中基类的protect成员可以在派生类中使用,而基类的private成员只能在基类中使用。
  • private,这种方式继承,基类的public,protect权限会被改为private,也就是说基类的所有权限都变成了私有,只能在其内部访问。 
    单继承例:

    class Base
    {
    public:
    void SetValue(int pri, int pro, int pub)
    void Show()
    private:
    int _pri
    ;
    protected:
    int _pro;
    public:
    int _pub;
    };

    class Derived:public Base
    {
    public:
    void SetDerived(int pri, int pro, int pub)
    {
    _pub = pub;
    _pro = pro;
    //_pri = pri; // 基类中私有的成员继承下来,不能访问
    _pubD = pub;
    _proD = pro;
    _priD = pri;
    }
    private:
    int _priD;
    protected:
    int _proD;
    public:
    int _pubD;
    };

在继承中会有一些小小的问题,比如说在基类中有一个成员变量或者成员函数,在派生类中也有一个相同名称的成员变量(与其类型无关)或者成员函数(与其参数无关),编译器在处理这类问题时选择同名隐藏(即隐藏基类中的成员函数或变量名),当调用这种相同名字的成员函数或变量时只会在派生类新增的成员中查找(除非调用时加上作用域)。

多继承:

多继承相对于单继承就是一个派生类Derived同时继承多个基类Base。介个没法用类似子承父业的例子解释^_^…。它的语法是:class Derived:[继承类型] Base1,[继承类型] Base2,在内存中存储基类的成员变量时 ,先继承谁谁的成员变量就在相对的低地址(对小端字节序的vs而言)出存放(意思就是先继承谁就先存放谁)。在单继承中派生类新增成员存放在基类前。这里的两个继承类型都得加,若不加则默认为以私有类型继承。派生类的大小:sizeof(Base1)+sizeof(Base2)+派生类新增成员的大小 
多继承例:

class Base1
{
public:
int _b1;
};
class Base2
{
public:
int _b2;
};
class Derived :public Base2, public Base1 //语法
{
public:
int _d;
};
int main()
{
cout << sizeof(Base1) << endl;
cout << sizeof(Base2) << endl;
cout<<sizeof(Derived)<<endl;
Derived d;
d._b1 = 1;
d._b2 = 2;
d._d = 3;
system("pause");
return 0;
}

测试结果:

菱形继承:

菱形继承顾名思义,肯定和菱形有关,废话不多说还是用图来解释吧。

 
Alt text就是C1和C2单继承自基类Base,派生类Derived再多继承自C1,C2,这样的结构像是菱形。计算菱形继承派生类的大小是:sizeof(C1)+sizeof(C2)+派生类新增成员大小 
菱形继承例:

class Base
{
public:
int _b;
};
class C1 : public Base
{
public:
int _c1;
};
class C2 :public Base
{
public:
int _c2;
};
class Derived : public C1, public C2
{
public:
int _d;
};
int main()
{
Derived d;
cout << sizeof(C1) << endl;
cout << sizeof(C2) << endl;
cout << sizeof(Derived) << endl;
d.C1::_b = 0;
d._c1 = 1;
d.C2::_b = 2;
d._c2 = 3;
d._d = 4;
system("pause");
return 0;
}

测试结果

菱形继承又出现了一些问题,就是它在不加访问限定符的情况下访问基类成员会出问题,编译器不通过这种访问(这时候编译器就傻了,到底该访问哪一个呢~)。为了解决这个二义性问题又出现了菱形虚拟继承。先来看看虚拟继承

虚拟继承

虚拟继承为解决多重继承出现的二义性问题而出现,在其派生类定义开始的地方先存放了一个指针,这个指针指向了一个偏移量表,这个偏移量表存放的是派生类成员相对于基类成员的偏移量,虚拟继承的大小sizeof(Base)+派生类新增成员大小+一个virtual指针 
虚拟继承例:

class Base
{
public:
Base()
{}
public:
int _b;
};
class Derived :virtual public Base
{
public:
Derived()
:Base()
{}
int _d;
};
int main()
{
cout << sizeof(Base) << endl;
cout << sizeof(Derived) << endl;
Base b;
Derived d;
d._b = 0;
d._d = 1;
system("pause");
return 0;
}

测试结果:

编译器会自动合成默认构造函数的情况:

  • class1内部的成员是另一个class2类的对象,class2这个类有构造函数,这时即使class1没有构造函数编译器也会自动合成一个构造函数。
  • 在继承中,基类定义缺省构造函数,派生类没有显示定义构造函数,编译器会自动合成构造函数。
  • 在虚拟继承中也会自动合成构造函数。为了填写偏移量表地址。
  • 类中有虚函数会自动合成构造函数用来初始化虚函数列表。
菱形虚拟继承:

结合了虚拟继承和菱形继承,解决了菱形继承的二义性问题。其派生类大小sizeof(C1)+sizeof(C2)+派生类新增成员的大小,菱形虚拟继承的virtual关键字只能加在继承基类时,因为只在继承积累时才有二义性问题。虚拟继承就不会在派生类中出现两份基类的成员。

测试代码:

class Base
{
public:
int _b;
};
class C1 : virtual public Base
{
public:
int _c1;
};
class C2 : virtual public Base
{
public:
int _c2;
};
class Derived : public C1, public C2
{
public:
int _d;
};
int main()
{
cout << sizeof(C1) << endl;
cout << sizeof(C2) << endl;
cout << sizeof(Derived) << endl;
Derived d;
d._b = 0;
d._c1 = 1;
d._c2 = 2;
d._d = 3;
system("pause");
return 0;
}

测试结果:

红框是虚拟继承自C1的virtual指针,它相对于自己的偏移量和相对于基类的偏移量分别是0和20字节,蓝框就是C2的virtual指针了。

总结
  1. 基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要 在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
  2. public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类 对象也都是一个父类对象。
  3. protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分, 是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的 都是公有继承。私有继承以为这is-implemented-in-terms-of(是根据……实现的)。通常比 组合(composition)更低级,但当一个派生类需要访问基类保护成员或需要重定义基类的虚函 数时它就是合理的。
  4. 不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存 在但是在子类中不可见(不能访问)。
  5. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最 好显示的写出继承方式。
  6. 在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承.

赋值兼容规则–public继承

  1. 子类对象可以赋值给父类对象(切割/切片)
  2. 父类对象不能赋值给子类对象
  3. 父类的指针/引用可以指向子类对象
  4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)

友元与继承 
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。 
继承与静态成员 
基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有 一个static成员实例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值