[C++ 从入门到精通] 8.构造函数详解、explicit、初始化列表

2023-07-22 13:06:19
  • 📢博客主页:https://loewen.blog.csdn
  • 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
  • 📢本文由 丶布布原创,首发于 CSDN,转载注明出处🙉
  • 📢现在的付出,都会是一种沉淀,只为让你成为更好的人✨


一. 构造函数

在类中,有一种特殊的成员函数——构造函数:它的名字和类名相同,并且在创建类的对象的时候,构造函数函数会被系统自动调用

1、构造函数的目的:初始化类对象的数据成员

下面,我们定义一个public型的构造函数来感受一下构造如何初始化数据成员:

//.h
class Time
{
public:
	int Hour;
	int Minute;
	int Second;
	
public:
    //定义构造函数
    Time(int tmphour, int tmpmin, int tmpsec);
};
//.cpp 构造函数的实现
Time::Time(int tmphour, int tmpmin, int tmpsec)
{
    Hour = tmphour;
    Minute = tmpmin;
    Second = tmpsec;
}

2、构造函数的特点:

  • 构造函数没有返回值,这也是构造函数的特殊之处。
  • 不可以手动调用构造函数,否则编译会出错。
  • 正常情况下,构造函数应该被手动声明为public:因为我们创建一个类对象时,系统要替我们调用构造函数,这说明构造函数需要是一个public函数,又因为类缺省的成员是private类型的,所以我们需要在构造函数前声明为public类型的才能被系统自动调用。
  • 如果构造函数中有多个参数,则我们创建类对象的时候也要加上这些参数类型。

注:这种创建类对象调用构造函数初始化类成员的方式和上一篇我们说的对象拷贝其实差不多,但是对象拷贝调用的不是本文介绍的传统的构造函数,而是调用的拷贝构造函数(后文讲解)。


二. 构造函数多参数情况

一个类中可以有多个构造函数,多个构造函数意味着可以为类对象的创建提供了多种初始化方式(这多个构造函数之间的区别视自己的需求而定,比如构造函数参数的数量类别不同)。

继续使用上面的类Time举例:

public:
    //定义构造函数1
    Time(int tmphour, int tmpmin, int tmpsec);
    //定义构造函数2,
    Time();  //无参
//.cpp 
Time::Time(int tmphour, int tmpmin, int tmpsec)  //构造函数1的实现
{
    Hour = tmphour;
    Minute = tmpmin;
    Second = tmpsec;
}
Time::Time()  //构造函数2的实现
{
    Hour = 12;
    Minute = 27;
    Second = 35;
}
int main()
{
    Time myTime1(12,30,50);         //调用构造函数1(方式1)
    Time myTime2{ 12,30,50 };       //调用构造函数1(方式2)
    Time myTime3 = Time(12,30,50);  //调用构造函数1(方式3)
    Time myTime4 = Time{12,30,50};  //调用构造函数1(方式4)
    Time myTime5 = { 12,30,50 };    //调用构造函数1(方式5)
    
    Time myTime1;             //调用构造函数2。注意无参构造函数不用Time myTime1()创建类对象
    Time myTime2 = Time();    //调用构造函数2
    Time myTime3{};           //调用构造函数2
    Time myTime4 = {};        //调用构造函数2
    Time myTime5 = Time{};    //调用构造函数2
}

三. 函数默认参数

1、定义: 函数默认参数,即为函数的参数设定一个默认值,我们称这个参数为函数的默认参数(默认有值的参数)。

2、规定:

  • 参数的默认值只能放在.h函数声明中。除非该函数没有声明,只有定义。
  • 具有多个参数的函数中指定默认值时默认参数必须出现在非默认参数的右边,一旦某个参数开始指定默认值,它右边所有参数必须指定默认值。
  • 声明构造函数时有m个参数,n个参数有默认值(0,nullptr或其他值),这意味着定义构造函数时,默认参数可以不必须给初始值,但是非默认参数必须给初始值。

