C/C++相关概念和易错知识点(40)(赋值兼容转换、多继承、继承与组合)

电磁兼容标准体系解析:从基础到认证的完整指南 本文系统解析了电磁兼容(EMC)标准体系,从基础概念到认证流程提供完整指南。文章阐述了EMC对于产品可靠性与市场准入的重要性,详细拆解了基础标准、通用标准、产品类标准及认证流程四层结构,并结合5G设备与工业控制等实战案例,为工程师提供了从设计初期到认证测试的高效应用策略。 阅读详情

1.赋值兼容转换

赋值兼容转换有一点易混,先看一下下面的代码,想想a、b、c对象里面存的什么,顺便结合以前的知识,对继承加深理解。


#include <iostream>
using namespace std;

class A
{
protected:
	A(int a)
		:_a(a)
	{}
	int _a;
};

class B : public A
{
public:
	B(int a, int b)
		:A(a)
		,_b(b)
	{}

	int _b;
};

int main()
{
	B b(1, 2);
	A a = b;
	A c(0);

	return 0;
}

结果如下,其中A c(0)操作不合语法,报错

(1)为什么A c(0)报错?

A的构造函数是protected,对外和private没有任何区别,都是无法访问的,因此在main函数里创建对象无论如何都是不会成功的,因为调不到private构造函数,也不会生成一个默认构造(因为已经有了一个构造函数)

(2)为什么B b(1, 2)操作可行?

B以public继承A的protected函数意味着B能访问A的protected函数,该函数在B中的访问限制为protected,所以在B中是可以调用A的构造函数的,能完成变量的初始化。这里有点难理解,属于继承中比较绕的部分了

(3)A a = b是什么操作?

这涉及一个新的概念:赋值兼容转换。这是子类给父类赋值的一种操作。在这里b是子类对象,a是父类对象。子类可给父类赋值,相当于将子类中的父类继承给它的那部分数据赋值给它。我们还可以使用父类指针或引用,指针就是指向子类的父类部分,当修改父类指针的指向内容时,子类的内容也被修改了。引用也是指向子类的父类部分,相当于给子类的父类部分取别名。

赋值兼容转换类似于将子类切出一部分给父类,所以这个过程又叫切割或切片。

(4)区分赋值兼容转换和隐式类型转换

最本质的区别在于两者的底层操作不同。对于赋值兼容转换来说,无论是赋值、指针、引用,都不会在中间生成临时变量,赋值就是截断后将子类数据拷贝给父类,指针和引用都是直接让父类指向子类对应部分的。而变量隐式类型转换会先用一个临时变量存储类型提升、截断后的数据,再将这个数据赋值回去,这个临时变量的有无是区分两者的关键。

对于赋值兼容转换来说,没有临时变量意味着当父类引用时不会出现权限缩小的情况,即我们可以通过父类引用修改子类的值。

而对于其他变量来说,只要左右数据类型不同就会发生隐式类型转换,中间会产生临时变量,这个变量是常属性的,会导致必须加const才能引用

(5)赋值兼容转换注意事项

a.赋值兼容转换是一个单独的语法规则,和隐式类型转换没有任何关系,前面已经很充分的分析了

b.赋值兼容转换只能在public继承方式下可用

只要继承不是public,无论访问限定符是什么,都不行

c.赋值兼容转换可以跨级

但必须保证子赋值给父,中间继承方式只能是public

2.拷贝构造

看一下下面的代码,尝试理解继承下的拷贝构造



#include <iostream>
using namespace std;

class A
{
public:
	A() = default;
	A(const A& a)
		:_a(new int[10])
	{
		for (int count = 0; count < 10; count++)
		{
			_a[count] = a._a[count];
		}
	}

	int* _a = new int[10] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
};

class B : public A
{
public:
	B() = default;

	B(const B& b)
		:A(b)
		,_b(new int[10])
	{
		for (int count = 0; count < 10; count++)
		{
			_b[count] = b._b[count];
		}
	}

	int* _b = new int[10];
};

int main()
{
	B b1;
	B b2 = b1;
	b1._a[0] = 1, b2._a[0] = 2;
	b1._b[0] = 3, b2._b[0] = 4;

	return 0;
}

继承下的六大成员函数都和普通类的成员函数大差不差,只要不显式写,都是默认调用父类的对应的默认成员函数,所以我们需要注意的就是当遇到深拷贝时要会显式地去写拷贝构造

其中特别注意子类的拷贝构造利用了赋值兼容转换,需要着重理解

3.多继承

(1)多继承理解

