构造函数的使用大全
概述
在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.激活函数需要满足的条件 通常情况下,激活…...
网安工程师好就业吗?零基础转行如何操作?
“ 就业是好就业的,但是太卷了,因为它本身就是个门槛低,技术高的工作。如果决定要走这条路,那么一定要下定决心好好学,学出来了这下半辈子就不用愁了。” 网络安全,这个在现代社会愈发受到重视的领域&#…...
ARCMAP实战:3分钟搞定面数据200米内点筛选(附反向选择技巧)
ARCMAP空间筛选实战:200米范围点数据高效提取与反向选择技巧 在地理信息系统(GIS)日常工作中,空间数据筛选是最基础却最频繁的操作之一。许多初学者面对"筛选某面要素200米范围内的点数据"这类需求时,往往会…...
VRCT:打破VRChat语言壁垒的实时翻译与转录工具
VRCT:打破VRChat语言壁垒的实时翻译与转录工具 【免费下载链接】VRCT VRCT(VRChat Chatbox Translator & Transcription) 项目地址: https://gitcode.com/gh_mirrors/vr/VRCT 在全球化的虚拟社交平台VRChat中,语言差异常常成为跨文化交流的主…...
AI赋能监控:让快马平台的Kimi模型帮你智能识别网页每日真更新
今天想和大家分享一个最近用AI辅助开发的实用小工具——智能网页更新检测系统。这个项目的核心目标是解决传统网页监控工具"误报率高"和"无法识别实质性更新"的痛点,特别适合需要跟踪竞品动态或内容更新的运营同学。 语义摘要比对技术 传统方案…...
【无标题】修改+删除顶层class
# 最顶层的class信息如何删除-- 步骤1:删除该班级学生关联的成绩(先删最底层score表) DELETE FROM score WHERE studentNo IN (SELECT studentNo FROM student0 WHERE classNo210601);-- 步骤2:删除该班级的所有学生(再…...
【黑金云课堂笔记】第一~二期FPGA知识点总结
知识卡片一:【FPGA 基础篇】开启硬件编程之门FPGA 的本质: FPGA(现场可编程门阵列)并非在运行软件程序,而是在构建电路本身。用户可以通过 Verilog/VHDL 等硬件描述语言,在芯片出厂后随时重新配置其内部逻辑…...
Python 使用 `raise` 报错抛出异常显示 Unicode 码如何解决
在 Python 开发中,我们经常使用 raise 抛出异常来处理错误情况。但有时候,异常信息中的中文或其他非 ASCII 字符会被显示为 Unicode 转义序列(如 \u6b63\u6587),而不是直接显示中文(如“正文”)…...
别让SDF警告淹没你!芯片后仿真中那些‘不起眼’却至关重要的VCS编译选项详解
别让SDF警告淹没你!芯片后仿真中那些‘不起眼’却至关重要的VCS编译选项详解 当数字IC设计进入后仿真阶段,工程师们常常会陷入海量警告信息的泥潭。特别是当SDF(Standard Delay Format)文件反标时产生的各类警告,往往…...
从网球场到棋盘:深入对比Moravec与Forstner算子在真实影像中的表现差异与选型建议
从网球场到棋盘:深入对比Moravec与Forstner算子在真实影像中的表现差异与选型建议 当我们需要从一张照片中找出那些独特的"地标"时——无论是网球场的边角线还是棋盘上的交叉点——特征点提取算法就像一位经验丰富的侦探,用不同的策略标记出关…...
SMBIOS字符串逆向解析技巧:从二进制数据到硬件信息全解密(含Type1实例分析)
SMBIOS字符串逆向解析技巧:从二进制数据到硬件信息全解密(含Type1实例分析) 在数字取证和硬件分析领域,SMBIOS数据结构就像一台计算机的"身份证档案库",存储着从主板序列号到电池规格等数百项硬件细节。但当…...