示例:

public:
    //多个参数的构造函数
    Time(int tmphour, int tmpmin = 10, int tmpsec);   //错误
    Time(int tmphour, int tmpmin, int tmpsec = 10);   //正确
Time::Time(int tmphour, int tmpmin, int tmpsec)
{
	Hour = tmphour;
	Minute = tmpmin;
	Second = tmpsec;

	cout << "Time的构造函数被调用" << endl;
	cout << "Time成员变量Hour的值" << Hour << endl;
	cout << "Time成员变量Minute的值" << Minute << endl;
	cout << "Time成员变量Second的值" << Second << endl;
}
int main()
{
    Time myTime(12,30);    //调用构造函数
}

在这里插入图片描述
可以看出,这里由于构造函数Time(int tmphour, int tmpmin, int tmpsec = 10)的3个参数中有一个默认参数int tmpsec = 10,所以我们初始化Time myTime(12,30)时,默认参数没有给值,只给出了两个非默认参数的初始值,这种情况是被允许的。


四. 隐式转换和explicit

1、隐式转换的出现

前面我们已经了解过数据转换之间的隐式转换了。下面,我们看一下构造函数中的隐式转换情况,假设构造函数只有一个参数:

public:
    //定义构造函数
    Time(int tmphour);
Time::Time(int tmphour)
{
	Hour = tmphour;
	
	cout << "Time的构造函数被调用" << endl;
}
void fun(Time tmp)
{
    return;
}
int main()
{
    Time myTime =  Time{16};  //正常初始化,调用了单参数的构造函数
    Time myTime1 = 16;        //这种含糊不清的写法(将int类型Time类型是不被允许的),但是编译器对其进行了隐式转换,使其可以正常调用构造函数
    fun(16);                  //同上,也是含糊不清的写法,存在临时对象或隐式转换的问题,编译器将实参16赋值给了形参Time tmp时,也默认做了隐式转换
}

在这里插入图片描述
我们可以看到,后面两种含糊不清的写法,其语法也没问题并且成功调用了构造函数,这是因为编译器自动对其做了隐式转换。这两种写法虽然语法没什么问题,但是总归看了会让人有一些不舒服的,那么能否要求构造函数不能做隐式转换呢?

2、禁止构造函数做隐式转换——explicit

如果构造函数声明中带有关键字explicit,则这个构造函数只能用于初始化和显式类型转换。

以带3参数构造函数为例

public:
    //3个参数的构造函数定义
    explicit Time(int tmphour, int tmpmin, int tmpsec);   //加入关键字explicit,禁止隐式转换
Time::Time(int tmphour, int tmpmin, int tmpsec)
{
	Hour = tmphour;
	Minute = tmpmin;
	Second = tmpsec;
}
int main()
{
    Time myTime1(12,30,50);         //调用构造函数1(方式1)
    Time myTime2{ 12,30,50 };       //调用构造函数1(方式2)
    Time myTime3 = Time(12,30,50);  //调用构造函数1(方式3)
    Time myTime4 = Time{12,30,50};  //调用构造函数1(方式4)
    Time myTime5 = { 12,30,50 };    //调用构造函数1(方式5)
}

在这里插入图片描述
在这里插入图片描述

我们看到,给构造函数加入限定词explicit后,调用方式5(复制列表初始化)——Time myTime5 = { 12,30,50 }报错,说明= {}方式是隐式类型转换。

以带1参数构造函数为例

public:
    //1个参数的构造函数定义
    explicit Time(int tmphour);   //加入关键字explicit,禁止隐式转换
Time::Time(int tmphour)
{
	Hour = tmphour;
}
void fun(Time tmp)
{
	return;
}
int main()
{
	Time myTime1(16);           //调用构造函数
	Time myTime2 = Time(16);    //调用构造函数
	Time myTime3{16};           //调用构造函数
	Time myTime4 = {16};        //调用构造函数,隐式类型转换
	Time myTime5 = Time{16};    //调用构造函数

	Time myTime6 = 16;          //调用构造函数(含糊不清),隐式类型转换
	fun(16);                    //调用构造函数(含糊不清),隐式类型转换
}

