当前位置: 首页 > news >正文

构造函数的使用大全

概述

        在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

        可以看到,虽然我们没有提供拷贝构造函数,但上面的base2base3却能够正常创建出来。这是因为编译器提供的默认版本的拷贝构造函数干活了:它会将已有对象的所有成员变量的值赋值给待创建对象的所有成员变量。这是不是意味着,我们就不需要自己实现拷贝构造函数了呢?来看看下面的示例代码。

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函数,输出的内容和指针都是一模一样的。这段代码运行后,大概率会导致程序崩溃。这是因为,默认的拷贝构造函数只是单单对成员变量进行了赋值。在上例中,只是将base1m_pszText直接赋值给了base2m_pszText。当base1base2离开作用域范围需要析构时,析构函数中会对各自的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
}

        可以看到,在拷贝构造函数中,我们自己分配了内存,并将内容拷贝了过来。base1base2调用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&#xff…...

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),实际项目中通常会有多个任务&#xff…...

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属性为编码方式&#xff0…...

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 一、顺序表概念 讲顺序表之前先引入线性表概念&#xff…...

ZBC通证月内已翻倍,Nautilus Chain 上线前夕的“开门红”

近日,Zebec Protocol生态通证ZBC迎来了大涨,据悉该通证月内最高涨幅接近了100%,为一众投资者、社区用户、Zepoch节点等带来了可观的回报,并为生态发展注入了十足的信心。我们看到,Zebec Protocol生态在近期宣布了“销毁…...

人工智能练习题:激活函数需要满足的条件、提高CNN的泛化能力、CNN输出特征图大小计算

文章目录1.激活函数需要满足的条件2.提高CNN泛化能力的方法3.CNN输出特征图大小计算第一次用ChatGPT,不得不说在处理大学生作业上,ChatGPT比国内的作业软件好用多了(感叹)。 1.激活函数需要满足的条件 通常情况下,激活…...

KingbaseES Json 系列三:Json数据操作函数一

KingbaseES Json 系列三--Json数据操作函数一(JSONB_EACH,JSONB_EACH_TEXT,JSONB_OBJECT_KEYS,JSONB_EXTRACT_PATH,JSONB_EXTRACT_PATH_TEXT,JSON_EACH,JSON_EACH_TEXT,JSON_OBJECT_KEYS,JSON_EXTRACT_PATH,JSON_EXTRACT_PATH_TEXT) JSON 数据类型是用来存储 JSON(JavaScript O…...

《设计模式》单例模式

《设计模式》单例模式 单例模式是一种常用的设计模式,其主要优点有: 提供了对唯一实例的全局访问。单例模式保证了整个系统中只有一个实例,这样就可以方便地对该实例进行访问和操作,避免了多个实例之间的冲突和不一致。避免了重…...

C/C++每日一练(20230224)

目录 1. 字符串排序 2. Excel表列名称 3. 颠倒二进制位 附录&#xff1a; 位移运算符 左移运算符<< 1.无符号 2.有符号 右移运算符>> 1.无符号 2.有符号 程序测试 1. 字符串排序 编写程序&#xff0c;输入若干个字符串。 要求: &#xff08;1&#x…...

基于YOLO的酸枣病虫害检测识别实践

在我前面的博文中对于农作物病虫害的检测识别已经做过了&#xff0c;不过那个主要是针对水稻的&#xff0c;文章如下&#xff1a;《基于yolov5的轻量级水稻虫害目标检测项目实践》感兴趣的话可以自行移步阅读。这里主要是针对酸枣常见的几种病虫害检测检测识别&#xff0c;首先…...

WAF:ModSecurity on Nginx(15)

预备知识 Nginx概述 Nginx ("engine x") 是一个高性能的HTTP和 反向代理 服务器&#xff0c;也是一个 IMAP/POP3/SMTP服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的&#xff0c;第一个公开版本0.1.0发布于2004年10月4日。其将源代…...

Qt 第3课、Qt 中的字符串类

1、C 标准库 STL STL 是意义上需要与C 一同发布的标准库STL 是一套以模板技术完成的 C类库STL 中包含了常用的算法和数据结构STL 包含了字符串类 2、Qt 和 STL STL 的具体实现依赖于编译器生产厂商STL 的 “标准” 只是其接口是标准的 — 相同的全局函数 — 相同的算法类和数…...

Vulnhub靶场----6、DC-6

文章目录一、环境搭建二、渗透流程三、思路总结一、环境搭建 DC-6下载地址&#xff1a;https://download.vulnhub.com/dc/DC-6.zip kali&#xff1a;192.168.144.148 DC-6&#xff1a;192.168.144.154 靶机描述&#xff1a;选择带k01的密码后面会用到 访问192.168.144.154&…...

华为OD机试真题Python实现【去重求和】真题+解题思路+代码(20222023)

去重求和 给定一个数组,编写一个函数, 计算他的最大N个数和最小N个数的和, 需要对数组进行去重。 🔥🔥🔥🔥🔥👉👉👉👉👉👉 华为OD机试(Python)真题目录汇总 输入 第一行输入M,M表示数组大小 第二行输入M个数,表示数组内容 第三行输入N表示需要…...

lammps教程:Ovito选择特定晶粒的方法

大家好&#xff0c;我是小马老师。 本文介绍如何使用ovito提取特定的晶粒。 在多晶的lammps模拟中&#xff0c;可能会对某一个特定晶粒的变形情况进行分析&#xff0c;此时&#xff0c;需要找到这个晶粒&#xff0c;并进行单独分析。 ovito有专用的晶粒识别命令&#xff0c;…...

DevEco Studio 3.1 Beta1版本发布——新增六大关键特性,开发更高效

智能代码编辑、端云一体化开发、低代码开发个性化…… 六大新增关键特性&#xff0c;开发更高效&#xff0c;体验更觉妙&#xff01; 立即点击链接下载&#xff0c;做DevEco Studio 3.1 Beta1版本尝鲜者&#xff01; 下载链接&#xff1a;HUAWEI DevEco Studio和SDK下载和升级 …...