0


c++类继承

这里写目录标题

c++类继承

继承简介

概念

继承是一种简化代码的手段之一,使代码可以实现复用。它允许在原来类的功能上进行扩展,衍生出新的功能。也可以把多个类的基本信息提取到某个类,减少代码的冗余。其中被继承的类通常叫做父类或基类,继承下来的类叫做子类或派生类

使用格式

子类名:继承类型 继承的父类。例如:以人类和学生类做例子

classperson{public:int _name;int _age;};classstu:publicperson{
    int_ id;}

其中,stu就是派生类,而person是父类

继承类型

其中继承类型有三种:public,protect,private继承。其中:

  • public继承下来的父类成员在子类中保持其访问权限
  • private继承下来的父类对象在子类中也变为private成员
  • 父类的private成员无论怎么样对子类也不可见
  • 但如果想让仅有父子能访问,外部不能访问。可以把父类中的成员属性设置为protected

就像什么呢?爸爸和儿子住一间房子下,虽然家里的东西大家可以共用,但是爸爸总有隐私不能让儿子发现,比如爸爸的私房钱

总结:基类其它成员在子类中的访问方式=min(基类访问限定符,访问方式),权限大小:public>protected>private。但实际中基本用public继承

classFather{public:int _a;protected:int _b;private:int _c;};classSon:publicFather{public:int _d;};

上述代码中,外部只能访问a和d,子类还可以访问b,但c只有父类能访问

基类和派生类的类型转换及切片

首先声明:切片不是强制类型转换,而是语法特性的一种

派生类可以赋值给父类,但父类不能赋值给子类

Father f1;
Son s1;
f1=s1;

为什么能这么赋值呢?

我们知道,子类中的成员肯定是不少于父类的成员的。在赋值时,子类只需要把父亲没有的成员给丢弃后赋值给子类,就像切面包一样一刀切下去,所以也叫做切片

在这里插入图片描述

那有没有基类赋值给派生类的方式呢?有的,用指针就行了

Son s1;
Father* f1=&s1;

但通过指针父类只能访问它有的成员,访问子类成员时程序就会崩溃

使用注意:只建议在public继承下使用

基类和子类的作用域及隐藏

作用域

我们知道,程序以一对花括号来表示不同的作用域,例如:

