构造函数详解

news2025/7/21 9:28:31

构造函数详解

  • 1.构造函数的概念与特性
  • 2.默认构造函数
    • (1)概念
    • (2)分类
    • (3)工作原理
  • 3.初始化列表
    • (1)定义
    • (2)为什么使用初始化列表
    • (3)必须使用初始化列表的情况:
  • 4.构造函数的使用

如果一个类中什么成员都没有,那么该类简称为空类。而空类中其实并不是真的什么都没有,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

  • 构造函数:主要完成初始化工作
  • 析构函数:主要完成资源的清理工作
  • 拷贝构造函数:主要用于使用同类对象初始化创建对象
  • 赋值运算符重载:主要是把一个对象赋值给另一个对象
  • 普通对象取地址重载
  • const对象取地址重载(取地址重载很少会自己实现)

本篇文章,我们来学习构造函数

1.构造函数的概念与特性

构造函数的概念:

对于以下Date类:

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.Init(2022, 7, 5);
	d1.Print();
	Date d2;
	d2.Init(2022, 7, 6);
	d2.Print();
	return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,我们可以使用构造函数在对象创建时就将信息设置进去。

构造函数是一个特殊的成员函数,名字与类名相同,用于给对象的成员变量赋初始值,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

构造函数的特性:

函数名与类名相同。

无返回值,但也没有被声明为void类型

对象实例化时编译器自动调用对应的构造函数。

构造函数可以重载,编译器根据参数选择调用合适的构造函数

构造函数可以无参,也可以带参。如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

class Date
{
public:
	// 1.无参构造函数
	Date()
	{}
	// 2.带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
void TestDate()
{
	Date d1; // 调用无参构造函数
	Date d2(2015, 1, 1); // 调用带参的构造函数
	Date d3();//因为通过无参构造函数创建对象时带了括号,这成了函数声明,声明了d3函数,该函数无参,返回一个日期类型的对象,会报错
	// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
}

2.默认构造函数

(1)概念

如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义了构造函数,编译器将不再自动生成无参的默认构造函数,我们就必须自己为它提供默认构造函数

class Date
{
public:
	// 如果用户显式定义了构造函数,编译器将不再生成
	Date(int year, int month, int day)
	{
	_year = year;
	_month = month;
	_day = day;
	}
	
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成无参的默认	构造函数
// 放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
	Date d1;
	return 0;
}

如果我们提供了构造函数而没有提供默认构造函数,那么下面的声明将会出错

Date d;

因为我们显示定义了构造函数,此时编译器不再自动生成无参的默认构造函数,我们也没有为它提供构造函数,此时编译器就会找不到合适的构造函数,因此会出错。这样做的目的也有可能是禁止使用者创建未明确初始化的对象

如果要创建对象而不显示地初始化,则必须定义一个不接受任何参数的默认构造函数。默认构造函数有两种,一种是全缺省地默认构造函数,另一种是无参的默认构造函数。

(2)分类

无参构造函数、全缺省构造函数、我们没写编译器默认生成的无参构造函数,都是默认构造函数,总的来说默认构造函数有两种,分别是无参构造函数和全缺省构造函数,建议使用全缺省默认构造函数,因为它很灵活,你可以传参数,也可以只传部分参数,也可以不传参数。默认构造函数只能有一个。

以下测试函数能通过编译吗?

class Date
{
public:
	Date()
	{
		_year = 1900;
		_month = 1;
		_day = 1;
	}
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
private:
	int _year;
	int _month;
	int _day;
};

void Test()
{
	Date d1;
}

显然是不可以通过编译的,因为编译器会调用默认构造函数,但是编译器不知道该调用哪个默认构造函数。

在这里插入图片描述

(3)工作原理

在不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数似乎没有什么用,d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。

默认构造函数真的没有用吗?

C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的基本数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型。

默认构造函数对内置类型成员变量不做处理,C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

默认构造函数对于自定义类型成员变量才会处理,它会去调用自定义类型的默认构造函数。

何时使用默认构造函数就够了,何时需要自己实现构造函数呢?

满足下列两个条件使用默认构造函数就够了:

