【C++进阶学习】第二弹——继承(下)——挖掘继承深处的奥秘

继承(上):【C++进阶学习】第一弹——继承(上)——探索代码复用的乐趣-CSDN博客

前言:

在前面我们已经讲了继承的基础知识,让大家了解了一下继承是什么,但那些都不是重点,今天,我们一起来挖掘一下继承底层的一些知识和一些极容易出错的点

目录

一、隐藏

1.1 隐藏的概念

1.2 隐藏的两种类型

二、派生类的默认成员函数

三、继承与友元

四、继承与静态成员

五、总结


一、隐藏

1.1 隐藏的概念

在 C++ 中,继承是一种机制,使得子类可以继承父类的成员变量和成员函数。然而,当子类中出现和父类同名的成员变量或成员函数时,会发生一种特殊的现象,即隐藏

隐藏是指:如果子类中出现了与父类同名的成员变量或成员函数,则子类中的这个成员会“隐藏”父类中的同名成员,使得父类中的同名成员在子类中不可见。

1.2 隐藏的两种类型

具体来说,有以下两种情况:

成员变量隐藏:
如果子类中出现了和父类同名的成员变量,则子类中的这个成员变量会隐藏父类中的同名成员变量。例如:

class Parent {
public:
    int a;
};

class Child : public Parent {
public:
    int a; // 此处的 a 会隐藏 Parent 中的 a
};

int main() {
    Child c;
    c.a = 10; // 此处修改的是 Child 中的 a,而不是 Parent 中的 a
    return 0;
}

成员函数隐藏:
如果子类中出现了和父类同名的成员函数,则子类中的这个成员函数会隐藏父类中的同名成员函数。例如:

class Parent {
public:
    void func() {
        cout << "Parent::func()" << endl;
    }
};

class Child : public Parent {
public:
    void func() {
        cout << "Child::func()" << endl;
    }
};

int main() {
    Child c;
    c.func(); // 此处调用的是 Child 中的 func(),而不是 Parent 中的 func()
    return 0;
}

需要注意的是,虽然子类中的成员会隐藏父类中的同名成员,但是父类中的成员仍然存在,只是在子类中不可见。如果想在子类中访问父类中被隐藏的成员,可以使用作用域运算符(::)来显式地指明要访问的成员所在的类。例如:

class Parent {
public:
    int a;
};

class Child : public Parent {
public:
    int a;
};

int main() {
    Child c;
    c.a = 10; // 此处修改的是 Child 中的 a
    c.Parent::a = 20; // 此处修改的是 Parent 中的 a
    return 0;
}

总之,在 C++ 中的继承中,隐藏是一种特殊的机制,需要注意避免误用。

二、派生类的默认成员函数

在 C++ 中,当我们定义一个类时,可以省略掉其中的成员函数的实现,而直接在类定义的外部提供其实现。这种情况下,如果我们不提供任何实现,那么 C++ 编译器会自动为我们提供一个默认的构造函数、析构函数和拷贝构造函数和拷贝赋值运算符。

对于派生类来说,情况也是类似的。当我们定义一个派生类时,如果我们不提供任何构造函数,那么 C++ 编译器会自动为我们提供一个默认的构造函数,其构造函数的参数列表和父类的构造函数一致。例如:

class Parent {
public:
    Parent(int a) : m_a(a) {}
    int m_a;
};

class Child : public Parent {
public:
    Child() : Parent(0) {} // 此处的构造函数会自动调用 Parent 的构造函数,并传入 0 作为参数
};

同时,如果我们没有提供任何析构函数,那么 C++ 编译器也会自动为我们提供一个默认的析构函数,其析构函数的函数体为空。例如:

class Parent {
public:
    ~Parent() {}
};

class Child : public Parent {
public:
    ~Child() {} // 此处的析构函数会自动调用 Parent 的析构函数
};

需要注意的是,如果我们提供了任何一个构造函数或析构函数,那么 C++ 编译器就不会再为我们提供默认的构造函数或析构函数了。这时如果我们需要使用默认的构造函数或析构函数,需要我们自己显式地提供。(析构顺序为先派生类再基类)

另外,对于拷贝构造函数和拷贝赋值运算符来说,如果我们没有提供任何拷贝构造函数和拷贝赋值运算符,那么 C++ 编译器会自动为我们提供一个默认的拷贝构造函数和拷贝赋值运算符,其行为是浅拷贝(Shallow Copy),即直接拷贝成员变量的值。例如:

class Parent {
public:
    Parent(int a) : m_a(a) {}
    int m_a;
};