int a=0;{int a=1;
    cout<<a<<endl;//1}
cout<<a<<endl;//2

1处的a输出1,2处的a输出0.因为1的作用域在a=1内部,而2的作用域在外部

成员变量隐藏

父类和子类它们属于不同的作用域,在有同名变量被声明时,使用起来也会发生上面代码类型的情况

classFather{public:int _a;};classSon:publicFather{public:int _a;};

Father f1;
Son s1;
f1._a=1;
s1._a=2;
cout<<s1._a<<endl;

在上面的情况,父类的成员将会被屏蔽,子类将会直接访问属于它的成员,输出为2

虽然语法上支持这种写法,但是,这样写代码非常容易造成二义性混乱,所以写代码要规避这种情况的发生

函数隐藏

条件:只要父类和子类有相同名字的函数就构成隐藏(忽略参数和返回值)

classFather{public:voidfunc(){
        cout<<"Father"<<endl;}};classSon:publicFather{public:voidfunc(int i){
        cout<<"Son"<<endl;}};

Father f1;
Son s1;
s1.func();//这么写会报错,因为隐藏了父类的func函数,所以提示缺少参数
s1.func(10);//正常调用

派生类的默认成员函数

我们知道,在我们写一个类的时候,编译器通常会给我们形成以下的默认成员函数:

  • 构造
  • 析构
  • 拷贝
  • 赋值

那么,由于派生类其中有父类的存在。派生类的这些函数到底是继承父类的呢还是自己实现的呢?

c++中采用了比较简单的方式,就是各管各的

总的来说,在派生类生成的默认函数中,父类的成员部分由父类的函数自己完成,而派生类独有的成员需要在派生类中自己实现。

  • 如果派生类中没有显示的定义默认函数,那么生成的函数中,父类部分调用父类的成员函数,子类的按普通类处理> 普通类没有定义默认函数的处理方式:内置类型不处理(随机值),自定义类型调用它的默认函数classFather{public:Father():_a(1){}int _a;};classSon:publicFather{public:int _b;};cout<<Son()._a<<" "<<Son()._b<<endl;> 在上述代码中,a输出1,因为派生类中默认调用的父类的构造。b输出随机值,因为派生类中未作处理
  • 如果定义了,会有一个顺序问题,构造函数会先调用父类,而析构函数先调用派生类

在这里插入图片描述

如果父类没有默认构造或其它成员函数,那么在派生类中必须显示调用父类的一部分

classFather{public:Father(int i):_a(i){}int _a;};classSon:publicFather{public:Son(){}int _b;};

这样的话会直接编译报错,在子类中显示调用就不会

而且在显示调用时,只能在初始化列表中调用

classSon:publicFather{public:Son():Father(1){}int _b;};

多继承及其带来的菱形继承问题

多继承

c++中允许一个派生类继承自多个基类,连接继承关系时使用逗号

classA{public:int _a;};classB{public:int _b;};classC:publicA,publicB{public:int _c;};

c可以访问到a和b两个成员变量

但是多继承是c++的一个早期设计的大坑,它有可能会带来数据冗余和二义性的问题

看下面的代码

classA{public:int _a;};classB:publicA{public:int _b;};classC:publicA{public:int _c;};classD:publicB,publicC{public:int _d;};

它们的继承关系是这样的

在这里插入图片描述

我们可以发现,在D这个类中,_a有两份!

因为D分别继承B和C,而BC都继承了A,所以D自然继承了BC中都包含的A的那一部分,造成了数据的冗余

而在使用的时候,不能直接这样

D d;
d._a=1;

会提示数据不明确而报错

解决方式:在_a前加上限定符

d.B::_a=1;

这个就是菱形继承了

但这么写相当的奇怪,我们必须想办法解决

菱形继承解决方法及其原理

我们先观察一下D类的内存情况吧

D d1;
d1.B::_a =1;
d1.C::_a =5;
d1._b =2;
d1._c =3;
d1._d =4;

在这里插入图片描述

其中谁先继承,谁就在内存的偏前位置

我们看到,圈出来的1和5都是属于A类的,确实冗余了

为了更正这个问题,后面提出了虚继承的概念

虚继承的使用,就是在可能会造成二义性的类前加入virtual修饰,(下面的B和C,因为同时继承了A,就可能造成二义性)这样就解决了数据二义性问题

classA{public:int _a;};classB:virtualpublicA{public:int _b;};classC:virtualpublicA{public:int _c;};classD:publicB,publicC{public:int _d;};

cout<<D._a<<endl;//这样使用就不会出现不明确的问题了

那么,虚继承的原理是什么呢?同样先观察内存

在这里插入图片描述

我们发现,我们能找到我们定义的变量,但中间的一些奇怪的数字是啥呢?

看着结构有点像地址,我们再跟踪地址找找看吧

在这里插入图片描述

我们发现它指向了一个值一个14一个0c,因为这是16进制,我们把它转换为10进制

然后在加上B类和C类中的地址,我们发现它居然指向一个值,就是本章第一张图片下面的01

那么,结论就来了

虚继承中会在每个派生类类中加入一张虚基表。先把父类的成员放在一个公共区域,虚基表中存了公共区域相对于此类的偏移量,那么每个类就可以计算出父类成员的位置了

标签: c++ 开发语言

本文转载自: https://blog.csdn.net/weixin_57402822/article/details/125984437
版权归原作者 东条希尔薇 所有, 如有侵权,请联系我们删除。

“c++类继承”的评论:

还没有评论