C++中的继承

C++中的继承

在C++中有三个很重要的特性,分别是封装,继承,多态,封装实现了管理,多态实现了灵活性,而继承就实现了代码的复用,在我们日常生产生活中,会发现很多类的关系相近,一个类中包含另一个类中所有成员,类似于车与轮子的关系等,基于这种情况我们提出了继承,一个类可以继承另一个类实现代码的复用。
可是我们前面的学习知道, C++有封装特性,有的东西是自己私有的,不希望被类外的访问,并且我们用访问限定符实现了这种保护关系。所以当我们实现继承的时候也分情况。
继承分了三种类型,公有继承,私有继承以及保护继承,我们在继承的类前加上访问限定符即可选定继承方式,三种继承关系保证了C++的封装以及安全性。
由于我们每个类中的成员也都受限于访问限定符的限制,配合三种继承方式,所以结合下来一共会有九种变化关系:

继承方式public成员protected成员private成员关系变化
public继承仍为 public成员仍为protected成员不可见非私有都不变
protected继承变为protected成员仍为protected成员不可见非私有都变保护
private继承变为private成员变为private成员不可见非私有变为私有

其实通过上表我们可以发现存在一个隐形的优先级,public>proyected>privated
当成员碰上继承方式的时候取优先级较低的进行继承,可是当父类是私有类型的时候,所有继承方式继承过来都是不可见的,这是为了保护隐私。

在使用继承中我们需要注意以下几点:
1.基类的私有成员在派生类中是不能被访问的,如果一些基类成员不想被基类对象直接访问,但需要在派生类中能访问,就定义为保护成员。可以看出保护成员限定符是因继承才出现的。
2.public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
3.protetced/private继承是一个实现继承,基类的部分成员并未完全成为子类接口的一部分,是has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。
4.不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,但是基类的私有成员存在但是在子类中不可见(不能访问)
5.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
6.在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承.

is-a/has-a关系:这个是一个小考点,做点补充,is-a就是“是什么”的意思,比如奔驰是一辆车,这种情况我们一般用public的接口继承,has-a是一种继承关系,比如车里边有轮胎,可是当我们车这个类继承轮胎类的时候是一种实现继承,轮胎还有一些细致的参数是不用继承下来的。

切片

当子类继承了基类后,事实上子类是拥有基类全部的成员的,那么我们就可以用子类给基类赋值,用基类的指针去指子类等操作,这个时候我们只用了子类继承下来基类的那一部分,这个行为被叫做切片。切片有以下几种规则
1.子类对象可以赋值给父类对象(切割/切片)
2.父类对象不能赋值给子类对象
3.父类的指针/引用可以指向子类对象
4.子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
首先我们来说明一二条

class car
{
protected:
 string _name;
};
class bmw :public car
{
private:
 int _num;
};
int main()
{
 car c;
 bmw b;
 c = b;
 system("pause");
 return 0;
}

在这里插入图片描述

上述图演示了当我们用子类给基类赋值时候的切片行为
而当我们用基类给子类赋值的时候,由于子类存在基类不存在的成员,所以失败
第三条和第四条就是说当我们用子类指针指向基类的时候虽然可以强转完成语法上的合理,可是由于越界访问会形成内存泄漏问题。

继承体系中的作用域

  1. 在继承体系中基类与派生类都有自己的作用域
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对成员直接访问,可是父类并没有被覆盖,只是被隐藏,可以用 父类::父类成员访问父类同名成员
  3. 注意在实际中的继承最好不要定义同名成员
  4. 这里说的隐藏,只要名字一样就会构成隐藏,与返回值,参数没关系

派生类的默认成员函数

这里提供一份详细的引用博客供参考
派生类的默认成员函数
需要注意的是,在子类构造的时候由于用基类初始化,所以会先调基类的构造函数,这个时候记得不要自己调用析构函数,因为v会引起栈帧的混乱。

单继承与多继承

多继承故名思意就是一个子类继承多个基类在这里插入图片描述
看起来多继承是方便了很多,可是事实使用上却麻烦不少,因为会产生菱形继承
在这里插入图片描述
菱形继承就会产生二义性与数据冗余问题
假设A类中有一个A函数,那么B与C函数就会分别继承A函数,从而D就会继承两份A函数,形成数据冗余,同样的要是B与C函数中有同名的函数会产生二义性问题
这里来一段代码说明数据冗余与二义性问题

#include<iostream>
#include<windows.h>
#include<string>
using namespace std;
class A
{
protected:
 char _a;
};
class B :public A
{
public:
 void display()
 {
  cout << "this is class B!\n" << endl;
 }
protected:
 char _b;
};
class C :public A
{
public:
 void display()
 {
  cout << "this is class C!\n" << endl;
 }
protected:
 int _c;
};
class D :public B, public C
{
protected:
 int _d;
};
int main()
{
 cout << sizeof(A) << endl;
 cout << sizeof(B) << endl;
 cout << sizeof(C) << endl;
 cout << sizeof(D) << endl;
 D d;
 d.C::display();
 d.B::display();
 system("pause");
 return 0;
}

在这里插入图片描述
这里可以看到,第四个D经过菱形继承由于内存对齐大小已经是16,然而里边对于A的_a继承了两遍,数据冗余,二义性经过添加作用域可以解决,那么这个时候D的对象模型是
在这里插入图片描述
这里我们可以很明显的发现a存了很多次,那么为了解决C++中的菱形继承所带来的问题,我们提出了虚继承来解决,虚继承又称共享继承,是面向对象编程的一种技术,是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类直接或间接派生的其他类。虚拟继承是多重继承中特有的概念,虚拟继承就是为了解决多重继承而出现的。
这里我想引入《C++ Primer》这本书中对虚继承的有关描述。
在C++语言中我们通过虚继承的机制来解决共享问题。虚继承的目的是令某个类作出声明,承诺共享它的基类。其中,共享的基类子对象称其为虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只含有唯一一个共享的虚基类子对象。
用上述代码作为例子,我们只用在B继承A与C继承A的时候声明是虚继承即可,这样B与C就会获得一个虚基表,虚基表里边存的是对于基类成员的偏移量。

class B: virtual public A

我们用virtual来虚继承
这个时候D的对象模型是
在这里插入图片描述
这样最终只有一个a,解决了数据冗余的问题,我们这里需要注意的是,D中存的是指向虚基表的指针,虚基表里边是通过偏移量找到a的而不是通过指针。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值