在这里插入图片描述
可以看到,隐式类型转换三个方式原形毕露了,可以通过上面另外四种显示类型转换的方式对他们三个进行修改:

Time myTime4 = Time{16};      
Time myTime6 = Time(16);       
fun(Time(16));                 

对于单参数构造函数,如果没有特殊理由,一般都声明为explicit,防止系统将一个数字默认隐式转换成对象(int/double)。也包括无参数和多参数的构造函数。


五. 构造函数初始化列表

上面我们通过构造函数对类成员变量初始化赋值时,是通过:

//.h
class Time
{
public:
	int Hour;
	int Minute;
	int Second;
	
public:
    //定义构造函数
    Time(int tmphour, int tmpmin, int tmpsec);
};
//.cpp
Time::Time(int tmphour, int tmpmin, int tmpsec)  
{
	Hour = tmphour;                 //初始化成员函数(赋值)
	Minute = tmpmin; 
	Second = tmpsec;
}

其实还有更推荐的初始化成员变量写法,就是构造函数初始化列表,推荐原因有两点:写法更专业,效率更高(上面构造函数多做了一个赋值的步骤)。

构造函数初始化列表写法如下:

Time::Time(int tmphour, int tmpmin, int tmpsec) : 
    Hour(tmphour),
    Minute(tmpmin),
    Second(tmpsec)                   //初始化成员函数(直接初始化,上面是省略初始化步骤,加了一个赋值步骤,虽然都是初始化成员变量,但这种效率会高一点点)
{
	//Hour = tmphour;
	//Minute = tmpmin;
	//Second = tmpsec;
}

有一点需要注意,有人有时候可能会用一个成员变量去初始化赋值另一个成员变量,如用成员变量Hour初始化Minute的值:

Time::Time(int tmphour, int tmpmin, int tmpsec) : 
    Hour(tmphour),
    Minute(Hour),
    Second(tmpsec)                   
{

}

上面有人这么写的目的是认为构造函数初始化列表先初始化的成员变量是HourHour有值了然后再将Hour的值初始化Minute,这个逻辑其实没问题。

但是要注意构造函数初始化列表先初始化谁要取决于头文件.h先定义哪个成员变量,本文先定义的int Hour,所以这么写没问题,但是如果先定义int Minute,那么构造函数初始化列表就会先初始化Minute,而这时Hour是没有值的,所以会出问题,大家注意不要用这种方式。


六. 总结

本文主要讲解的知识点如下:

  1. 构造函数的目的就是初始化类对象的数据成员
  2. 创建类对象的参数类型要和构造函数中的参数类型相匹配(顺序和个数)。
  3. 定义构造函数的默认参数(0、nullptr或其它默认值)要放到头文件中,且统一在非默认参数的右边。
  4. 其他创建类对象初始化构造函数时,头文件声明的默认参数可以不必须给初始值,但是非默认参数必须给初始值。
  5. 禁止构造函数做隐式转换(= {}初始化方式)——explicit
  6. 学会使用构造函数初始化列表的方式来初始化类成员变量。

下雨天,最惬意的事莫过于躺在床上静静听雨,雨中入眠,连梦里也长出青苔。
更多推荐

flutter聊天界面-TextField输入框buildTextSpan实现@功能展示高亮功能

flutter聊天界面-TextField输入框buildTextSpan实现@功能展示高亮功能最近有位朋友讨论的时候,提到了输入框的高亮展示。在flutterTextField中需要插入特殊样式的标签,比如:“请@张三回答一下”,这一串字符在TextField中输入,当输入@时弹出好友列表选择,然后将“@张三”高亮显

用Vite从零到一创建React+ts项目