  • 如果一个类中的成员全是自定义类型,或者内置类型成员在声明时已经给了缺省值,即无需对内置类型成员做处理。
  • 自定义成员都提供了默认构造函数

如果有内置类型的成员变量且需要显示传参初始化,或者自定义类型成员没有构造函数,那么就要自己实现构造函数。

我们运行下下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

在这里插入图片描述

3.初始化列表

(1)定义

与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。初始化列表是成员变量定义的地方,也就是为其开辟空间的地方。

具体我们来看下面代码,我们有一个日期类对象,我们可以这样来定义构造函数:

class Date                                                                                                                                                   
{                                                                                                                                                            
  public:                                                                                                                                                    
    Date(int year,int month,int day)                                                                                                                         
    {                                                                                                                                                        
      _year = year;                                                                                                                                          
      _month = month;                                                                                                                                        
      _day = day;                                                                                                                                            
    }                                                                                                                                                        
  protected:                                                                                                                                                 
    int _year;                                                                                                                                               
    int _month;                                                                                                                                              
    int _day;                                                                                                                                                
};         

也可以利用初始化列表:

class Date                                                                                                                                                   
{                                                                                                                                                            
  public:                                                                                                                                                    
    Date(int year,int month,int day)                                                                                                                         
      :_year(year),
      _month(month),
      _day(day)  //使用初始话列表初始化成员变量                                                                                                                 
    {                                                                                                                                                                                                                                                                                                                                                                                                              
    }                                                                                                                                                        
  protected:                                                                                                                                                 
    int _year;                                                                                                                                               
    int _month;                                                                                                                                              
    int _day;                                                                                                                                                
};         

(2)为什么使用初始化列表

如上两种方法来定义构造函数,一种是在构造函数体内对成员变量进行初始化,一种是使用初始化列表来初始化成员变量,都可以达到初始化成员变量的效果,两者有什么区别呢?

假如有如下需求,我们需要将日期类拓展为时间单位更精确。

//定义一个时间类
class Time
{
  public:
    Time(int hour = 0,int minute = 0,int second = 0)
    {//定义一个全缺省构造函数
      _hour = hour;
      _minute = minute;
      _second = second;
      cout<<"Time();"<<endl;
    }
  protected:
    int _hour;
    int _minute;
    int _second;
};


class Date
{
  public:
    Date(int year,int month,int day,int hour,int minute,int second)
    {
      _year = year;
      _month = month;
      _day = day;
      _time = Time(hour,minute,second);
    }
  protected:
    int _year;
    int _month;
    int _day;
    Time _time;//时间类作为日期类的成员变量
};

int main()
{
    Date d(1999, 3, 26, 12, 18, 24);
    return 0;
}

运行结果如下:

在这里插入图片描述

在创建一个Time类匿名变量来对_time 进行初始化时,这里的完成动作应该是调用一次构造函数,而运行出来结果却是两次。

这是因为所有构造函数都要走初始化列表,初始化列表会给每个成员都进行一次初始化,如果你的初始化列表显示初始化了某个成员,它就会去初始化,如果你没有显式初始化某个成员,它也会初始化,对于内置类型的成员变量,它会按照默认值进行初始化,如果没有设置默认值,就会用随机值进行初始化,对于自定义类型成员会调用它的默认构造函数进行初始化。

也就是说尽管你在函数体内进行初始化操作,实际上人家已经在初始化列表中就已经初始化过了,所以还不如在初始化列表中直接进行初始化操作,这样就可以减少默认构造函数调用的次数,这样就会更高效一些。

(3)必须使用初始化列表的情况:

除了性能问题之外,有些时候合初始化列表是不可或缺的,以下几种情况时必须使用初始化列表:

  1. 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
  2. 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
  3. 没有默认构造函数的类类型,因为当你没有在初始化列表中对类类型成员进行初始化的时候,编译器会调用默认构造函数在初始化列表中来对其进行初始化,如果我们没有默认构造函数,那么就必须在初始化列表中调用其它构造函数来对该类类型的成员进行初始化,否则编译器调用默认构造函数在初始化列表中对其进行初始化的时候就会报错。

4.构造函数的使用

我们也无法像调用成员函数那样使用对象来调用构造函数,因为在构造函数构造出对象之前,对象是不存在的,因此构造函数被用来创建对象,而不能通过对象来调用。

C++提供了多种初始化对象的方式。

第一种方式是隐式地调用构造函数:

其功能可以描述为创建一个名为d的Date对象,并将其数据成员初始化为指定的值。

Date d(1999,3,26);

第二种方式是显示地调用构造函数:

这种方法不是对d进行初始化,而是将新值赋值给它。通过让构造函数创建一个新的临时对象,然后将其内容复制给d,然后程序调用析构函数,以删除该临时对象。(有些编译器可能要过一段时间才会删除临时对象,因此析构函数的调用将延迟)

Date d = Date(1999,3,26);

如果既可以通过初始化,也可以通过赋值来设置对象的值,则应采用初始化方式。通常这种方式的效率更高。

第三种方式是结合new运算符和隐式调用:

Date* d = new Date(1999,3,26);

第四种方式是使用列表初始化:

只要提供与某个构造函数的参数列表相匹配的内容,并用大括号将它们括起(C++11):

Date d = {1999,3,26};
Date d {1999,3,26};
Date* d = new d{1999,3,26};

这些列表与下面的构造函数相匹配:

Date(int year,int month,int date);

无参的时候不可以像上面那样隐式地调用构造函数,因为编译器无法区分这是函数的声明还是对象的定义。

Date d();//error
Date d;//correct

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/17121.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WebRTC系列<五>我与一位大佬的聊天记录

原本打算想用webrtc部署虚幻项目。后来在了解虚幻过程中&#xff0c;得知虚幻有像素流插件&#xff0c;导出项目里带有STUN和TURN服务&#xff0c;但是在webGL项目里比如three.js、babylon.js如果也能部署在服务器端&#xff0c;那就厉害了&#xff0c;也很有想象力空间。 基本…

表白墙网站练习【前端+后端+数据库】

表白墙网站练习【前端后端数据库】 开发该表白墙&#xff08;简单网站&#xff09;的基本步骤&#xff1a; 1.约定前后端交互接口 2.开发服务器代码 编写Servlet能够处理前端发来的请求编写数据库代码&#xff0c;来获取/存储关键数据 3.开发客户端代码 基于ajax能够构造请…

一体化Ethercat通信伺服电机在汇川H5U PLC上的应用案例介绍(上)

内容介绍了一体化低压伺服Ethercat通信的电机在汇川H5UPLC上的使用&#xff0c;本篇主要讲解环境的搭建以及使用AutoShop软件的在线调试功能&#xff0c;简单控制电机位置、速度模式运行&#xff1b; 一、系统构成 本系统主要构成是电脑&#xff0c;H5U-1614MTD-A8&#xff0c;…

家长杂志家长杂志社家长编辑部2022年第30期目录

卷首语 读懂童心,营造乐学趣学好场景 本刊编辑部; 1 本刊视线_关注《家长》投稿&#xff1a;cn7kantougao163.com 留守儿童学习动力不足的成因与激发策略 蔡斌林; 4-6 农村留守儿童加强心理健康教育的策略 张芸; 7-9 本刊视线_学校体育 中学体育线上线下教学融…

【Struts2框架】idea快速搭建struts2框架

文章目录什么是SSH框架&#xff1f;Struts2框架1、struts2的环境搭建1.1 创建web项目&#xff08;maven&#xff09;&#xff0c;导入struts2核心jar包1.2 配置web.xml&#xff08;过滤器&#xff09;&#xff0c;是struts2的入口&#xff0c;先进入1.3 创建核心配置文件struts…

STM32 Bootloader开发记录 3 固件签名校验

STM32 Bootloader开发记录 3 固件签名校验 文章目录STM32 Bootloader开发记录 3 固件签名校验1. 移植mbedtls1.1 编译mbedtls1.2 修复rsa_sign的一个bug1.3 测试RSA1.3.1 **RSA加解密&#xff1a;**1.3.2 **RSA签名验签&#xff1a;**1.3.3 **生成秘钥对**1.4 移植到STM321.4.1…

NFV中:DPDK与SR-IOV应用场景及性能对比

DPDK与SR-IOV两者目前主要用于提高IDC&#xff08;数据中心&#xff09;中的网络数据包的加速。但是在NFV&#xff08;网络功能虚拟化&#xff09;场景下DPDK与SR-IOV各自的使用场景是怎样的&#xff1f;以及各自的优缺点&#xff1f; 本文主要通过从以下几点来阐述这个问题&a…

视觉SLAM十四讲(高翔版本),ch4章节部分笔记

目标&#xff1a;理解slam的框架以及它的理论知识。供以后自己查阅。 这一章主要非常重要&#xff0c;也是理解后续优化的基础&#xff0c;它是将旋转矩阵和平移向量&#xff0c;转化为李代数的形式进行优化&#xff0c;因为它有很多好处。好处如下&#xff1a; 意思就是采用…

Linux硬盘垃圾清理心得

最近有台系统盘才10G的服务器咔咔报警&#xff0c;一共才10G的空间&#xff0c;运维还设置了80%的报警阈值&#xff0c;实在难顶。为了清理硬盘里的垃圾&#xff0c;敲了不少命令&#xff0c;怕以后忘了&#xff0c;记录一下。 首先输入df -h查看一下硬盘空间占用情况&#xf…

呼叫中心中间件(mod_cti基于FreeSWITCH)-通话记录(CDR)接口

支持把FreeSWITCH的通话记录写入mysql,sqlserver,oracle等数据库&#xff0c;也可以写入redis的list&#xff0c;或者PUBLISH到redis的channel,方便业务程序实时获取通话记录。 使用说明 如果一个通话是A呼叫B&#xff0c;那么就有2个通话记录&#xff0c;一个叫aleg,一个叫b…

Vue3 - 全局指令(详细教程)

前言 咱们在真实项目开发中&#xff0c;其实有很多指令都是通用的。我们绝对不可能去每个页面都定义一次&#xff0c;这样不仅写起来困难&#xff0c;维护起来更是困难&#xff0c;你想一下&#xff0c;假设稍微变点逻辑&#xff0c;你就需要翻阅好几个文件去改。 其实用法和局…

家用吸尘器的总体结构设计

目 录 摘 要 i Abstract ii 1 引言 1 2 家用吸尘器的历史及发展 2 2.1 家用吸尘器的历史 2 2.2 业界的发展情况 3 3 家用吸尘器的分类 5 3.1 卧式&#xff08;Canister&#xff09; 5 3.2 立式&#xff08;Upright&#xff09; 5 3.3 手持式 &#xff08;Handy&#xff09; 6 3…

ON1 NoNoise AI 2023:AI智能摄影降噪工具

ON1 NoNoise AI 2023中文版是一款强大的AI智能摄影降噪工具&#xff01;使用 AI 驱动的 NoNoise AI 快速去除噪点并获得照片中最清晰的细节。 更快地获得绝对最佳结果&#xff01; ON1 NoNoise 比其他领先的图像去噪产品快十倍&#xff0c;结果会让您大吃一惊&#xff01; 基于…

HI3516DV300 图像输入

HI3516DV300 图像输入 易百纳的一个开发板&#xff0c;以及GC2053的摄像头。 硬件 海思 海思sensor接口如下&#xff0c;用的是差分信号&#xff0c;共4对数据线&#xff08;或者说4条lane&#xff09;&#xff0c;两对差分时钟。 连接器 海思核心板和扩展板之间通过板件连…

小啊呜产品读书笔记001:《邱岳的产品手记-06》11讲 如何借鉴灵感 12讲 产品案例分析:LabRdr的设计实验

小啊呜产品读书笔记001&#xff1a;《邱岳的产品手记-06》11讲 如何借鉴灵感 & 12讲 产品案例分析&#xff1a;LabRdr的设计实验一、今日阅读计划二、泛读&知识摘录1、11讲 如何借鉴灵感&#xff1f;2、12讲 产品案例分析&#xff1a;LabRdr的设计实验三、头脑风暴叮嘟…

【计算机毕业设计】外卖点餐源码

一、系统截图&#xff08;需要演示视频可以私聊&#xff09; 摘 要 民以食为天&#xff0c;外卖点餐系统餐饮业一直是与人们日常生活息息相关的产业。传统的电话外卖点餐或者到店消费已经不能适应市场发展的需求。随着网络的迅速崛起&#xff0c;互联网日益成为提供信息的最佳…

电话机器人详解,电销机器人获客的正确姿势是什么?

电话机器人详解&#xff0c;电销机器人获客的正确姿势是什么&#xff1f; 相信很多没接触过电话机器人的人都会好奇。 首先是预设Q&A问答主流程 通过关键词辅助指导&#xff0c;终端在与客户互动时&#xff0c;会智能地获取相应的关键词库&#xff0c;逐一回答客户的问题…

ISACA证书维持| 内附QA

Q如何维持证书 A证书的维持主要由两部分组成&#xff0c;即维持费以及CPE&#xff08;继续职业教育&#xff09;学时。ISACA会在每个日历年的第三季度以电子邮件及印刷版的方式向所有持证人员发送次年费用清单通知。年度维护费支付可在 https://www.isaca.org/renewals 上在线…

完全背包问题的解决方法______闫氏 DP 分析法

完全背包问题的解决方法&#xff1a; 闫氏 DP\ DPDP 分析法: 通过集合划分&#xff0c;我们可以得到第 i\ ii 个物品有两种状态&#xff1a;  1.选 1−T\ 1 - T1−T 个&#xff0c;最优解为前 i−1\ i− 1i−1 个物品的所有选择中&#xff0c;还能选取当前 k\ kk 个第i\ ii 个…

模式识别与机器学习 第一章:绪论

一、基础概念 样本: 所研究对象的单个个体、实例。样本集: 若干样本的集合。类或类别: 在所有样本上定义的一个子集&#xff0c;处于同一类的样本具有相似的性质&#xff0c;即具有相同的模式。特征: 用于表征样本的观测&#xff0c;也称属性。通常是数值表示的某些量化特征&a…