C++支持多继承,即一个子类继承多个父类,享有多个父类的成员函数和成员变量。在语法上只需要用逗号隔开,分别写继承方式和继承的父类,在特性上和单继承基本上没区别,前面理解了这里就非常简单。

(2)菱形继承

这是在谈到多继承时不得不面对的问题——菱形继承。我们刚刚理解多继承觉得没啥问题,不就是单继承的翻版吗?但下面这种情况就有问题了。

我们会发现,BCD都没有问题,但E就有问题了,E通过两条继承关系链继承了两次A,意味着E里面有两份A的成员变量,这是单继承永远碰不到的情况,这叫数据冗余。

在数据冗余的前提下,E想要初始化或者访问A继承下来的成员变量时,也不知道到底要访问哪一份,这叫做二义性。

(3)二义性

观察下面的代码,看看如何消除二义性。

#include <iostream>
using namespace std;

class A
{
public: 
	int _a;
};

class B : public A
{};

class C : public B
{};

class D : public A
{};

class E : public C, public D
{};

int main()
{
	E e;
	e.D::_a = 1, e.C::_a = 2;
	cout << e.D::_a << " " << e.B::_a << endl;
	return 0;
}

执行结果

我们发现,只有E会产生二义性,而BC和D分别对应的A是唯一的,由于E同时继承了BCD,我们只需要在BCD中访问_a就可以避免歧义,即确定类域。

(4)数据冗余和虚拟继承

二义性解决了,但数据冗余仍然存在。事实上,我们发现当B和D出现的时候,数据冗余就已经无法避免了

所以源头是在B和D身上,我们是否可以使用一种办法,让B和D中的A实际上只有一份呢,这就要使用到virtual,即虚拟继承。

我们先看看在刚才代码的基础上给B和D使用虚拟继承后会发生什么?


#include <iostream>
using namespace std;

class A
{
public: 
	int _a;
};

class B : virtual public A
{};

class C : public B
{};

class D : virtual public A
{};

class E : public C, public D
{};

int main()
{
	E e;
	e.D::_a = 1, e.C::_a = 2;
	cout << e.D::_a << " " << e.B::_a << endl;
	return 0;
}

结果是

可以看到,当使用虚拟继承后,BD共享一个A,即整个继承体系中只有一份A,这就避免了数据冗余的情况。

在实际情况中,我们应当慎重使用多继承,一定要提防菱形继承,尽量不要使用,就算我们有菱形虚拟继承来解决这个问题。在很多语言中都没有多继承这个概念了,就是因为菱形继承带来的很多冗杂的语法处理和理解难度。

4.继承与组合

(1)继承和组合的区分和选择问题

继承上面已经讲了很多,不再重复。组合就是在一个类里使用另一个类作为它的成员变量,这种写法之前我们见过很多次了,在很多语法讲解上都使用了组合。我们现在需要思考:组合和继承的区别是什么?在两者之间怎样选择?

组合和继承的本质都是复用。继承语法中父子的关系通常是:子是父,子是父属性的一种延伸;

而组合通常是:A的组成中含有B,A的组成中含有C。继承是is关系,组合是has关系。

在选择过程中,如果两者表现出强is关系,那就使用继承,如植物和水果,动物与人。如果表现出强has关系,就使用组合,如汽车和汽油,自行车和轮胎。如果呈现两者均可的关系(如stack含有vector,stack就是vector),那么就优先使用组合。

(2)黑箱复用和白箱复用

继承作为面向对象编程三大特性之一,为什么反而推荐优先使用组合呢?这需要涉及黑箱白箱(黑盒白盒)的概念。

继承本质上是一种白箱复用,而组合是黑箱复用。

黑箱是不可见的,就像当我们使用组合时,被使用的类的细节我们是不知道的,但我们会用,如STL很少有人能讲清楚里面的细节。当使用黑箱复用时,我们关注的是这个类的功能,有什么接口,这是非常容易学习的。不仅如此,黑箱复用还将封装的特性完美展示了出来,我们只能调用该类向外展示的函数接口,其余细节完全不知道,因此如果这个类出现了毛病,有人去维护,它们只需要修bug就行了,只需要保证对外提供的接口是安全的,功能是正常的就可以了。

白箱复用是可见的,就像当我们使用继承时,父类除private以外其余的成员我们都可以直接调用。我们书写的代码是嵌在父类上的,这种模式下封装的特性就明显被弱化了。父类中的protected成员对外不开放但对子开放,就会导致当父类出现毛病时,有人去修改时,如果动了protected成员函数,那么子类的代码会立马失效,而一般来讲,protected成员更倾向于类功能的底层,函数更多,父类的书写者不可能去考虑子类调用的情况来适配,这对于书写子类功能的人来说无疑是一场灾难。这也是高内聚(函数功能集中),低耦合(类与类之间的关系不紧密)的反面教材。