class Child : public Parent {
public:
    Child(int a, int b) : Parent(a), m_b(b) {}
    int m_b;
};

int main() {
    Child c1(1, 2);
    Child c2(c1); // 此处调用的是 Child 的默认拷贝构造函数,直接拷贝 m_a 和 m_b 的值
    return 0;
}

但是,如果我们的类中有指针类型的成员变量,那么默认的拷贝构造函数和拷贝赋值运算符就会出现问题,因为它们只会拷贝指针的值,而不会拷贝指针所指向的内存。这时我们需要自己提供一个拷贝构造函数和拷贝赋值运算符,实现深拷贝(Deep Copy)。例如:

class Parent
{
public:
	Parent(int a)
		:_a(a)
	{}
	int _a;
};
class Child :public Parent
{
public:
	int* _b;

	Child(int a, int b)
		:Parent(a),
		_b(new int(b))         //深度拷贝
	{}
	~Child()
	{
		delete _b;
		_b = nullptr;
	}
	Child(const Child& c)
		:Parent(c)
	{
		_b = new int(*c._b);
	}
	Child& operator=(const Child& c)
	{
		if (this != &c)
		{
			_a = c._a;
			delete _b;
			_b = new int(*(c._b));			
		}
		return *this;
	}
	void Print()
	{
		cout << "_a:" << _a << " " << "_b:" << *_b << endl;
	}
};
int main()
{
	Child c1(1, 2);     
	Child c2(c1);      //此处调用的是c2的深度拷贝
	c2.Print();

	Child c3 = c1;
	c3.Print();
	return 0;
}

运行结果:

我们通过两张图来总结一下:

三、继承与友元

在C++中,友元关系不能被继承,因为友元关系是独立于类定义的,并不是类的成员。因此,如果在父类中声明了一个友元函数或友元类,子类无法继承这种关系。

下面是一些相关知识点:

1、友元函数不能是成员函数:友元函数不是类的成员函数,因此不能使用this指针,也不能直接访问类的私有成员。需要通过类的对象或引用来访问私有成员。
2、友元关系不具有传递性:如果A类声明了B类为友元,则B类不会自动成为A类的友元。
友元函数可以是模板函数:模板函数可以被声明为类的友元,这样模板函数可以访问类的私有成员。
3、友元类:如果一个类声明另一个类为友元,则该友元类的所有成员函数都可以访问原类的私有成员。
4、友元不能继承:由于友元关系不是类的成员,因此不能被继承。如果在父类中声明了一个友元函数或友元类,子类无法继承这种关系。但子类可以在自己的范围内重新声明该友元关系。
示例:

#include<iostream>
#include<string>
using namespace std;
class Base {
public:	
	friend void friendFunction(Base&);
protected:
	int value;
};

class Derived : public Base {
public:
	// friendFunction 在 Derived 中不是友元函数,需要重新声明
	friend void friendFunction(Derived&);
};

void friendFunction(Base& base) {
	// 可以访问 Base 的私有成员
	base.value = 10;
}

void friendFunction(Derived& derived) {
	// 可以访问 Derived 的私有成员
	derived.value = 20;
}

int main() {
	Derived derived;
	friendFunction(derived);
	return 0;
}

在上面的示例中,由于友元关系不能被继承,因此在Derived类中需要重新声明friendFunction函数为友元函数,以便在Derived类的实例上调用该函数。

四、继承与静态成员

在 C++ 中,类可以包含静态成员变量和静态成员函数,其中静态成员变量属于类本身,而不是类的某个对象,因此它们可以在不创建类对象的情况下被访问。

当一个类继承另一个类时,子类可以继承其父类的静态成员,并且可以在子类中重新定义这些静态成员。在这种情况下,子类和父类各自拥有自己的静态成员变量,它们之间没有任何关系。

下面是一个简单的例子:

#include<iostream>
using namespace std;

class Parent {
public:
	static int a;
};

int Parent::a = 10;      //静态成员的定义只能在类外进行

class Child : public Parent {
public:
	static int a;      //类中只能声明静态成员
};

int Child::a = 20;       //静态成员的定义只能在类外进行

int main() {
	cout << Parent::a << endl;    //输出10
	cout << Child::a << endl;     //输出20
	return 0;
}

运行结果:

