这里写目录标题
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
那么,结论就来了
虚继承中会在每个派生类类中加入一张虚基表。先把父类的成员放在一个公共区域,虚基表中存了公共区域相对于此类的偏移量,那么每个类就可以计算出父类成员的位置了
版权归原作者 东条希尔薇 所有, 如有侵权,请联系我们删除。