白盒测试和黑盒测试也遵循上面的规则。黑盒测试注重对功能,接口的测试;而白盒测试注重于观察底层代码细节,从源码入手测试。白盒测试相比较黑盒测试更轻松。

综上,我们可以充分理解为什么推荐组合而不是继承。总的来说继承是以牺牲一定封装特性换来的,而随着我们深入学习,我们会意识到,封装才是编程思想的老大,任何破坏封装特性的行为都要深思熟虑。当然,如果确实存在强is关系时,继承依然可靠。

【Java基础知识 1】Java入门级概述 优快云最强Java专栏,包含全部Java基础知识点、Java8新特性、Java集合、Java多线程、Java代码实例,理论结合实战,实现Java的轻松学习。 阅读详情

相关推荐

多尺度变换(Multidimensional Scaling ,MDS)详解

介绍多尺度变换基本原理

5216

C++ 对象间的继承关系(赋值兼容规则)

C++ 通过类派生 的机制来支持继承。被继承的类称为基类 ,新产生的类为派生类 或子类,基类和派生类的集合 称作类继承层次结构。

qq_63282188的博客 2179

DISM错误87详解:RestoreHealth选项无法识别的修复方法

DISM(部署映像服务和管理工具)是Windows系统维护与修复的核心命令行工具,常与SFC(系统文件检查器)配合使用,用于扫描和修复组件存储损坏、系统文件缺失等问题。在执行系统健康检查时,用户常会遭遇错误87,提示“未在此上下文中识别出restorehealth选项”。该错误的本质并非工具损坏,而是命令参数结构不符合DISM的语法解析规则,或当前系统环境(如Windows 7、WinRE/PE、32位环境)不支持该参数。理解DISM的命令层级、参数挂载关系以及运行上下文,是精准排除故障的前提。通过核对标准

weixin_34116482的博客 484

多态性:虚函数

先来讲讲赋值兼容规则。        前面说过,派生类如果是从基类公有继承的,则它会包含基类中除构造函数和析构函数外的所有成员,基类的公有成员也成为派生类的公有成员,又因为对象只能访问类的公有成员,所以基类对象具有的功能,派生类对象都有。这样就引出了赋值兼容规则。        赋值兼容规则就是指在基类对象可以使用的地方都可以用公有派生类对象来代替。注意必须是公有派生类。赋值兼容规则中

chunlanse2014的专栏 710

SQL2000在Win10上安装完整指南:兼容模式、SP4补丁与远程连接配置

数据库兼容性是老系统迁移与运维中绕不开的难题。当历史遗留的SQL Server 2000遭遇全新的Windows 10环境,安装程序常因系统版本检测失败而拒绝运行,这背后是旧版安装程序与NT内核版本间的识别错位。通过兼容模式模拟旧系统环境、以管理员身份运行安装程序,并处理注册表挂起与杀毒软件干扰,即可突破基础安装障碍。安装后的SP4补丁与TCP/IP端口配置,则是保障服务稳定与远程连接的关键。这一技术路径广泛适用于制造企业MES、进销存系统及医院HIS等仍依赖老旧数据库的场景,为IT运维与数据迁移测试提供了

weixin_29266679的博客 443

vivado2020.2安装教程:Windows平台完整指南

手把手带你完成vivado2020.2安装教程,覆盖下载、配置到验证全过程,解决常见安装问题,确保在Windows平台顺利部署开发环境,适合初学者快速上手。

weixin_42607969的博客 1345

【自学C++】C++变量赋值

变量 的赋值就是给已经定义的变量进行重新设置值的过程,C++ 中变量的赋值可以单独为单个变量赋值,也可以同时为多个变量赋值。 参数 参数 描述 varname1 需要赋值的变量。 value1 需要给变量设置的值。 说明 给变量赋值的前提就是变量的已经定义了。 参数 参数 描述 varname1, varname2, varname3 需要赋值的变量。 value1, value2, value3 需要给变量设置的值。 说明 多个变量同时赋值时,多个变量之间使

haicoder_ibai的博客 5015

WhatsApp注册流程逆向分析:从协议解析到风控对抗实战

