当前位置: 首页 > article >正文

C++之 继承 (inheritance)

目录

启示

一、基本语法

二、继承的方式

三种:

公共基础 / 保护继承 / 私有继承

三、继承中的对象模型

①父类中所有非静态成员属性都会继承给子类

②而私有成员属性同样继承过去,但是被编译器隐藏,因此无法访问

四、继承中构造和析构顺序

构造的顺序:      父类 > 子类

析构的顺序:        子类 > 父类

五、继承同名成员处理方式

六、继承同名静态成员处理方式

七、多继承

八、菱形继承


启示

首先,对于一个学校,他有自己的学院/部门,学院下有自己的专业,如图

继承就是承接上一级的内容,同时延续自己下一级的内容


一、基本语法

class 子类 : 继承方式 父类
{}

子类又称派生类 ,体现了个性

父类又称基类,体现了共性

继承方式有public/private/protected


二、继承的方式

三种:

公共基础 / 保护继承 / 私有继承

他们有如下关系

 显而易见的,父类中的private,无论是哪种继承方式,子类都不可访问

接下来我们使用代码进行测试


首先是public公共继承

class B :public A
{
public:
	void func()
	{
		a = 1;// 父类中公共权限成员,到子类中仍然是公共权限成员
		b = 2;// 父类中保护权限成员,到子类中仍然是保护权限成员
		c = 3;// 父类中私有权限成员,不可访问
	}
};

 c不可访问 

 同时,带上测试函数

 因为b是保护权限成员,只能在类内访问,类外不可访问


然后是protected保护继承

class B :protected A // 保护继承
{
public:
	void func()
	{
		a = 1;// 父类中公共权限成员,到子类中变成是保护权限成员
		b = 2;// 父类中保护权限成员,到子类中仍然是保护权限成员
		c = 3;// 父类中私有权限成员,不可访问
	}
};

 c不可访问

接下来是测试函数

可以看到,由于a/b是保护权限成员,因此类外都不能访问 


最后是private私有继承

class B :private A // 私有继承
{
public:
	void func()
	{
		a = 1;// 父类中公共权限成员,到子类中变成是私有权限成员
		b = 2;// 父类中保护权限成员,到子类中仍然是私有权限成员
		c = 3;// 父类中私有权限成员,不可访问
	}

c不可访问 

测试函数:

 同样的,由于a/b是私有权限成员,因此类外都不能访问 


三、继承中的对象模型

①父类中所有非静态成员属性都会继承给子类

②而私有成员属性同样继承过去,但是被编译器隐藏,因此无法访问

class Base // 父类
{
public:
	int a;
protected:
	int b;
private:
	int c;
};
class Son :public Base
{
public:
	int d;
};
void test01()
{
	cout << sizeof(Son) << endl;
}


接下来我们使用vs的开发人员命令提示符工具展示真实的分布情况

①打开 vs2022的开发人员命令提示符工具

② 跳转盘符:输入F:,点击回车

③再接着输入 cd 具体路径, 点击回车

 ④接下来输入 cl d1 reportSingleClassLayout子类名 文件名

文件名可以输入首字符,然后按一下Tab,就会自动补全

 可以看到子类Son父类Base,总Size是16。

子类包含父类的abc变量和自身的d


四、继承中构造和析构顺序

子类继承父类后,创建子类对象,也会调用父类的构造函数

那么构造和析构的顺序是什么?


下面创建父类Base子类Son,并添加各自的构造和析构函数

class Base // 父类
{
public:
	Base()
	{
		cout << "父类构造函数" << endl;
	}
	~Base()
	{
		cout << "父类析构函数" << endl;
	}
};
class Son :public Base // 子类
{
public:
	Son()
	{
		cout << "子类构造函数" << endl;
	}
	~Son()
	{
		cout << "子类析构函数" << endl;
	}
};
void test01()
{
	Son s;
}

 因此,

构造的顺序:      父类 > 子类

析构的顺序:        子类 > 父类


五、继承同名成员处理方式

当父类和子类出现同名对象或函数

①访问子类同名成员,直接访问

②访问父类同名成员,加作用域(所有同名) 


