继承概述(为什么出现继承)
作为面向对象三大特性之一,继承的好处是可以减少重复的代码。比如,我们在建立一个比较大的网站时,网站中有很多页面,每个页面其实是有一些相同的东西的,比如标题栏,导航栏还是广告等等。如果我们在写每个页面时都把这些公共的部分重新写一遍,代码会非常累赘,我们可以把公共的部分封装为一个类(父类),后面的页面去继承这个类,这样公共部分的代码只需要写一遍,后面需要继承即可,便减少了许多代码。
继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中?
//父类(基类)
class Base {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//子类(派生类)
class Son: public Base {
public:
int m_D;
};
void test01() {
Son s1;
cout << sizeof(s1) << endl;
}
打印结果为16
这里主要是想验证父类中私有成员是否被子类继承下去了。打印结果说明答案是肯定的
进一步验证
打开visual studio Developer Command Prompt
首先定位到当前CPP文件的盘符
然后输入cl/d1 reportSingleClassLayout查看的类名 所属文件名
注意cl(这个是大写字母L的小写l)
d1(这个是数字1)
结果如下:
结论:父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到
继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后?
class Base {
public:
Base() {
cout << "父类构造函数" << endl;
}
~Base() {
cout << "父类析构函数" << endl;
}
};
class Son: public Base {
public:
Son() {
cout << "子类构造函数" << endl;
}
~Son() {
cout << "子类析构函数" << endl;
}
};
void test01() {
Son s1;//实例化一个对象
}
打印结果:
结论:继承中,实例化子类对象时,先调用父类构造函数,再调用子类构造函数,析构顺序与构造顺序是相反的
菱形继承问题以及解决方法
概念:
两个派生类(子类)继承同一个基类(父类),又有某个类同时继承这两个派生类(子类)这种继承称为菱形继承
假如现在有 A B C D四个类 以下就是菱形继承
带来的问题:
class A {
public:
int m_Age;
};
class B: public A {};
class C: public A {};
class D: public B, public C {};
void test01() {
D d1;
d1.B::m_Age = 10;
d1.C::m_Age = 12;
cout << d1.B::m_Age << endl;
cout << d1.C::m_Age << endl;
}
打印结果:这个很好理解,同名的成员变量 想要输出的话加作用域区分即可
但是问题是如果我们想打印输出D类中的数据 呢
d1.m_Age //因为d1是D类实例化得来的
//所以想要输出同名成员变量直接输出即可
//无需加作用域区分
它会报错,因为D类继承了B,C 类,也就是说它继承 了A类的m_Age继承了两份。那么就会出现二义性问题。它不知道是输出B类的还是C类的。也就是说,我们只需要一份数据即可,不能同时要两份。因此,菱形继承带来的问题是子类继承了两份相同的数据,导致了资源浪费以及毫无意义
解决办法(利用虚继承):
利用虚继承可以解决菱形继承的问题。语法很简单`
//继承前加上virtual关键字就变为了虚继承
//此时它们的公共父类A就变为了虚基类(虚父类)
class B: virtual public A {};
class C: virtual public A {};
class D: public B, public C {};
day根据打印结果 显然虚继承之后 m_Age这个数据只有一份了。继承下来的也只有一份。以最近一次赋值为值
原理剖析:
运用命令提示符查看D类的对象模型:
结果如下:
这是没有用到虚继承时D类的对象模型 很显然它继承了两份相同的数据,所以会出现二义性
分析之前先解释两个名词:
vbptr(虚基类指针):
v:virtual(虚拟)
b:base(基类)
ptr:pointer(指针)
它指向的地址是一个虚基类表
vbtable(虚基类表) :table是表格的意思
里面记录了偏移量
分析如下:
显然,当是虚继承时,数据只有一份了,它继承于A类.D类在继承B C类时,是继承了两个指针.它们对应的有相应的偏移量.比如B类vbptr初值为0它会找到对应的虚基类表,虚基类表中记录的偏移量是8 那么0 + 8 = 8 就会对应从A类继承来的m_Age,C类同理,vbptr初值是4,偏移量是4,4 + 4 = 8 它也能对应地找到m_Age的地址.