移动应用安全分析的核心在于理解客户端与服务端的交互协议与验证机制。通过逆向工程,可以深入剖析应用的数据加密、传输与身份验证原理,这对于构建安全模型、进行自动化测试和风控策略研究具有重要技术价值。在即时通讯、金融支付等高安全要求场景中,掌握应用注册流程的底层实现是评估其安全性的关键。本文聚焦于使用动态分析工具如Frida进行Hook和流量抓包,结合设备指纹识别与模拟器检测技术,详细解析了大型应用的注册风控体系,为安全研究人员和开发者提供了从环境搭建到实战对抗的完整方法。

weixin_33935636的博客 317

赋值兼容规则

赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员,而且所有成员的访问控制属性也和基类完全相同。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。赋值兼容规则中所指的替代包括以下的情况:    ·派生类的对象可以赋值给基类对象。   ·派生类的对象可以初始化基类的引用

williamzhang1111的专栏 3599

解决Google Play签名问题:Android App Bundle上传指南

摘要:本文分享了解决Android App Bundle上传至Google Play时遇到的签名问题的经验。当使用pepk.jar工具加密私钥时,出现"NoSuchAlgorithmException"错误。解决方案包括:确保使用JDK 11+版本、更新pepk.jar工具、验证密钥库信息,并提供详细命令行示例。通过升级开发环境和正确配置签名参数,最终成功生成加密密钥文件。文章为开发者提供了实用的技术参考,强调耐心排查问题的重要性。(149字)

m0_62153576的博客 146

Python全栈入门到实战【基础篇 05】核心基础:Python的2种运行模式(交互式+命令行式)

Python代码运行模式详解:交互式与命令行式对比 摘要: Python提供两种核心运行模式:交互式(REPL)和命令行式(脚本式)。交互式模式适合快速测试语法,即时反馈结果,通过终端输入python启动,特点是无需保存、临时执行,适合验证小段代码逻辑。命令行式模式需将代码保存为.py文件后运行,适合开发完整程序,可重复执行和修改。两种模式各有优势:交互式便于学习调试,命令行式适合实际开发。掌握两种模式的使用场景和切换方法,是Python入门的关键技能,能显著提升学习效率。

那些看似不起波澜的日复一日,会在某天让你看到坚持的意义 4561

(34.2)赋值兼容规则

文章目录1.赋值兼容规则 1.赋值兼容规则 赋值兼容规则是指:在需要基类对象的任何地方,都可以使用公有派生类的对象来代替 通过公有继承,派生类得到了基类中除构造函数,析构函数之外的所有成员。这样,共有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决 赋值兼容规则中所指的替代包括以下的情况: (1)派生类的对象可以赋值给基类对象 (2)派生类的对象可以初始化基...

喜欢打篮球的普通人 1726

IoT安全软件:Forescout二次开发_Forescout基础架构与原理

Forescout平台是一个集成了多种安全功能的综合解决方案,旨在帮助组织管理和保护其网络中的IoT设备。该平台通过自动发现、分类和监控网络中的设备,提供实时的安全可见性和控制。Forescout的核心优势在于其能够处理混合环境中的各种设备,包括传统IT设备、IoT设备和OT设备。Forescout平台由多个核心组件组成,每个组件负责特定的功能。了解这些组件的运作机制是进行二次开发的基础。假设我们需要定义一个策略,禁止所有厂商为XYZ的设备访问互联网。选择策略类型。

chenlz2007的博客 1965

Item 40:明智地使用多继承 Effective C++笔记

Item 40: Use multiple inheritance judiciously. 多继承(Multiple Inheritance,MI)是C++特有的概念,在是否应使用多继承的问题上始终争论不断。一派认为单继承(Single Inheritance,SI)是好的,所以多继承更好; 另一派认为多继承带来的麻烦更多,应该避免多继承。本文的目的便是了解这两派的视角。具体从如下三个

分享技术,共建开放平台 825

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Effective C++ 读书笔记 Item40 明智而审慎地使用多重继承

使用多继承时, 一个问题是不同基类可能具有相同名称,产生歧义(即使一个名字可访问,另一个不可访问) 多继承(Multiple Inheritance,MI)是C++特有的概念,在是否应使用多继承的问题上始终争论不断。一派认为单继承(Single Inheritance,SI)是好的,所以多继承更好; 另一派认为多继承带来的麻烦更多,应该避免多继承。本文的目的便是了解这两派的视角。具体从如下三个方面...

Vermont_的博客 279

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

C++入门基础知识61——【关于C++继承】

关于C++继承 基类 & 派生类 访问控制和继承 继承类型 多继承

zhaoylzy的博客 1889

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

C++中组合和继承的概念及意义

