构造函数的使用大全
概述
在C++中创建一个对象时,通常需要做一些数据初始化的工作,因此便提供了一个特殊的成员函数 —— 构造函数。一般情况下,并不需要程序员主动调用构造函数,而是在创建对象时,由系统自动调用。构造函数可以由程序员定义,如果未定义,则编译器会提供默认的构造函数。构造函数没有返回值,也不需要加void类型声明,且其名称必须与类名相同。
默认构造函数
构造函数是可以重载的,我们把没有任何参数的构造函数称为默认构造函数。默认构造函数可以由程序员自己实现,也可以不实现,而交给编译器去提供。但编译器提供的默认构造函数并没有初始化成员变量,成员变量的值很可能是随机的,后续使用会导致各种不可预料的风险和问题。因此,最好由程序员自行实现默认构造函数,并在初始化列表中为每一个成员变量赋初始值。可参看下面的示例代码。
class CBase
{
public:CBase();private:int m_nNumber;
};CBase::CBase() : m_nNumber(66)
{NULL;
}
当然,也可以不写初始化列表,而在构造函数的函数体中为每一个成员变量赋值(不推荐这种方式)。但如果类中有引用类型、常量类型的成员变量,则必须在初始化列表中进行赋值,不能在函数体内进行赋值。可参看下面的示例代码。
class CBase
{
public:CBase(bool &bCheck);void Show(){printf("%d\n", m_bCheck);}private:int m_nNumber;const std::string m_strText;bool &m_bCheck;
};CBase::CBase(bool &bCheck) : m_nNumber(66), m_strText("CSDN"), m_bCheck(bCheck)
{m_strText = "hello"; // 函数体内赋值,编译错误m_bCheck = false; // 函数体内赋值,编译错误
}bool bCheck = true;
CBase base(bCheck);
base.Show(); // 输出:1
bCheck = false;
base.Show(); // 输出:0
可以看到,常量成员变量m_strText和引用成员变量m_bCheck必须在初始化列表中赋值。如果在函数体内赋值,则会发生编译错误。还有一点需要注意:成员变量初始化的顺序,不是由初始化列表中的顺序决定的,而是由成员变量声明的顺序决定的。
带参数的构造函数
构造函数可以带一个或多个参数,一般用这些参数去初始化内部的成员变量。可参看下面的示例代码。
class CBase
{
public:CBase(int nNumber, const std::string &strText);private:int m_nNumber;const std::string m_strText;
};CBase::CBase(int nNumber, const std::string &strText) : m_nNumber(nNumber), m_strText(strText)
{NULL;
}CBase base(66, "CSDN");
拷贝构造函数
创建一个对象时,可以从另一个已经创建好了的对象去复制或拷贝。程序员未提供拷贝构造函数时,编译器会提供一个默认版本的拷贝构造函数。可参看下面的示例代码。
class CBase
{
public:CBase(int nNumber, const std::string &strText);void Show();private:int m_nNumber;const std::string m_strText;
};CBase::CBase(int nNumber, const std::string &strText) : m_nNumber(nNumber), m_strText(strText)
{NULL;
}void CBase::Show()
{printf("data is %d, %s\n", m_nNumber, m_strText.c_str());
}CBase base1(66, "CSDN");
CBase base2(base1);
base2.Show(); // 输出:data is 66, CSDNCBase base3 = base1;
base3.Show(); // 输出:data is 66, CSDN
可以看到,虽然我们没有提供拷贝构造函数,但上面的base2和base3却能够正常创建出来。这是因为编译器提供的默认版本的拷贝构造函数干活了:它会将已有对象的所有成员变量的值赋值给待创建对象的所有成员变量。这是不是意味着,我们就不需要自己实现拷贝构造函数了呢?来看看下面的示例代码。
class CBase
{
public:CBase();~CBase();void Show();private:char *m_pszText;
};CBase::CBase() : m_pszText(NULL)
{m_pszText = new char[66];strcpy(m_pszText, "CSDN");
}CBase::~CBase()
{delete[] m_pszText;m_pszText = NULL;
}void CBase::Show()
{printf("text is %s, 0x%08X\n", m_pszText, (unsigned int)m_pszText);
}{CBase base1;base1.Show(); // 输出:text is CSDN, 0x00935290CBase base2 = base1;base2.Show(); // 输出:text is CSDN, 0x00935290
}
可以看到,将base1赋值给base2后,两个对象调用Show函数,输出的内容和指针都是一模一样的。这段代码运行后,大概率会导致程序崩溃。这是因为,默认的拷贝构造函数只是单单对成员变量进行了赋值。在上例中,只是将base1的m_pszText直接赋值给了base2的m_pszText。当base1和base2离开作用域范围需要析构时,析构函数中会对各自的m_pszText进行释放操作。此时,对同一个指针释放了两次,第二次释放时,m_pszText其实已经变成了野指针,从而导致崩溃。
解决该问题的方法是:自行实现拷贝构造函数,对指针进行深拷贝,而不是浅拷贝。可参看下面的示例代码。
class CBase
{
public:CBase();CBase(const CBase &base);~CBase();void Show();private:char *m_pszText;
};CBase::CBase() : m_pszText(NULL)
{m_pszText = new char[66];strcpy(m_pszText, "CSDN");
}CBase::CBase(const CBase &base)
{m_pszText = new char[66];strcpy(m_pszText, base.m_pszText);
}CBase::~CBase()
{delete[] m_pszText;m_pszText = NULL;
}void CBase::Show()
{printf("text is %s, 0x%08X\n", m_pszText, (unsigned int)m_pszText);
}{CBase base1;base1.Show(); // 输出:text is CSDN, 0x00EA5290CBase base2 = base1;base2.Show(); // 输出:text is CSDN, 0x00EA4090
}
可以看到,在拷贝构造函数中,我们自己分配了内存,并将内容拷贝了过来。base1和base2调用Show函数后,指针的输出值不一样,也验证了这一点。
转换构造函数
转换构造函数是指只有一个参数,且该参数的类型不同于自身类的类型,通过该参数可以构造对象的构造函数。可参看下面的示例代码。
class CBase
{
public:CBase();CBase(int nData);void Show();private:int m_nData;
};CBase::CBase() : m_nData(66)
{NULL;
}CBase::CBase(int nData) : m_nData(nData)
{NULL;
}void CBase::Show()
{printf("data is %d\n", m_nData);
}CBase base = 88;
base.Show(); // 输出:data is 88
可以看到,我们直接将88赋值给了base对象,这是因为发生了隐式类型转换,相当于自动执行了下面的代码。
CBase base = CBase(88);
首先调用转换构造函数创建了一个临时的CBase对象,然后调用默认的拷贝构造函数创建base对象。在有的编译器下,会自动优化上述代码,不会创建临时对象,而是直接调用转换构造函数创建base对象。如果不想发生隐式类型转换,可以在转换构造函数前添加explicit关键字。可参看下面的示例代码。
class CBase
{
public:CBase();explicit CBase(int nData);void Show();private:int m_nData;
};CBase base = 88; // 不能隐式类型转换了,会编译出错
base.Show();CBase base2(88); // 正常调用转换构造函数
base2.Show();
移动构造函数
在上面介绍拷贝构造函数时,提到了浅拷贝和深拷贝的概念。默认的拷贝构造函数属于浅拷贝,会造成重复释放指针的问题。自己实现的拷贝构造函数可以采用深拷贝,但深拷贝也有缺点:从一个临时的右值对象进行深拷贝时,会导致额外的内存创建和释放的开销。为了解决该问题,在C++ 11中,提出了移动构造函数的概念。所谓移动构造函数,就是将临时的右值对象内部的指针移动过来,新对象抢占指针的所有权,原来的右值对象失去指针的所有权。可参看下面的示例代码。
class CBase
{
public:CBase();CBase(CBase &&base); // 移动构造函数~CBase();private:char *m_pszData;
};CBase::CBase() : m_pszData(NULL)
{m_pszData = new char[66];strcpy(m_pszData, "CSDN");printf("CBase default constructor: %s\n", m_pszData);
}CBase::CBase(CBase &&base) : m_pszData(NULL)
{m_pszData = base.m_pszData;base.m_pszData = NULL;printf("CBase move constructor: %s\n", m_pszData);
}CBase::~CBase()
{if (m_pszData == NULL){printf("CBase destructor: NULL\n");} else{printf("CBase destructor: %s\n", m_pszData);delete[] m_pszData;m_pszData = NULL;}
}CBase GetBase()
{CBase base;return base;
}CBase base = GetBase();
上述代码执行后的输出如下:
CBase default constructor: CSDN
CBase move constructor: CSDN
CBase destructor: NULL
CBase destructor: CSDN
可以看到,GetBase()函数返回的是一个右值对象,将其赋值给base对象时,正好适用移动构造函数。在CBase的移动构造函数内部,我们将m_pszData指针进行了转移。默认情况下,传入构造函数中的实参如果是左值对象,会调用拷贝构造函数,而不会调用移动构造函数。如果想要左值对象也调用移动构造函数,可以先调用C++ 11中的std::move()函数,将左值对象强制转换成右值对象。
相关文章:
构造函数的使用大全
概述 在C中创建一个对象时,通常需要做一些数据初始化的工作,因此便提供了一个特殊的成员函数 —— 构造函数。一般情况下,并不需要程序员主动调用构造函数,而是在创建对象时,由系统自动调用。构造函数可以由程序员定义…...
ASP.NET Core MVC 项目 IOC容器
目录 一:什么是IOC容器 二:简单理解内置Ioc容器 三:依赖注入内置Ioc容器 四:生命周期 五:多种注册方式 一:什么是IOC容器 IOC容器是Inversion Of Control的缩写,翻译的意思就是控制反转。 …...
ARM工控机/网关- 钡铼技术
一、NXP处理器ARM控制器的介绍 NXP半导体是汽车、穿戴、消费电子等领域中智能机器解决方案的领先供应商。其产品线庞大,包括处理器、微控制器、快速设计平台、ARM控制器等。在物联网控制、汽车电子、安全应用等领域,NXP处理器ARM控制器已成为半导体行业的…...
为什么都在喊数据可视化?它究竟怎么做?
在数字化转型的浪潮中,不论是传统行业,还是新兴行业总会提到“数据可视化”这个词。那数据可视化到底是什么?为什么会受到那么多人追捧?又该怎么才能做到数据可视化呢? 一、数据可视化是什么? 首先“可视…...
nodejs+vue停车场停车位短租系统vscode
目 录前端技术:nodejsvueelementui 前端:HTML5,CSS3、JavaScript、VUE 1、 node_modules文件夹(有npn install产生) 这文件夹就是在创建完项目后,cd到项目目录执行npm install后生成的文件夹,下载了项目需要的依赖项。 2、…...
物理真机上LUKS结合TPM的测试 —— 使用随机数密钥
1. 创建磁盘空间 命令如下: dd if/dev/zero ofenc.disk bs1M count50 实际命令及结果如下: $ dd if/dev/zero ofenc.disk bs1M count50 输入了 500 块记录 输出了 500 块记录 52428800 字节 (52 MB, 50 MiB) 已复制,0.0587495 sÿ…...
Linux USB 开发指南
文章目录Linux USB 开发指南1 前言1.1 文档简介1.2 目标读者1.3 适用范围2 模块介绍2.1 模块功能介绍2.2 相关术语介绍2.3 模块配置介绍2.3.1 Device Tree 配置说明2.3.2 board.dts 配置说明2.3.3 kernel menuconfig 配置说明2.4 源码结构介绍2.5 驱动框架介绍2.6 Gadget 配置2…...
FreeRTOS入门(03):队列、信号量、互斥量
文章目录目的队列(queue)信号量(semaphore)互斥量(mutex)互斥量递归互斥量总结目的 FreeRTOS提供给用户最核心的功能是任务(Task),实际项目中通常会有多个任务ÿ…...
Biome-BGC在模拟过程中,如何使用Linux、Python等,完成前处理和后处理工作???
在Biome-BGC模型中,对于碳的生物量积累,采用光合酶促反应机理模型计算出每天的初级生产力(GPP),将生长呼吸和维持呼吸减去后的产物分配给叶、枝条、干和根。生物体的碳每天都按一定比例以凋落方式进入凋落物碳库;对于水份输运过程…...
【unittest学习】unittest框架主要功能
1.认识unittest在 Python 中有诸多单元测试框架,如 doctest、unittest、pytest、nose 等,Python 2.1 及其以后的版本已经将 unittest 作为一个标准模块放入 Python 开发包中。2.认识单元测试不用单元测试框架能写单元测试吗?答案是肯定的。单…...
京东测开岗3+1面经+经验分享,拿到offer,月薪34k....
现在,招聘黄金时间已经来临,在网上看了很多大佬的面经,也加了很多交流群,受到了很多朋友的提点,今天终于轮到我来分享面经啦,之前面试了几家公司,最后拿到了京东测试岗的 offer,这里…...
后端接收格式为x-www-form-urlencoded的数据
1.x-www-form-urlencoded是什么? x-www-form-urlencoded纸面翻译即所谓url格式的编码,是post的默认Content-Type,其实就是一种编码格式,类似json也是一种编码传输格式。form表单中使用 form的enctype属性为编码方式࿰…...
LeetCode 707. 设计链表
LeetCode 707. 设计链表 难度:middle\color{orange}{middle}middle 题目描述 设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:valvalval 和 nextnextnext。valvalval 是当前节点的值,nextnextnext 是指向下…...
HTTP的主要作用是什么
1、客户与服务器建立连接; 2、客户向服务器提出请求; 3、服务器接受请求,并根据请求返回相应的文件作为应答; 4、客户与服务器关闭连接。 HTTP的性质: 1、HTTP是一种无状态协议,即服务器不保留与客户交…...
SpringBoot系列-- @Enable 模块驱动
Enable 模块驱动 Enable 模块驱动是以 Enable 为前缀的注解驱动编程模型。所谓 “模块” 是指具备相同领域的功能组件集合,组合所形成一个独立的单元。比如 WebMVC 模块、AspectJ 代理模块、Caching (缓存)模块、JMX (Java 管理扩…...
PHP程序员适合创业吗?
创业是一件自然而然的事,不需要人为选择。 只要你是一个努力能干主动的人,当你在一个行业深耕5年之后,就会发现人生发展的下一步就是创业。当然如果行业合适的话。 什么叫行业合适呢? 就是创业的成本并不那么高,不需…...
2023年CDGA考试-第12章-元数据(含答案)
2023年CDGA考试-第12章-元数据(含答案) 单选题 1.元数据架构的类型主要有四种下列哪项不属于分布式元数据架构的优点? A.减少了批处理 B.元数据的质量完全取决于源系统 C.最大程度的减少了实施和维护所需的工作量 D.元数据总是尽可能保持最新且有效 答案 B 2.元数据管理是…...
数据结构之顺序表篇
一、顺序表概念 二、顺序表各类接口实现 *顺序表初始化 **顺序表销毁 ***顺序表插入操作 ****顺序表删除操作 *****顺序表查找操作 ******顺序表实现打印操作 三、顺序表整体实现源码 *SeqList.h **SeqList.c ***test.c 一、顺序表概念 讲顺序表之前先引入线性表概念ÿ…...
ZBC通证月内已翻倍,Nautilus Chain 上线前夕的“开门红”
近日,Zebec Protocol生态通证ZBC迎来了大涨,据悉该通证月内最高涨幅接近了100%,为一众投资者、社区用户、Zepoch节点等带来了可观的回报,并为生态发展注入了十足的信心。我们看到,Zebec Protocol生态在近期宣布了“销毁…...
人工智能练习题:激活函数需要满足的条件、提高CNN的泛化能力、CNN输出特征图大小计算
文章目录1.激活函数需要满足的条件2.提高CNN泛化能力的方法3.CNN输出特征图大小计算第一次用ChatGPT,不得不说在处理大学生作业上,ChatGPT比国内的作业软件好用多了(感叹)。 1.激活函数需要满足的条件 通常情况下,激活…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...
解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...
Kafka主题运维全指南:从基础配置到故障处理
#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...
AD学习(3)
1 PCB封装元素组成及简单的PCB封装创建 封装的组成部分: (1)PCB焊盘:表层的铜 ,top层的铜 (2)管脚序号:用来关联原理图中的管脚的序号,原理图的序号需要和PCB封装一一…...
aardio 自动识别验证码输入
技术尝试 上周在发学习日志时有网友提议“在网页上识别验证码”,于是尝试整合图像识别与网页自动化技术,完成了这套模拟登录流程。核心思路是:截图验证码→OCR识别→自动填充表单→提交并验证结果。 代码在这里 import soImage; import we…...
