深入理解C++构造函数
目录
1.引言
2.默认构造函数
3.自定义构造函数
4.带继承关系类的构造函数
5.带多重继承关系类的构造函数
6.带虚继承关系类的构造函数
7.总结
1.引言
对于学过C++的来说,构造函数是非常熟悉不过的了。但是你真正了解它吗?构造函数内部初始化变量的顺序是怎么样的?多种继承构造函数的执行顺序?等等。今天我从前两天编程时遇到的一个问题说起,代码如下(只列举了部分关键代码):
#pragma once
#include <QObject>
#include <memory>class cCtkContext {};class CLinkCoreFactory
{
public:explicit CLinkCoreFactory(cCtkContext* pContext) : m_pContext(pContext) {}//...
private:cCtkContext* m_pContext;
};class IDataNotify
{
public:virtual void notify() = 0;//...
};class CThirdIInterface
{
public:explicit CThirdIInterface(CLinkCoreFactory* pFactory): m_pFactory(pFactory) {}//...private:CLinkCoreFactory* m_pFactory;
};class CDeviceManager : public QObject, public IDataNotify, public CThirdIInterface
{
public:CDeviceManager(cCtkContext* pContext, QObject* parent = nullptr): QObject(parent), m_linkCoreFactory(new CLinkCoreFactory(pContext)), CThirdIInterface(m_linkCoreFactory.get()){}
public:void notify() override {}//...private:std::unique_ptr<CLinkCoreFactory> m_linkCoreFactory;
};int main()
{cCtkContext context;CDeviceManager manager(&context);return 0;
}
在CDeviceManager内部定义了m_linkCoreFactory,CDeviceManager又继承于CThirdIInterface,CThirdIInterface又依赖于CLinkCoreFactory,于是CDeviceManager构造的时候,在写法上先构造了变量m_linkCoreFactory,再构造类CThirdIInterface,但是调试运行的时候,最后发现CThirdIInterface中的m_pFactory是无个效指针,为什么会这样呢?下面就这个问题深入讲解一下。
2.默认构造函数
如果类中没有定义任何构造函数,编译器会自动提供一个默认构造函数,这个构造函数不接受任何参数,并且不进行任何初始化操作(对象内的成员变量将使用它们的默认构造函数进行初始化,对于内置类型,这通常意味着它们将不会被初始化)。但是,一旦定义了任何构造函数(包括带参数的构造函数),编译器将不再自动生成默认构造函数。如果你需要默认构造函数,你必须显式地定义它。
示例如下:
#include <iostream>
using namespace std; class Box { public: //使用default关键字定义默认构造函数,length、breadth、height不会初始化Box()=default;//也可以自定义构造函数,初始化length、breadth、height
#if 0Box() : length(0.0),breadth(0.0),height(0.0) {}
#endif //也可以自定义带参数的构造函数,初始化length、breadth、heightBox(double len, double bre, double hei): length(len), breadth(bre), height(hei) { } // 成员函数来计算盒子的体积 double getVolume(void) { return length * breadth * height; } private:double length; // 长度 double breadth; // 宽度 double height; // 高度
}; int main() { Box Box1(3.3, 3.3, 3.3); // 声明 Box1 为 Box 类的一个对象 // Box1 的三个边初始化为 3.3 // 输出 Box1 的体积 cout << "Volume of Box1 : " << Box1.getVolume() << endl; return 0;
}
在Box内部,定义了一个默认构造函数,可以定义多个自定义构造函数,即是说构造函数是可以重载的。
3.自定义构造函数
在C++中,自定义构造函数允许你为类的实例提供特定的初始化逻辑。构造函数初始化列表是构造函数体之前的一个冒号(:)后跟一个以逗号分隔的初始化器列表。它用于初始化成员变量,特别是那些不能通过赋值来初始化的成员(如const成员、引用成员或没有默认构造函数的类类型的成员)。
上面示例中的:
Box(double len, double bre, double hei): length(len), breadth(bre), height(hei) { }
就属于自定义构造函数。
4.带继承关系类的构造函数
在C++中,子类(派生类)不能直接继承父类(基类)的构造函数。但是,子类可以定义自己的构造函数,并在其构造函数内部调用父类的构造函数(如果父类有可访问的非私有构造函数)以进行必要的初始化。这是通过使用成员初始化列表来实现的,在初始化列表中可以显式地调用父类的构造函数。
以下是一个简单的示例,展示了如何在带有继承的C++类中处理构造函数:
#include <iostream>
using namespace std; // 基类
class Base {
public: // 基类的构造函数 Base(int val) : a(val) { cout << "Base class constructor called, a = " << a << endl; } // 基类的另一个构造函数(可选) Base() : a(0) { cout << "Base class default constructor called, a = " << a << endl; }
private:int a;
}; // 派生类
class Derived : public Base {
public: // 派生类的构造函数,显式调用基类的构造函数 Derived(int val1, int val2) : Base(val1), b(val2) { cout << "Derived class constructor called, b = " << b << endl; } // 如果需要,也可以定义默认构造函数(不显式调用Base的构造函数时,会调用Base的默认构造函数) Derived() : Base(), b(0) { cout << "Derived class default constructor called, b = " << b << endl; }
private:int b;
}; int main() { // 使用自定义构造函数创建Derived对象 Derived obj1(5, 10); // 使用默认构造函数创建Derived对象(同时也会调用Base的默认构造函数) Derived obj2; return 0;
}
在上面的代码中,Derived类继承自Base类。Derived类有两个构造函数:一个接受两个参数(一个用于Base类,一个用于自身),另一个是无参数的默认构造函数。在Derived的构造函数中,通过成员初始化列表显式地调用了Base类的构造函数。
如果Base类没有可访问的默认构造函数,并且你试图在Derived类中定义一个不接受任何参数的构造函数而不显式调用Base类的某个构造函数,那么编译器会报错,因为它不知道应该使用Base类的哪个构造函数。
此外,构造函数初始化列表是初始化成员变量的首选方式,因为它比在构造函数体内赋值更高效,特别是对于常量成员、引用成员以及没有默认构造函数的类类型成员。
注意事项
•初始化顺序:派生类的构造函数首先初始化基类部分,然后初始化派生类自己的成员变量。这意味着,即使你在派生类构造函数体内调用基类构造函数,基类部分也已经在调用之前被初始化了。
5.带多重继承关系类的构造函数
在C++中,多重继承允许一个类(派生类)同时从多个基类继承属性和行为。在处理多重继承时,派生类的构造函数需要负责初始化所有基类以及自身的成员变量。这通常是通过构造函数初始化列表来完成的,其中可以明确指定如何调用各个基类的构造函数。
下面是一个C++多重继承的例子,展示了如何在构造函数中初始化多个基类:
#include <iostream>
using namespace std; // 第一个基类
class Base1 {
public: Base1(int val) : a(val) { cout << "Base1 constructor called, a = " << a << endl; }
private:int a;
}; // 第二个基类
class Base2 {
public: Base2(int val) : b(val) { cout << "Base2 constructor called, b = " << b << endl; }
private:int b;
}; // 派生类,继承自Base1和Base2
class Derived : public Base1, public Base2 {
public: // 派生类的构造函数 // 必须显式调用所有基类的构造函数 Derived(int val1, int val2, int val3) : Base1(val1), Base2(val2), c(val3) { cout << "Derived constructor called, c = " << c << endl; }
private:int c;
}; int main() { // 创建Derived类的实例 Derived obj(1, 2, 3); return 0;
}
在这个例子中,Derived类同时从Base1和Base2两个基类继承。Derived类的构造函数通过构造函数初始化列表显式地调用了Base1和Base2的构造函数,并传递了相应的参数。同时,它也初始化了自己的成员变量c。
需要注意的是,构造函数初始化列表中基类的调用顺序与它们在派生类声明中的顺序相同,而不是与它们在初始化列表中出现的顺序相同。在这个例子中,Base1的构造函数将在Base2的构造函数之前被调用,尽管在初始化列表中Base2的调用出现在Base1之前。
此外,如果基类中有默认构造函数(即不接受任何参数的构造函数),并且派生类的构造函数没有显式调用任何基类构造函数,那么将隐式调用基类的默认构造函数(如果存在的话)。但是,如果基类没有可访问的默认构造函数,并且派生类的构造函数没有显式调用基类的某个构造函数,那么编译器将报错。
现在我们回顾一下在文章开头的例子调试发现CThirdIInterface中的m_pFactory是无个效指针,原因就是CDeviceManager的构造函数:
CDeviceManager(cCtkContext* pContext, QObject* parent = nullptr): QObject(parent), m_linkCoreFactory(new CLinkCoreFactory(pContext)), CThirdIInterface(m_linkCoreFactory.get()){}
CThirdIInterface构造函数的调用实际先于m_linkCoreFactory的构造,导致传入CThirdIInterface构造函数的指针pFactory是空的。
6.带虚继承关系类的构造函数
虚继承(Virtual Inheritance)用于解决多重继承中可能出现的菱形继承(Diamond Inheritance)问题,即一个类从多个基类继承,而这些基类又共同继承自同一个基类,导致基类在派生类中被多次实例化的问题。通过虚继承,可以确保基类在继承体系中只被实例化一次。
在虚继承中,派生类的构造函数依然需要负责初始化所有基类(包括虚基类和其他非虚基类)以及自身的成员变量。但是,与常规继承不同的是,虚基类的构造函数只在最底层的派生类中被调用一次,且调用顺序由其在继承体系中的深度决定(从最顶层的基类开始,向下直到最底层的派生类)。
示例如下:
#include <iostream>
using namespace std; // 虚基类
class VirtualBase {
public: VirtualBase() { cout << "VirtualBase constructor called" << endl; }
}; // 第一个基类,虚继承自VirtualBase
class Base1 : virtual public VirtualBase {
public: Base1() { cout << "Base1 constructor called" << endl; }
}; // 第二个基类,也虚继承自VirtualBase
class Base2 : virtual public VirtualBase {
public: Base2() { cout << "Base2 constructor called" << endl; }
}; // 派生类,同时继承自Base1和Base2
class Derived : public Base1, public Base2 {
public: Derived() { cout << "Derived constructor called" << endl; }
}; int main() { // 创建Derived类的实例 Derived obj; return 0;
}
在这个例子中,Derived类从Base1和Base2继承,而Base1和Base2都虚继承自VirtualBase。当你创建一个Derived类的实例时,构造函数的调用顺序如下:
1) VirtualBase的构造函数首先被调用(因为VirtualBase是最顶层的基类,并且只会被实例化一次)。
2) 接着是Base1的构造函数(注意,尽管Base1虚继承自VirtualBase,但VirtualBase的构造函数已经在前面被调用了,所以这里不会再次调用它)。
3) 然后是Base2的构造函数(同样,VirtualBase的构造函数不会被重复调用)。
4) 最后是Derived的构造函数。
输出将是:
VirtualBase constructor called
Base1 constructor called
Base2 constructor called
Derived constructor called
注意事项
•构造函数的调用顺序:在虚继承中,最派生类的构造函数负责首先调用虚基类的构造函数,然后是其他非虚基类的构造函数。
•初始化责任:由于虚基类只被初始化一次,因此最派生类负责调用虚基类的构造函数。
7.总结
构造函数是C++中非常重要的概念,它们用于在对象创建时初始化对象的成员变量。构造函数没有返回类型,并且其名称必须与类名相同。通过构造函数,可以确保对象在创建时处于有效的状态。
相关文章:
深入理解C++构造函数
目录 1.引言 2.默认构造函数 3.自定义构造函数 4.带继承关系类的构造函数 5.带多重继承关系类的构造函数 6.带虚继承关系类的构造函数 7.总结 1.引言 对于学过C的来说,构造函数是非常熟悉不过的了。但是你真正了解它吗?构造函数内部初始化变量的顺…...
J025_斗地主游戏案例开发(简版)
一、需求描述 完成斗地主游戏的案例开发。 业务:总共有54张牌; 点数:3、4、5、6、7、8、9、10、J、Q、K、A、2 花色:黑桃、红桃、方片、梅花 大小王:大王、小王 点数分别要组合4种花色,大小王各一张。…...
路径规划 | 飞蛾扑火算法求解二维栅格路径规划(Matlab)
目录 效果一览基本介绍程序设计参考文献 效果一览 基本介绍 路径规划 | 飞蛾扑火算法求解二维栅格路径规划(Matlab)。 飞蛾扑火算法(Firefly Algorithm)是一种基于自然界萤火虫行为的优化算法,在路径规划问题中也可以应…...
优化Cocos Creator 包体体积
优化Cocos Creator 包体体积 引言一、优化图片文件体积:二、优化声音文件体积:三、优化引擎代码体积:四、 优化字体字库文件大小: 引言 优化Cocos Creator项目的包体体积是一个常见且重要的任务,尤其是在移动设备和网…...
TCPDump协议分析工具
TCPDump协议分析工具 TCPDump是一个强大的命令行工具,用于捕获和分析网络数据包。它能够实时监控和记录网络流量,帮助网络管理员和安全专家排查网络问题、分析流量和检测网络攻击。以下是TCPDump的详细介绍,包括其安装、基本使用、过滤规则和…...
土壤分析仪:解密土壤之奥秘的科技先锋
在农业生产和生态保护的道路上,土壤的质量与状况一直是我们关注的焦点。土壤分析仪,作为现代科技在农业和环保领域的杰出代表,以其高效、精准的分析能力,为我们揭示了土壤的奥秘,为农业生产提供了科学指导,…...
计算1的数量
1. 计算1的数量 题目ID:9809必做题100分 最新提交: Accepted 100 分 历史最高: Accepted 100 分 时间限制: 1000ms 空间限制: 524288kB 题目描述 给定一个n*m的二进制矩阵,请你数一数矩阵中完全被0上下左右包围的1的数…...
Linux udp编程
我最近开了几个专栏,诚信互三! > |||《算法专栏》::刷题教程来自网站《代码随想录》。||| > |||《C专栏》::记录我学习C的经历,看完你一定会有收获。||| > |||《Linux专栏》࿱…...
【开源项目】Rust开发复制文件夹目录结构工具
说明 由于我经常需要在多个大容量的移动硬盘中查找和新增文件,我希望把硬盘的目录结构放到服务器的自建网盘中,因此开发了这个工具,使得在不同硬盘之间的文件管理变得更加便捷 项目地址:https://github.com/VinciYan/folder_clon…...
PostgreSQL的pg_dirtyread工具
PostgreSQL的pg_dirtyread工具 pg_dirtyread 是一个第三方PostgreSQL扩展,它允许用户读取数据库文件中的“脏”数据,即那些被标记为删除或不再可见的数据。这个扩展对于数据恢复和调试非常有用,尤其是在需要恢复被删除或更新前的数据时。 以…...
苹果梦碎:Vision Pro的辉煌与失落,苹果已决定暂停 Vision Pro 后续产品的研发工作
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点 苹果Vision Pro:科技巨头的跌宕起伏 在科技的海洋中,苹果公司一直以其创新精神和卓越品质引领潮流。然而,即…...
推荐一款uniapp拖动验证码插件
插件地址:易盾验证码 - DCloud 插件市场 具体使用方式访问插件地址自行获取...
十年期国债收益率
十年期国债收益率是指政府发行的、期限为十年的国债的年化收益率。它被广泛视为一个国家经济健康状况和未来经济前景的重要指标,同时也是金融市场中的一个重要基准利率。 下面我将详细解释十年期国债收益率的相关内容及其意义。 十年期国债收益率的意义 经济健康的…...
使用Go编写的持续下行测速脚本,快速消耗流量且不伤硬盘
GoSpeed 声明 此工具仅用于测试与学习,请勿用于非法用途,如使用此程序请确保所有下载的内容都拥有合法的使用权或分发权,避免侵犯版权、恶意访问 此工具仅用于测试与学习,请勿用于非法用途,如使用此程序请确保所有下载的内容都拥有合法的使用权或分发权,…...
保护国外使用代理IP的安全方法
为了保护在国外使用代理IP的安全,用户可以采取以下方法: 1. 选择可信的代理服务器 在选择代理服务器时,用户应该选择那些经过验证和信任的服务器,如知名的VPN服务提供商。这些服务器通常具有更高的安全性和隐私保护措施。 2. 使用…...
18集 学习ESP32的ESP-DL深度学习教程-《MCU嵌入式AI开发笔记》
18集 学习ESP32的ESP-DL深度学习教程-《MCU嵌入式AI开发笔记》 参考文档:https://docs.espressif.com/projects/esp-dl/zh_CN/latest/esp32/tutorials/index.html 使用TVM自动生成模型部署项目 本案例介绍了使用 TVM 部署模型的完整流程。 该项目基于 TVM v0.14…...
jmeter-beanshell学习9-放弃beanshell
写这篇时候道心不稳了,前面写了好几篇benashell元件,突然发现应该放弃。想回去改前面的文章,看了看无从下手,反正已经这样了,我淋了雨,那就希望别人也没有伞吧,哈哈哈哈,放在第九篇送…...
Web 性能入门指南-1.5 创建 Web 性能优化文化的最佳实践
最成功的网站都有什么共同点?那就是他们都有很强的网站性能和可用性文化。以下是一些经过验证的有效技巧和最佳实践,可帮助您建立健康、快乐、值得庆祝的性能文化。 创建强大的性能优化文化意味着在你的公司或团队中创建一个如下所示的反馈循环ÿ…...
【Android】Service介绍和生命周期
人不走空 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨 介绍 Service是Android程序中四大基础组件之一,它和Activity一样都是Context的子类,只不…...
[论文笔记]RAPTOR: RECURSIVE ABSTRACTIVE PROCESSING FOR TREE-ORGANIZED RETRIEVAL
引言 今天带来又一篇RAG论文笔记:RAPTOR: RECURSIVE ABSTRACTIVE PROCESSING FOR TREE-ORGANIZED RETRIEVAL。 检索增强语言模型能够更好地适应世界状态的变化并融入长尾知识。然而,大多数现有方法只能从检索语料库中检索到短的连续文本片段࿰…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