方式一:使用create-react-app命令创建项目1、使用以下命令初始化一个空的npm项目npminit-y2、输入以下命令安装Reactnpmicreate-react-appps:如果失败的话尝试(1:使用管理员身份执行命令(2:切换镜像重试3、输入以下命令创建项目create-react-app项目文件夹名

flask 插件 Flask-RESTful

1、安装pipinstallflask-restful2、使用HelloWorld一个简单的例子:#-*-coding:utf-8-*-fromflaskimportFlaskfromflask_restfulimportResource,Apiapp=Flask(__name__)api=Api(app)classH

解决react使用redux toolkits时出现的数组对象长度始终为0的怪异问题

有个react项目在添加购物车后,立马白屏,看一下console报错propertiesofundefined(readinglength)那意思是说数组没有长度,然后定位Header.tsx的182行,果然是数组长度报错回到具体代码中:发现shoppingCartItems实际是通过reduxToolkit(RTK)

大模型从入门到应用——LangChain:代理(Agents)-[工具(Tools):人工确认工具验证和Tools作为OpenAI函数]

分类目录:《大模型从入门到应用》总目录LangChain系列文章:基础知识快速入门安装与环境配置链(Chains)、代理(Agent:)和记忆(Memory)快速开发聊天模型模型(Models)基础知识大型语言模型(LLMs)基础知识LLM的异步API、自定义LLM包装器、虚假LLM和人类输入LLM(HumanInpu

Pytorch-YOLOv4梳理——原理和复现

yolov1到yolov3的梳理:YOLO总结,从YOLOv1到YOLOv3_追忆苔上雪的博客-CSDN博客首先说一点,就是yolov4的分支有点多,先梳理一下出现的顺序。AlexeyBochkovskiy提出了YOLOv4然后针对YOLOv4的模型缩放(modelscale),提出了Scaled-YOLOv4Scal

黑马JVM总结(八)

(1)StringTable面试题1.81.6时(2)StringTable的位置jvm1.6时StringTable是常量池的一部分,它随着常量池存储在永久代当中,在1.7、1.8中从永久代变成了堆中,为什么做这个更改呢?因为永久代的内存效率很低,永久代是在FullGC的时候才会触发永久代的垃圾回收,FullGC只有

Archicad 26 for Mac - 打造卓越的3D建模工具

随着建筑设计和规划的日益复杂化,寻找一款功能强大且易于使用的3D建模工具变得至关重要。而Archicad26forMac正是您在建筑设计领域中的理想选择。无论您是一名建筑师、室内设计师还是建筑工程师,Archicad26都将成为您的得力助手。作为一款全球领先的BIM(建筑信息模型)软件,Archicad26forMac

Spring 6.0 新特性

文章目录Spring的发展历史AOTGraalVMSpringBoot实战AOTRuntimeHints案例分析RuntimeHintsRegistrarSpringBoot中AOT核心代码Spring的发展历史AOTSpring6.0的新特性AheadofTime(AOT)编译是一种技术,可以提前将Spring应用程

Jmeter常用线程组设置策略

一、前言​在JMeter压力测试中,我们时常见到的几个场景有:单场景基准测试、单场景并发测试、单场景容量测试、混合场景容量测试、混合场景并发测试以及混合场景稳定性测试在本篇文章中,我们会用到一些插件,在这边先给大家列出:​CustomThreadGroups插件PS:在我们正式测试中,统一使用非GUI界面运行,只有在调

对比接口测试工具在自动化测试优缺点:Jmeter、Python、Postman

一、JMeter总结:适合对代码不敏感的使用人员,不会代码也可以完成接口自动化,设计框架。适合紧急迭代的项目。JMeter接口测试的优势小巧轻量级,并且开源免费,社区接受度高,比较容易入门支持多协议,并提供了比较高级的扩展能力,允许自己定义和扩展新的协议支持,比如扩展支持阿里提供的Dubbo协议的JMeter插件等学习

热文推荐