 创建父类Base子类Son,并在2者种加入属性m_A,在测试函数test01中尝试输出m_A

class Base
{
public:
	Base()
	{
		m_A = 50; 
	}
	int m_A;
};
class Son :public Base
{
public:
	Son()
	{
		m_A = 100;
	}
	int m_A;
};

 直接输出,是子类同名成员

若要输出父类同名成员,要加上作用域


 同理,如果要输出同名成员函数,也是要加父类的作用域

class Base
{
public:
	Base()
	{
		m_A = 50; 
	}
	void func()
	{
		cout << "Base_func" << endl;
	}
	int m_A;
};
class Son :public Base
{
public:
	Son()
	{
		m_A = 100;
	}	
	void func()
	{
		cout << "Son_func" << endl;
	}
	int m_A;
};
void test02()
{
	Son s;
	s.func();
	s.Base::func();
}


 同时,如果子类出现了和父类的同名函数,子类将会隐藏掉父类中所有的同名函数,如下

class Base
{
public:
	Base()
	{
		m_A = 50; 
	}
	void func()
	{
		cout << "Base_func" << endl;
	}
	void func(int a) // 同名函数,而且重载
	{
		cout << "Base_fun(int a)" << endl;
	}
	int m_A;
};
class Son :public Base
{
public:
	Son()
	{
		m_A = 100;
	}	
	void func()
	{
		cout << "Son_func" << endl;
	}

