继承定义
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类
继承是is a的关系,子类(派生类)继承了父类(基类),子类拥有和父类一样的属性:
所以子类可以使用父类的方法,父类的成员变量也拷贝到了子类!
定义格式
派生类 继承方式 基类
class subclass : public parentclass {}
继承方式
权限:public>protected>private
父类该成员的权限与继承方式的权限取最小权限,就是在子类的成员权限
public都可以访问
protected 类外不可以访问,但子类可以访问
private 类外不可以访问,子类不可见
不可见:
父类中的"成员"还是拷贝到了子类中,子类访问不到,但子类内存中有
基类和派生类对象赋值转换
这部分是继承的重中之重,没完全理解这部分,继承肯定还存在问题,多态肯定也懵
下面我来阐述下我个人的理解,如有问题,敬请各位大佬斧正
1.基类不能赋值给派生类
2.派生类对象可以赋值给基类的对象/指针/引用,有个形象的说法叫切片或者切割
1:
基类含有的成员肯定小于等于派生类,这类似于类与对象this指针的赋值,只允许权限缩小,不允许权限扩大
2:
①派生类赋值给基类的对象
#include<iostream>
using namespace std;
class A
{
public:
void show()//切片
{
cout << _age << endl;
}
protected:
int _age=10;
};
class B :public A
{
public:
int _num;
};
int main()
{
B b;//派生类
A a=b;//基类
}
可以看到派生类继承了A,另外有拥有特有成员_num
将派生类赋值给基类的时候会发生截断(切片)
a是具体的类,会另外开辟出一个父类对象的空间(这在多态原理中会用到),并将派生类中父类的成员拷贝过去,虚函数指针并不会拷贝过去,a是具体的类,拥有A独有的虚函数指针
②派生类赋值给基类的指针/引用
这种情况指针会指向派生类中和父类共同的部分
像这样:
该指针和引用仅能访问和父类的公共部分,并且只能访问继承下来的父类成员
#include<iostream>
using namespace std;
class A
{
public:
//A:this *= B:this*;
void show()//切片
{
cout << _age << endl;
}
protected:
int _age=10;
};
class B :public A
{
public:
int _age = 20;
int _num;
};
int main()
{
//1
B b1;
b1.show();
//2
A* a1 = &b1;
a1->show();
//3
A& a2 = b1;
a2.show();
return 0;
}
b1,a1的内部结构
实例化b1后,b1调用show函数,派生类可以调用父类的函数,此时发生,父类的this指针=子类的this指针,发生切片行为,并且这个指针只能访问从父类中继承下来的成员,不能访问子类的特有成员
此外,子类跟父类都有独有的作用域,子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义
所以从上图中看到b1中有两个_age,从A中继承下来的等于10,重定义的等于20,所以打印的是父类继承下来的10
同理2,3也发生了切片,调用函数该指针只能访问基类继承下来的成员而不是重定义的20
运行结果:
那如果我们对B类修改一下,第一步就会打印出20,
因为父类继承下来的是10,在构造函数中,修改了父类继承下来的为20
菱形继承
单继承和多继承:
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况
菱形继承的问题(坑)
#include<iostream>
using namespace std;
class A
{
public:
int _a;
};
class B :public A
{
public:
int _b;
};
class C :public A
{
public:
int _c;
};
class D :public B, public C
{
int _d;
};
int main()
{
D d;
//d._a = 10; 二义性编译器不知道调用从B还是C中继承而来的_a
return 0;
}
d的类成员模型:
可以看到,D继承了来自从B来的_a,以及从C来的_a,造成了数据冗余,
当我们d._a访问的时候,编译器不知道访问哪个_a,又造成了二义性
如果通过下面方式解决:
虽然可以通过指定作用域来访问_a,解决了二义性,但是无法解决冗余性,一个类有同一成员变量有两份,这是不允许的
为了解决这个问题
提出了虚继承(跟虚函数没得关系)
#include<iostream>
using namespace std;
class A
{
public:
int _a;
};
class B :virtual public A
{
public:
int _b;
};
class C :virtual public A
{
public:
int _c;
};
class D :public B, public C
{
int _d;
};
int main()
{
D d;
d._a = 20;
cout << d._a << endl;
d.B::_a = 10;
cout << d.B::_a << endl;
return 0;
}
程序执行d._a=20后
从监视窗口看:
虚继承后,可以看到d内中的_a被同时赋值了,就不存在二义性,也没有了冗余相当于只有一份数据
C++编译器是如何解决虚继承的二义性和冗余的
通过监视窗口并不能真正的查看到真实的情况,编译器被处理过了
所以通过内存窗口查看
①:不是虚继承的情况
在普通菱形继承里面有两份_a的地址
②虚继承情况
_a放在了最下面
在虚继承,两个_a共用了一份地址,就解决了数据冗余和二义性
但B和C区域中多了两行数据,这一行刚好四个字节
很容易想到是指针,那它指向的是什么呢?
让我来带你研究
虚基表:
可以看到所指向位置的下一个分别为20,12
这分别对应B里面跟C里面的指针(虚基表指针)到公共基类_a的偏移量
那这有什么用呢?
D d;
B b=d;
C c=d;
像这样b,c发生切片,要找到公共的_a就需要通过这个偏移量。
第一行全部为零,是为多态虚表预留的偏移量(多态再介绍)