几种继承方式
公有继承
父类 | 子类 | 孙类 |
public | public | public |
protected | protected | protected |
private | private | private |
- 基类成员——在派生类中的访问属性不变
- 派生类成员函数——可以访问基类的公有成员和受保护成员,不能访问私有成员恒源。
- 派生类以外的其他函数——可以通过派生类的对象访问基类的公有成员,但是不能访问受保护成员和私有成员。
私有继承
父类 | 子类 | 孙类 |
public | private | private |
protected | private | private |
private | private | private |
- 基类成员——在派生类中所有属性都变成private
- 派生类成员函数——可以访问基类的公有成员和受保护成员,不能访问基类的私有成员
- 派生类以外的其他函数——不能访问从基类继承的任何成员。
受保护继承
父类 | 子类 | 孙类 |
public | protected | protected |
protected | protected | protected |
private | private | private |
- 基类成员——公有成员和受保护成员在派生类中都变成受保护类型,基类的私有成员属性不变。
- 派生类成员函数——可以访问基类的公有成员和保护成员,不能访问基类的私有成员。
- 派生类以外的其他函数——不能访问从基类继承的任何成员。
继承中的对象模型
子类中会继承父类的私有成员,只是被编译给隐藏起来,访问不到私有成员
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Base{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//子类中 会继承父类的私有成员,只是被编译给隐藏起来,访问不到私有成员
class Son :public Base{
public:
int m_D;
};
void test01(){
cout << sizeof(Son) << endl;
}
int main(){
test01();
system("pause");
return EXIT_SUCCESS;
}
继承关系下的构造函数和析构函数调用顺序
- 子类会继承父类的成员属性,成员函数
- 但是 子类 不会继承 父类 构造函数 和 析构函数
- 只有父类自己知道如何构造和析构自己的属性,而子类不知道
如果父类声明了有参的构造函数,则系统不会再提供默认的无参构造函数。此时,子类的构造函数就无法写无参的了,因为调用不到父类的无参构造函数。
解决办法:利用初始化列表方式显式调用有参构造
class Base2{
public:
Base2(int a){
this->m_A = a;
cout << "有参构造函数调用" << endl;
}
int m_A;
};
class Son2:public Base2{
public:
Son2(int a ) : Base2(a)//利用初始化列表方式 显示调用 有参构造
{}
};
整个测试代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Base{
public:
Base(){
m_A = 10;
cout << "Base默认构造函数调用" << endl;
}
~Base(){
cout << "Base的析构函数调用" << endl;
}
int m_A;
};
// 子类会继承父类的成员属性,成员函数
//但是 子类 不会继承 父类 构造函数 和 析构函数
//只有父类自己知道如果构造和析构自己的属性,而子类不知道
class Son :public Base{
public:
Son(){
cout << "Son默认构造函数调用" << endl;
}
~Son(){
cout << "Son的析构函数调用" << endl;
}
};
void test01(){
//Base b1;
Son s1;
}
class Base2{
public:
Base2(int a){
this->m_A = a;
cout << "有参构造函数调用" << endl;
}
int m_A;
};
class Son2:public Base2{
public:
Son2(int a ) : Base2(a)//利用初始化列表方式 显示调用 有参构造
{}
};
void test02(){
Son2 s2(1000);
}
int main(){
test01();
system("pause");
return EXIT_SUCCESS;
}
继承中的同名成员处理
- 成员属性 直接调用先调用子类,如果想调用父类 需要作用域
- 成员函数 直接调用先调用子类,父类的所有版本都会被隐藏,除非显示用作用域运算符去调用
测试代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Base {
public:
Base() {
m_A = 100;
}
void fun() {
cout << "Base func调用" << endl;
}
void fun(int a) {
cout << "Base func (int a)调用" << endl;
}
int m_A;
};
class Son :public Base {
public:
Son() {
m_A = 200;
}
void fun() {
cout << "Son func调用" << endl;
}
int m_A;
};
void test01() {
Son s1;
//运行结果 200 就近原则
cout << s1.m_A << endl;
//想调用 父类中 的m_A
cout << s1.Base::m_A << endl;
s1.fun();
//调用父类的func
s1.Base::fun();
s1.Base::fun(10);
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
继承中静态成员的处理
静态成员属性 子类可以继承下来
测试代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Base{
public:
static void func(){
cout << "base fun()" << endl;
}
static void func(int a){
cout << "base fun(int)" << endl;
}
static int m_A;
};
int Base::m_A = 10;
class Son :public Base{
public:
static void func(){
cout << "son fun()" << endl;
}
static int m_A;
};
int Son::m_A = 20;
//静态成员属性 子类可以继承下来
void test01(){
cout << Son::m_A << endl;
//访问父类的m_A
cout << Base::m_A << endl;
Son::func();
//访问 父类中同名的函数
Son::Base::func(10);
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
多继承
多继承中很容易引发二义性。解决办法——作用域
测试代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Base1{
public:
Base1(){
m_A = 10;
}
int m_A;
};
class Base2{
public:
Base2(){
m_A = 20;
}
int m_A;
};
//多继承
class Son :public Base1, public Base2{
public:
int m_C;
int m_D;
};
//多继承中很容易引发二义性
void test01(){
cout << sizeof(Son) << endl;
Son s1;
//s1.m_A; //二义性
cout << s1.Base1::m_A << endl;
cout << s1.Base2::m_A << endl;
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
菱形继承和虚继承
菱形继承
这种继承带来的问题:
测试代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal{
public:
int m_Age;
};
//虚基类 Sheep
class Sheep :public Animal{};
//虚基类 Tuo
class Tuo :public Animal{};
class SheepTuo :public Sheep, public Tuo{
};
//菱形继承的解决方案 利用虚继承
//操作的是共享的一份数据
void test01(){
SheepTuo st;
st.Sheep::m_Age = 10;
st.Tuo::m_Age = 20;
cout << st.Sheep::m_Age << endl;
cout << st.Tuo::m_Age << endl;
cout << st.m_Age << endl; //可以直接访问,原因已经没有二义性的可能了,只有一份m_Age
}
int main() {
test01();
//test02();
system("pause");
return EXIT_SUCCESS;
}
test01()里面的最后一行会报错。以为二义性。
子类羊驼的内部结构:
菱形继承的解决方案——虚继承
虚继承
继承时,写上关键字virtual,就变成虚基类了。
测试代码如下:
#include "pch.h"
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal{
public:
int m_Age;
};
//虚基类 Sheep
class Sheep :virtual public Animal{};
//虚基类 Tuo
class Tuo :virtual public Animal{};
class SheepTuo :public Sheep, public Tuo{
};
//菱形继承的解决方案 利用虚继承
//操作的是共享的一份数据
void test01(){
SheepTuo st;
st.Sheep::m_Age = 10;
st.Tuo::m_Age = 20;
cout << st.Sheep::m_Age << endl;
cout << st.Tuo::m_Age << endl;
cout << st.m_Age << endl; //可以直接访问,原因已经没有二义性的可能了,只有一份m_Age
}
//通过地址 找到 偏移量
//内部工作原理
void test02(){
SheepTuo st;
st.m_Age = 100;
//找到Sheep的偏移量操作
cout<< *(int *)((int *)*(int *)&st + 1) << endl;
cout << *(int*)((int*)*(int *)&st + 1) << endl;
//找到Tuo的偏移量
cout << *((int *)((int *)*((int *)&st + 1) + 1)) << endl;
//输出Age
cout << ((Animal*)((char *)&st + *(int*)((int*)*(int *)&st + 1)))->m_Age << endl;
}
int main() {
test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
此时子类是内部结构:从结构中可以看出,m_Age参数只有一个了
子类内部有vbptr——虚基类指针,指向一张虚基类表,通过表找到偏移量,找到共有的资源
通过虚表获取值
根据下方代码和上方的图片,分析test02里面的三个偏移量是怎么写出来的、
class Animal{
public:
int m_Age;
};
//虚基类 Sheep
class Sheep :virtual public Animal{};
//虚基类 Tuo
class Tuo :virtual public Animal{};
class SheepTuo :public Sheep, public Tuo{};
void test02(){
SheepTuo st;
st.m_Age = 100;
//找到Sheep的偏移量操作
cout << *(int*)((int*)*(int *)&st + 1) << endl;
//找到Tuo的偏移量
cout << *((int *)((int *)*((int *)&st + 1) + 1)) << endl;
//输出Age
cout << ((Animal*)((char *)&st + *(int*)((int*)*(int *)&st + 1)))->m_Age << endl;
}
sheep的偏移量求解过程:
首先对于SheepTuo的对象st取地址,得到指向SheepTuo的指针,然后做一个强制转换来改变指针的步长,此时代码为:
(int *)&st
此时指针指向箭头所指的位置——即存储Sheep的区间::
之后对上侧代码取*,以此来找到Sheep的虚基类表。指针指向红色箭头所指的位置(下图为虚表截图)
*(int*)&st
在这张表中,指针的步长是多大呢?因此,前面还需要在家一个类型转换。此时的代码为:
(int*)*(int*)&st
之后给这个指针+1,使其指向红色箭头所示位置
之后再做一次类型转换,然后通过*运算符获取改地址上面存储的值——8
*(int*)((int*)*(int*)&st+1)
对于Tuo的偏移量的求解过程:
首先对于SheepTuo的对象st取地址,得到指向SheepTuo的指针,然后做一个强制转换来改变指针的步长,此时代码为:
(int *)&st
此时指针指向箭头所指的位置——即存储Sheep的区间:
之后对指针进行+1操作,此时指针指向箭头所指位置——即存储Tuo的区间::
此时代码为:
(int*)&st+1
之后对整体取*,则来到了Tuo的虚基类表:
*((int*)&st+1)
对于这个表,前面需要加个(int*)来设置此时的指针类型,此时指针指向箭头所指的位置——即Tuo的虚基类表:
此时代码为:
(int*)*((int*)&st+1)
然后对于指针整体+1,使其指向箭头所指的位置:
此时代码:
(int*)*((int*)&st+1)+1
然后给整体一个类型,
(int*)((int*)*((int*)&st+1)+1)
此时对于整体进行取*操作,得到了存储在此地址的值:
*(int*)((int*)*((int*)&st+1)+1)
通过偏移量输出m_Age的值
首先对于SheepTuo的对象st取地址,得到指向SheepTuo的指针,然后做一个强制转换来改变指针的步长(此时我们给其设置为char*类型,使其步长为1),此时代码为:
(char *)&st
通过上面,我们知道Sheep的偏移量是
*(int*)((int*)*(int*)&st+1)
将两个偏移量相加,则得到0+8(0的位置移动到8的位置)。此时,箭头所指方向为:
((char*)&st+*(int*)((int*)*(int*)&st+1))
通过上图可知,M_Age是Animal类型,因此要对整体进行类型转换。
(Animal*)((char*)&st+*(int*)((int*)*(int*)&st+1))
之后通过->来获取M_Age的值:
((Animal*)((char*)&st+*(int*)((int*)*(int*)&st+1)))->m_Age