1,继承在面向对象中具有举足轻重的地位,面向对象当中的很多高级技术都和继承是息息相关的,比如面向对象的高端课程《设计模式》中的每一种技术都和继承有关,因此我们非常有必要在学习 C++ 时,把继承好好的掌握;通过生活中的例子,来入手面向对象中的概念,绝对是一个很好的选择,因为面向对象理 论起源于生活; 2,生活中的继承是一种关系,发生在两个实体之间,最直接的例子是儿子继承了父...

weixin_30737433的博客 167

Item 40:明智地使用多继承

Item 40: Use multiple inheritance judiciously. 多继承(Multiple Inheritance,MI)是C++特有的概念,在是否应使用多继承的问题上始终争论不断。一派认为单继承(Single Inheritance,SI)是好的,所以多继承更好; 另一派认为多继承带来的麻烦更多,应该避免多继承。本文的目的便是了解这两派的视角。具体从如下三个方面来介绍:

DoronLee的博客 437

嵌入式C/C++语言相关知识——C++八股

参考该博客:https://blog.youkuaiyun.com/songbijian/article/details/132507421

why1249777255的博客 1700

c 语言中的菱形继承,一文读懂C++中的继承之菱形继承(案例分析)

前言我们上一篇说了世间万物都有一个继承体制,或多或少子类继承了父类的某些特征,但大多都是单向继承,但是就有些特例他就是多继承,比如:我们从图片中就可以看到,两栖动物它既继承了水生动物的一部分特性,也继承了陆地动物的一些特性,那么我们的代码,会不会也会有这种多继承现象呢,我们一起来看一下。提示:以下是本篇文章正文内容,下面案例可供参考一、什么是多继承?1.单继承我们来看一个图先了解一下单继承,再看有...

weixin_28867333的博客 354

史上最全C/C++面试题集锦

select、poll、epoll都是IO多路复用的一种机制,可以监视多个文件描述符,一旦某个文件描述符进入读或写就绪状态,就能够通知系统进行相应的读写操作。Select优点:可移植性好,因为在某些Unix系统中并不支持poll和epoll对于超时时间提供了更好的精度:微妙,而poll和epoll都是毫秒级Select缺点:支持监听的文件描述符fd的数量有限制,最大数量默认是1024个。

红军不怕远征难,万水千山只等闲 1万+

NDK13_C++基础:继承与多态

NDK开发汇总 一 继承 1 一个最简单的继承案例 构造函数与析构函数的调用顺序 子类对象调用父类的成员 定义父类: plane.h #pragma once #include<string> class Plane { public: Plane(); Plane(std::string name,int year); ~Plane(); void fly(); v...

baopengjian的专栏 417

C++之虚函数与虚继承详解

虚继承和虚函数是完全无相关的两个概念。 虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题: 其一,浪费存储空间; 第二,存在二义性问题,通常可以将派生类对象的地址赋值给基类对象,实现的具体方式是,将基类指针指向继承类(继承类有基类的拷贝)中的基类对象的地址,但是多重继承可能存在一个基类的多份拷贝,这就出现了二义性。 虚继承可以解决多种继承前面提到的两个问题: 虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继

居老师的博客 2080

【PTA/C++】继承与派生

定义基类Point(点)和派生类Circle(圆),求圆的周长。Point类有两个私有的数据成员float x,y;Circle类新增一个私有的数据成员半径float r和一个公有的求周长的函数getCircumference();主函数已经给出,请编写Point和Circle类。//请编写你的代码int main()return 0;输入格式:输入圆心和半径,x y r中间用空格分隔。输出格式:输出圆的周长,小数点后保留2位有效数字。1 2 3输出样例:在这里给出相应的输出。

2403_88108595的博客 1508

C与C++的常错小细节

C与C++的常错小细节 1.fork()系统调用是Unix下以自身进程创建子进程的系统调用,一次调用,两次返回。如果返回值是0,则表示是子进程,如果返回值>0,则表示是父进程。 在fork()调用处整个父进程空间会原模原样地复制到子进程中,包括指令、变量值、程序调用栈、环境变量、缓冲区等。 2.const char *node="ABC";    指向字符常量的指针     *node的值不

agate_zhang的博客 533

C/C++ 中的类型转换方式

在进行类型转换时,需要谨慎考虑转换的必要性和合理性,以避免出现上述问题。尽量遵循类型安全的原则,使用合适的类型转换方式,并对可能出现的问题进行充分的测试和验证。

2301_77891039的博客 1082
上一篇: C/C++相关概念和易错知识点(39)(继承规则、继承下的构造和析构、函数隐藏)
下一篇: C/C++相关概念和易错知识点(41)(虚函数、协变、析构函数的重写)
-SGlow-
博客等级 码龄3年 1513粉丝 118原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值