在上面的例子中,Parent 类和 Child 类都有一个静态成员变量 a,它们各自拥有自己的实现。在 main 函数中,我们可以直接通过类名来访问这些静态成员变量。

       需要注意的是,如果子类中没有重新定义父类的静态成员变量,那么子类可以直接访问父类的静态成员变量,例如 Parent::a。如果子类重新定义了父类的静态成员变量,那么子类只能访问自己的静态成员变量,例如 Child::a。

       此外,静态成员函数也可以继承,并且可以在子类中重新定义。在子类中重新定义父类的静态成员函数时,子类的静态成员函数会隐藏父类的静态成员函数,因此如果在子类中需要调用父类的静态成员函数,需要使用作用域运算符 :: 来显式地调用。

       还有一个需要注意的点就是,类中只能声明静态成员,静态成员的定义只能在类外进行。

总之,在 C++ 中,静态成员在继承中的行为与普通成员有所不同,需要注意其使用方法。

五、总结

以上就是C++继承中需要额外注意的点,此外,还有一个很重要的知识点我们还没讲到——多继承、菱形继承、虚拟继承,这几个知识点是有很大关联性的,且我们在平时使用继承时也很容易出错,鉴于篇幅问题,这几个问题会在下一篇单拎出来来讲,今天的内容就到此为止。

感谢各位大佬观看,创作不易,还请各位大佬一键三连!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/717836.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

企业社会责任认证:提升品牌价值的关键

社会责任认证&#xff08;Social Responsibility Certification&#xff09;是现代企业在经营过程中主动履行社会责任、尊重人权、保护环境等方面所获得的认证。这不仅是企业管理的要求&#xff0c;更是企业赢得社会信任和支持的关键。 社会责任认证是企业在经营过程中&#xf…

nvm 报错https://npm.taobao.org/mirrors/node/index.json 淘宝镜像更换

文章目录 一、问题背景二、解决问题1. 获取配置文件的位置2. 修改配置文件中的镜像源配置3. 修改 npm 镜像源 一、问题背景 使用nvm的时候报错: Could not retrieve https://npm.taobao.org/mirrors/node/index.json. 由于淘宝的镜像域名更换&#xff0c;npm.taobao.org 域名…

基于WPF技术的换热站智能监控系统15--实时读取PLC数据

1、创建PLC实时数据 1、添加数据块 2、创建6个变量 用来表示水泵1和水泵2的参数&#xff0c;可以根据现场实际情况添加更多的变量参数 3、设置块属性并编译 4、下载该程序到PLC中 5、添加监控表 2、读取设备数据 S7协议下的tcp直接通讯&#xff0c;配置简单&#xff0c;一般P…

浏览器必装插件推荐:最新版Simple Allow Copy,解除网页复制限制!

经常在网上找资料的朋友&#xff0c;尤其是学生党&#xff0c;总会遇到一个问题&#xff1a;很多资料网站的文字是禁止复制的。于是大家通常会使用各种文字识别软件来图文转换&#xff0c;或者直接手打。 今天这款小工具&#xff0c;可以轻松复制各种氪金网站上的任何文字&…

李沐:用随机梯度下降来优化人生!

大侠幸会&#xff0c;在下全网同名「算法金」 0 基础转 AI 上岸&#xff0c;多个算法赛 Top 「日更万日&#xff0c;让更多人享受智能乐趣」 今天我们来聊聊达叔 6 大核心算法之 —— 优化 算法。吴恩达&#xff1a;机器学习的六个核心算法&#xff01; 梯度下降优化算法是机器…

【数据结构与算法 刷题系列】求带环链表的入环节点(图文详解)

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法 经典例题》C语言 期待您的关注 ​ 目录 一、问题描述 二、解题思路 方法一&#xff1a;数学公式推导法 方法…

Kaggle比赛:成人人口收入分类

拿到数据首先查看数据信息和描述 import pandas as pd import seaborn as sns import matplotlib.pyplot as plt # 加载数据&#xff08;保留原路径&#xff0c;但在实际应用中建议使用相对路径或环境变量&#xff09; data pd.read_csv(r"C:\Users\11794\Desk…

超高清图像生成新SOTA!清华唐杰教授团队提出Inf-DiT:生成4096图像比UNet节省5倍内存。

清华大学唐杰教授团队最近在生成超高清图像方面的新工作&#xff1a;Inf-DiT&#xff0c;通过提出一种单向块注意力机制&#xff0c;能够在推理过程中自适应调整内存开销并处理全局依赖关系。基于此模块&#xff0c;该模型采用了 DiT 结构进行上采样&#xff0c;并开发了一种能…

持续学习的综述: 理论、方法与应用

摘要 为了应对现实世界的动态&#xff0c;智能系统需要在其整个生命周期中增量地获取、更新、积累和利用知识。这种能力被称为持续学习&#xff0c;为人工智能系统自适应发展提供了基础。从一般意义上讲&#xff0c;持续学习明显受到灾难性遗忘的限制&#xff0c;在这种情况下…