	int m_A;
};

子类中有func函数,而父类也有func函数以及其重载函数func(int a

此时即使子类没有同名重载函数,也不能直接调用重载函数,因为被隐藏

需要加父类的作用域 

 


六、继承同名静态成员处理方式

与同名成员相同的处理方式

①访问子类同名成员,直接访问

②访问父类同名成员,加作用域(所有)


父类Base与子类Son,都有静态成员static m_a; 

class Base
{
public:
	static int m_a; // 类内声明
};
int Base::m_a = 10; // 类外初始化
class Son :public Base
{
public:

	static int m_a;
};
int Son::m_a = 5;

接下来使用2种方式访问:①通过对象访问②通过类名访问

void test01()
{
	// 通过对象访问
	Son s;
	cout << s.m_a << endl;
	cout << s.Base::m_a << endl;

	// 通过类名访问
	cout << Son::m_a << endl;
	cout << Base::m_a << endl;
	cout << Son::Base::m_a << endl;
}

 值得注意的是,Son::Base::m_a ,意思是,

Son::通过类名访问Base::作用域下的m_a数据

而Base::m_a是直接通过类名访问m_a


同样的,如果要访问静态成员函数,也是加作用域或不加作用域

class Base
{
public:
	static int m_a;
	static void func()
	{
		cout << "Base的静态成员函数" << endl;
	}
};
int Base::m_a = 10;
class Son :public Base
{
public:

	static int m_a;
	static void func()
	{
		cout << "Son的静态成员函数" << endl;
	}
};
int Son::m_a = 5;
void test02()
{
	// 通过对象访问
	Son s;
	s.func();
	s.Base::func();

	// 通过类名访问
	Son::func();
	Base::func();
	Son::Base::func();
}


 同理,如果父类中出现了同名静态成员函数的重载,也会被隐藏,在调用时必须要加父类的作用域


七、多继承

语法:

class 子类: 继承方式 父类1,继承方式 父类 2......

不建议使用


例:

class Base1 // 父类1
{
public:
	Base1()
	{
		m_a = 100;
	}
	int m_a;
};
class Base2 // 父类2
{
public:
	Base2()
	{
		m_b = 200;
	}
	int m_b;
};
//class 子类: 继承方式 父类1,继承方式 父类 2......
class Son :public Base1, public Base2
{
public:
	Son()
	{
		m_c = 300;
		m_d = 400;
	}
	int m_c;
	int m_d;
};

使用Son类创建对象,并查看其大小

void test01()
{
	Son s;
	cout << sizeof(s) << endl;
}

4个int,大小16

 使用开发人员命令提示符也可以看到包含父类1和父类2的两个int,以及自己的2个int,一共是16


而当父类中出现同名成员时,不可以直接输出 

class Base1 // 父类1
{
public:
	Base1()
	{
		m_a = 100;
	}
	int m_a;
};
class Base2 // 父类2
{
public:
	Base2()
	{
		m_a = 200;
	}
	int m_a;
};
//class 子类: 继承方式 父类1,继承方式 父类 2......
class Son :public Base1, public Base2
{
public:
	Son()
	{
		m_c = 300;
		m_d = 400;
	}
	int m_c;
	int m_d;
};

可以看到,父类1和父类2都有m_a

尝试输出时,会提示不明确,因为重名了

因此,需要加作用域


八、菱形继承

 

 ①传电动汽车继承了汽车的price,纯汽油汽车同样继承了汽车的price,当混合动力汽车使用公共属性price时,就会产生二义性

②混合动力汽车继承自汽车的属性price继承了2份,造成额外开销,只需一份即可

代码:

class car // 父类 车类
{
public:
	int price;
};

class pure_gasoline_car:public car // 纯汽油汽车
{};
class pure_electric_vehicle:public car // 纯电动汽车
{};
class hybrid_electric_vehicle :public pure_gasoline_car, public pure_electric_vehicle //混合动力汽车
{};

而在测试函数中,不可直接调用任何一个的price

void test01()
{
	hybrid_electric_vehicle c;
	c.price = 10;
}

必须要加上作用域 

void test01()
{
	hybrid_electric_vehicle c;
	c.pure_gasoline_car::price = 10;
	c.pure_electric_vehicle::price = 20;

	cout << c.pure_gasoline_car::price << endl;
	cout << c.pure_electric_vehicle::price << endl;
}

打印输出

但是,菱形继承导致数据有2份,造成资源浪费

使用开发人员命令提示符工具查看

 

确实会有2份

接下来,使用虚继承解决 

	//				在public前加上virtua,变为虚继承
class pure_gasoline_car:virtual public car // 纯汽油汽车
{};
class pure_electric_vehicle:virtual public car // 纯电动汽车
{};

在2个子类的继承方式前加上virtual,即变成虚继承

此时,原父类/基类称为虚基类

再次输出

可以发现 打印出了同样的数据,因为此时两个子类的price共用同一块空间

因此我们不再需要区分作用域,也可以直接打印出price

 使用开发人员命令提示符工具查看

 可以看到,pure_gasoline_car的vbptr(虚基类指针)指向它的虚基类表(下面红色),起始位置是0(左边红色),偏移量是8(下面虚基类表里写的8),因此指向8的位置

同理,pure_electric_vehicle的虚基类指针从4偏移4到8的位置,也指向8

因此,两个虚基类指针指向同一块空间


http://www.kler.cn/a/10028.html

相关文章:

  • 期权懂|期权新手入门教学:期权合约有哪些要素?
  • LLMs之PDF:zeroX(一款PDF到Markdown 的视觉模型转换工具)的简介、安装和使用方法、案例应用之详细攻略
  • JMeter基础篇
  • Sql server 备份还原方法
  • 实现3D热力图
  • vue2面试题6|[2024-11-11]
  • 【HDR图像处理】HDR图像的色调映射 | python+opencv代码实现总结
  • ASEMI代理ADA4940-1ACPZ-R7原装ADI车规级ADA4940-1ACPZ-R7
  • Zookeeper集群 + Fafka集群
  • Mysql数据库存储过程
  • 实现mini智能助理—模型训练
  • 五、手把手搭建K8S保姆级教程
  • Python 进阶指南(编程轻松进阶):十七、Python 风格 OOP:属性和魔术方法
  • __builtin_xxx指令学习【3】__builtin_popcount __builtin_popcountll
  • ROS开发之如何使用RPLidar A1二维激光雷达?
  • 基于DSP+FPGA的机载雷达伺服控制系统的硬件设计与开发(一)总体设计
  • VMware vSphere 8.0c - 企业级工作负载平台
  • 腾讯云GPU服务器NVIDIA P40 GPU、P4、T4和GPU自由卡详解
  • 如何测试物联网安全性
  • Linux命令·lsof
  • 数据库导入报错:1452,1832,1215 - Cannot add foreign key constraint 等错误
  • 电瓶隔离器工作原理与发展简史
  • 【财富管理转型】财富管理转型的本质
  • SpringMVC执行流程
  • ICLR 2023 | 达摩院开源轻量人脸检测DamoFD
  • 【MySQL】实验七 视图