构造函数的使用大全
概述
在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.激活函数需要满足的条件 通常情况下,激活…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