白酒:茅台镇白酒的酒厂社会责任与可持续发展

云仓酒庄豪迈白酒&#xff0c;作为茅台镇的品牌&#xff0c;不仅在产品品质和口感方面有着卓着的表现&#xff0c;在酒厂社会责任和可持续发展方面也做出了积极的探索和实践。 首先&#xff0c;云仓酒庄豪迈白酒注重环境保护和资源利用。酒厂在生产过程中严格控制能源消耗和排放…

使用 Nstbrowser 管理多个帐户 - 2024 年最佳反检测浏览器

每个人一定都看过那些房间里全是窃听器的老间谍电影&#xff0c;对吧&#xff1f;现在这些电影可能看起来有点好笑&#xff0c;但互联网并没有好到哪里去&#xff01; 事实上&#xff0c;每个你打开的页面在你浏览时都在被监控&#xff01;此外&#xff0c;当你管理多个账户时…

基于ChatGPT-4o自然科学研究全流程实践技术应用

自然科学研究遵循严谨的科学方法论&#xff0c;包括文献调研、问题综述、试验设计、提出假设、数据清洗、统计诊断、大数据分析、经典统计模型&#xff08;回归模型、混合效应模型、结构方程模型、Meta分析模型&#xff09;、参数优化、机器/深度学习、大尺度模型构建与模拟、论…

【AI开发】CRAG、Self-RAG、Adaptive-RAG

先放一张基础RAG的流程图 https://blog.langchain.dev/agentic-rag-with-langgraph/ 再放一个CRAG和self-RAG的LangChain官方博客 Corrective RAG(CRAG) 首先需要知道的是CRAG的特色发生在retrieval阶段的最后开始&#xff0c;即当我们获得到了近似的document&#xff08;或者…

【proteus仿真】基于51单片机的电压检测系统

【proteus仿真】基于51单片机的电压检测系统 资料下载地址&#xff1a;关注公众号 小邵爱电子 获取 1.前言 使用51单片机和ADC模块设计一个数字电压表&#xff0c;将模拟信号0~5V之间的电压转换为数字量信号&#xff0c;并通过LED实时显示电压数据 、 2.仿真原理图 3.硬件…

简单几步把完整的Windows塞进U盘,小白都能看懂

前言 小白之前写过相似的文章&#xff0c;但教程是通过WinPE操作实现的。 把Windows系统装进U盘&#xff0c;从此到哪都有属于你自己的电脑系统 有些小伙伴反馈教程写得很复杂&#xff0c;简直生涩难懂。 为啥要写得这么复杂呢&#xff1f;小白是想让小伙伴们多了解一些不同…

为什么MOSFET是双向导通的

MOSFET 的电压控制机理是利用栅极电压的 大小改变感应电场生成的导电沟道的厚度&#xff08;感生电荷的多少&#xff09;&#xff0c;来控制漏极电流 Id 的。从图1&#xff08;b&#xff09;中可 以看出&#xff0c;当栅极电压 V gs小于开启电压 V th时&#xff0c;无论 V ds的…

Android系统上Bootchart的使用

Android系统的启动细节分析&#xff0c;可以用工具bootchart来进行 一、Bootchart简介 官网地址&#xff1a;https://www.bootchart.org/ Google推荐bootchart作为开机优化的首选工具&#xff1a;https://source.android.com/devices/tech/perf/boot-times#bootchart bootc…

第三方软件测试报告包括哪些内容?如何获取专业第三方测试报告?

第三方软件测试报告是由独立的第三方公司进行软件测试后所生成的报告。该报告会清晰地呈现出软件在各个方面的测试结果和评估。通过第三方公司的专业测试&#xff0c;这些报告具有公正、中立和权威的特点。 一、第三方软件测试报告包括哪些内容? 1、功能测试&#xff1a;验证…

3d中毒了打不开模型怎么办---模大狮模型网

3D中毒了打不开模型怎么办&#xff1f;这是很多3D爱好者都会遇到的问题。在使用3D建模软件时&#xff0c;有时会出现打不开模型的情况&#xff0c;这可能是由于软件本身的问题&#xff0c;也可能是由于电脑配置不够高导致的。下面我们就来看看如何解决这个问题。 首先&#xff…

解密:不用import,Python编程将遭遇什么?

在Python中,import 语句用于导入其他模块或库,如果不使用 import,会导致以下问题&#xff1a; 无法使用外部库或模块&#xff1a; Python标准库以及第三方库提供了丰富的功能和工具,如果不导入这些库,就无法使用它们提供的功能。 代码可读性降低&#xff1a; import 语句可…