C++/语法@初始化列表

目录

  • 初始化列表
    • 特征
    • 疑惑
    • 区别
    • 必在初始化列表中初始化的三种成员变量
      • 1、引用成员变量
        • 程序例子:
        • 运行结果:
      • 2、const成员变量
        • 程序例子:
        • 运行结果:
      • 3、自定义类型成员(没有默认构造函数的类)
        • 程序例子:
        • 运行结果:
    • 总结

初始化列表

特征

以一个冒号开始 ;
以一个逗号作为数据成员的分割标志 ;
在每个成员变量后面跟个括号,括号内放入初始值或表达式 ;

如下:

class Date
{
public:
	// 构造函数
	Date(int year = 0, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)				// 初始化列表
	{}

private:
	int _year;
	int _month;
	int _day;
};

注意
1、每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

2、有3种成员变量,必须放在初始化列表位置中进行初始化。哪3种变量在下面再做讲解。



疑惑

在学习之前,有没有这么一个疑问:前面学习 构造函数 的时候,如下:

class Date
{
public:
	// 构造函数
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

  不是已经实现了对象变量的初始化了吗?怎么又蹦出来个初始化列表一说,跟前面学的构造函数的初始化有什么区别吗?

答案是,两者是有所区别的。
首先,什么叫初始化?或者说初始化的文意是什么?初始化的含义是只运行一遍、只赋值一次…,即一次以后就没有初始化的什么事了。但是在前面学习的构造函数中,如下:

	// 构造函数
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;

		_year = 2023;
		_month = 12;
		_day = 13;
	}

我们可以在构造函数里面,对要初始化的变量,多次赋值,而这明显在概念上与初始化相违背。
  确切的说,这种方式,构造函数内的语句只能称为赋初值,但不能称为初始化。因为初始化只能初始化 ( 赋值 ) 一次,而这里的构造函数体内可以多次赋值。这便是两者在概念上的差别



区别

前面了解了两种 “初始化” 概念上的的区别后,那么有个问题,在功能上、实际效果中,两者有没有什么区别呢?

我们发现,不管是使用初始化列表:

	// 构造函数
	Date(int year = 0, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)				// 初始化列表
	{}

进行初始化,还是使用:

	// 构造函数
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

这种方式实现“初始化”,这里两者在功能实现上,并没有什么区别啊。
是的,对于有些类型的成员变量,不管是放在初始化列表中进行初始化,还是放在构造函数体内进行初始化,两者在实际效果上并没有什么区别。
但是 ! 如前面特征中注意事项中的第二点提到的,有3种类型的成员变量,必须放在初始化列表中进行初始化。

下面对这3种类型依个进行学习。



必在初始化列表中初始化的三种成员变量

  在类中成员变量为以下三种类型之一时,初始化必须要在初始化列表中才能够让编译器自动去初始化变量。



1、引用成员变量

当类中的成员变量类型为引用时,如下:

程序例子:
class A
{
public:
	// 可以理解成初始化列表是对象的成员变量的定义的地方
	A(int ref = 0)
		:_ref(ref)
	{}
	void print()
	{
		cout << _ref << endl ;
	}
private:
	// 成员变量的声明
	int& _ref;		// 引用

};

int main()
{
	A a1;
	a1.print();
	return 0;
}


运行结果:

在这里插入图片描述

收入眼帘的,我们发现在初始化列表中初始化过的引用成员变量_ref 并不是我们所想的初始化为0啊。这是因为,
引用成员变量_ref在初始化列表中,引用的是构造函数中的形参,形参缺省值是0,当程序运行到初始化列表中_ref的值确实是被初始化为0的,我们通过调试可以发现,结果如下:

在这里插入图片描述


但是出了构造函数后,形参ref在构造函数作用域内时开辟的空间将随着出了构造函数的作用域而被销毁,空间使用权归还编译器,这时 _ref 引用的空间依旧是原先形参开辟的空间,但出了构造函数后那块空间已经归还编译器了,因此那块空间将是随机值,所以打印的结果是随机值。

要想如我们所构想的,给引用成员变量_ref 初始化为0,那么我们要先给 _ref 提供一个有效的对象。这个对象并不会在出了构造函数后就被销毁,如:

class A
{
public:
	static int s;
	A()
		:_ref(s)
	{}

	void print()
	{
		cout << _ref << endl;
	}
private:
	// 成员变量的声明
	int& _ref;		// 引用

};

int A::s = 0;
int main()
{
	A a1;
	a1.print();
	A::s = 10;
	a1.print();
	return 0;
}

我们在类中声明了一个静态整形变量s,然后将_ref 初始化为静态整形变量s的别名,同时在类外,对s定义初始化为0。这时就完成了对 引用成员变量 _ref 的初始化,即绑定到静态对象s的地址上。

运行结果:

在这里插入图片描述

结果显示,当我们对静态整形变量s更改时,引用成员变量 _ref 的值也跟着更改。

那么如果,我们将引用成员变量 _ref 放在构造函数体内进行初始化呢?

	static int s;
	A()
	{
		_ref = s;
	}

编译器会报错,编译不通过。具体是为什么,我是这么理解的
  首先,引用的特性是在创建时必须被初始化。在private中只是引用声明,还没有被创建,而当用类创建出对象时,编译器自动调用构造函数,对成员变量初始化。
  其次,程序是先运行到初始化列表,而后才进入函数体内的。当运行到函数体内时,引用成员变量已经被创建出来了,而被创建出来后又没有对其初始化,即给定一个对象(在C++中对象和变量是等效的说法)。这违背了引用的特性,所以编译器会报错。
所以要在引用成员变量被创建之前,即在初始化列表中,对引用成员变量进行初始化。
可以这么理解,在初始化列表中 "_ref(s) " 时,引用成员变量_ref被创建出来并且作为了静态对象s的引用。而程序运行如果出了初始化列表进入函数体内时,已经完成了对引用成员变量 _ref 的创建了。


以上,便是必须在初始化列表中初始化的成员变量类型之一,引用成员变量。接下来,对下一个成员变量类型进行学习。






2、const成员变量

程序例子:
class B
{
public:
	// 可以理解成初始化列表是对象的成员变量的定义的地方
	B(int a = 0)
		:_n(a)
	{}
	void print()
	{
		cout<< _n << endl;	
	}
private:
	// 成员变量的声明
	const int _n;   // const
};

int main()
{
	B b;
	
	return 0;
}



运行结果:

在这里插入图片描述

原因和引用成员变量类似,也是因为const关键字的特性: const 变量在声明时必须被初始化,因为它们一旦被赋值之后就不能再修改。
所以当程序跑到构造函数体内时,const修饰的成员变量 _n 已经被创建出来了,但是又没有初始化,所以编译器会报错。
因此同样的,对于const修饰的成员变量的初始化,必须在初始化列表中。

下面学习最后一种必须在初始化列表中初始化的成员变量类型。






3、自定义类型成员(没有默认构造函数的类)

对于自定义类型成员,并且自定义类型是没有默认构造函数的类,就是只有需要传参才能对对象进行初始化的构造函数。如下:

程序例子:
class A
{
public:
	A(int a)
		:_a(a)
	{}
	int Getnum()
	{
		return _a;
	}
private:
	int _a;
};


class B
{
public:
	// 可以理解成初始化列表是对象的成员变量的定义的地方
	B(int a, int ref)
		:_aobj(1)
	{}
	void print()
	{
		cout<< _aobj.Getnum() << endl;
	}
private:
	// 成员变量的声明
	A _aobj;		// 没有默认构造函数(需要传参才可以调用的构造函数)
};

int main()
{
	B b;
	b.print();
	
	return 0;
}

  程序中,类B中的有一个成员变量_aobj,类型是自定义类型类A。在类A中,有一个显示构造函数,但是因为该构造函数参数没有缺省值,因此要想调用这个构造函数时,必须得传参数。即类A中是一个没有默认构造函数(不用传参数就能调用的构造函数 )的类。

  这种情况下,要想对类B中的成员变量_aobj 初始化时,必须得放到初始化列表中进行i初始化。先看看程序运行的结果,



运行结果:

在这里插入图片描述

结果如期所至,对象_aobj 的成员变量被初始化成1。

  至于为什么没有默认构造函数的自定义类型的变量,初始化时必须放在初始化列表中,按照我的理解,和前面的原因是一致的:
因为程序跑到类B中构造函数体内时,成员变量类A实例出的对象 _aobj 已经被创建出来了,而创建出来时又没有给对象_aobj传入参数,又因为类A中没有默认构造函数,所以对象_aobj调用构造函数失败,即类B的成员变量_aobj初始化失败,导致类B实例的对象b初始化失败,因此编译器报错。

  如果还有疑问:命名函数体内已经 " _aobj(1); " 传参数初始化了啊?为什么说没给对象_aobj传参呢?
对于这个疑问,可以理解为,当程序运行到构造函数体内的 " _aobj(1);" 时( 当_aobj初始化是放在构造函数体内,而不是初始化列表时 ),对象_aobj已经在初始化列表处的位置被创建出来了, 创建出来的时候又因为没有传入参数,而对象_aobj的类型A中又没有默认构造函数,所以对于对象_aobj调用类A的构造函数进行初始化时,编译出错。


至此,对于必须放在初始化列表中进行初始化的类型,介绍完毕。


如果到此还不太理解,可以联想以上三种类型,在普通函数或者main函数中,定义(创建)时的要求是什么?可以理解为是一样的概念。如下:

class C
{
public:
	C(int c)
	: _c(c)
	{}
private:
	int _c;
}

int main()
{
	const int a;	// 编译能通过吗?
	int& b;		    // 编译能通过吗?
	class C;        // 编译能通过吗?
	
	return 0;
}

以上在main函数中,创建出来的三个变量,a、b、c 能被编译器顺利通过吗?这个问题就留给你们自己去思考了。






总结

1、对于大多数类型的成员变量,初始化时放在初始化列表也行,放在构造函数体内也罢,效果都是一样的;

2、对于以上特殊的三种成员变量类型,必须放在初始化列表中进行初始化;

注意事项
成员变量的初始化顺序就是成员变量在类中声明的顺序,跟在初始化列表中的先后次序无关



建议

1、对于所有的成员变量的初始化,都放在初始化列表中进行初始化。因为 C++ 的设计中,构造函数的初始化列表就是专门用于初始化成员变量的地方。将所有成员变量的初始化都放在初始化列表中有助于提高代码的清晰度和一致性。

2、初始化列表中成员变量初始化的先后次序,与类中成员变量的声明顺序保持一致,有助于代码的可观读性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode:2132. 用邮票贴满网格图 | 二维前缀和 + 二维差分和】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【docker 】Dockerfile指令学习

学习文档地址 上篇文章&#xff1a;【docker 】基于Dockerfile创建镜像 Dockerfile指令文档地址 .dockerignore 文件 Dockerfile指令 常见的指令 Dockerfile 指令说明FROM指定基础镜像&#xff0c;用于后续的指令构建。MAINTAINER指定Dockerfile的作者/维护者。&#xff…

伦敦金投资者的本质其实是风险管理者

长期在市场中可以稳定盈利的投资者&#xff0c;他们的秘密是什么&#xff1f;很多人以为&#xff0c;肯定是他有别人所没有的交易策略。其实并不是&#xff0c;交易技术固然很重要&#xff0c;但在持续盈利的问题上&#xff0c;技术所占的重要性是次要的&#xff0c;而主要的是…

Django 模型操作 - 多对多(九)

一、多对多关联管理器(对象调用) 前提&#xff1a;多对多&#xff08;双向均有关联管理器&#xff09;一对多&#xff08;只有多的那个类的对象有关联管理器&#xff0c;即反向才有&#xff09; 语法格式&#xff1a;正向&#xff1a;属性名反向&#xff1a;小写类名加 _set注意…

H3C ER G2系列路由器信息泄露漏洞

H3C ER G2系列路由器信息泄露漏洞 免责声明漏洞描述漏洞影响漏洞危害漏洞页面漏洞复现1. 构造poc2. 发生数据包&#xff0c;获取密码3. 登录系统 免责声明 仅用于技术交流,目的是向相关安全人员展示漏洞利用方式,以便更好地提高网络安全意识和技术水平。 任何人不得利用该文章…

详解—【C++】lambda表达式

目录 前言 一、lambda表达式 二、lambda表达式语法 2.1. lambda表达式各部分说明 2.2. 捕获列表说明 三、函数对象与lambda表达式 前言 在C98中&#xff0c;如果想要对一个数据集合中的元素进行排序&#xff0c;可以使用std::sort方法。 #include <algorithm> #i…

Java EE 多线程之多线程案例

文章目录 1. 多线程案例1.1 单例模式1.1.1 饿汉模式1.1.2 懒汉模式1.1.3 多线程下的单例模式 1.2 阻塞队列1.2.1 阻塞队列定义1.2.2 生产者消费者模型的意义1.2.4 标准库中的阻塞队列1.2.5 实现阻塞队列1.2.6 用阻塞队列实现生产者消费者模型 1.3 实现定时器1.3.1 标准库中的定…

V2X在做什么?连接未来智能出行的车联网(上)

来源&#xff1a;德思特测试测量 德思特分享丨V2X在做什么&#xff1f;连接未来智能出行的车联网&#xff08;上&#xff09; 原文链接&#xff1a;德思特分享 | V2X在做什么&#xff1f;连接未来智能出行的车联网&#xff08;上&#xff09; 欢迎关注虹科&#xff0c;为您提…

美易官方:零售销售数据提振信心

美易全球投资中心副总裁Kenny Jolin表示全球股市在经历了动荡之后逐渐恢复了稳定。最近&#xff0c;美国股市表现强劲&#xff0c;连续六天上涨&#xff0c;道琼斯指数也创下了新高。这一趋势不仅反映了投资者信心的恢复&#xff0c;也表明了全球经济正在逐渐复苏。 他说&#…

如何在Centos 7环境下安装MySQL并登录

目录 先获取MySQL官方yum源 然后正常使用yum命令下载mysql即可完成MySQL的下载 使用mysql客户端登录mysqld服务端 能够登录mysql客户端后&#xff0c;我们最后还需要做一点配置 先获取MySQL官方yum源&#xff08;包括对yum源的介绍&#xff09; 介绍一下yum源 yum源就是一…

文献管理器Zotero使用WebDAV结合内网穿透实现公网环境跨平台同步文献笔记

文章目录 一、Zotero安装教程二、群晖NAS WebDAV设置三、Zotero设置四、使用公网地址同步Zotero文献库五、使用永久固定公网地址同步Zotero文献库 Zotero 是一款全能型 文献管理器,可以 存储、管理和引用文献&#xff0c;不但免费&#xff0c;功能还很强大实用。 ​ Zotero 支…

net实践记录

文章目录 前言是否使用继承快捷输入 实体&#xff1b;引用class&#xff0c;提示有保护性System.NullReferenceException:“未将对象引用设置到对象的实例。” 总结 前言 记录使用.net 项目开发过程基础问题记录&#xff0c;便于快速回顾与查询&#xff1b; 是否使用继承 快捷…

关于git clone速度极慢的解决方法

&#xff01;&#xff01;&#xff01;&#xff01;前提条件&#xff1a;得有一个可靠且稳定的梯子&#xff0c;如果没有接下来的就不用看了 前言&#xff1a;我在写这篇文章前&#xff0c;也搜索过很多相关git clone速度很慢的解决方法&#xff0c;但是很多很麻烦&#xff0c…

Maven环境搭建及配置

Maven环境搭建及配置 1.下载部署 官方网站下载正式版的Maven文件,打开bin目录&#xff0c;复制路径然后去环境变量中的path下配置环境变量&#xff0c; 如果只有一个用户只需要在上面path配置复制的路径,当然也可以直接在下面配置,下面配置默认给所有用户都配置 设置完成打开控…

垃圾收集器及内存分配

目录 垃圾收集器种类 HotSpot虚拟机所包含的收集器 垃圾收集器部分源码 垃圾收集器后台日志参数说明与配对关系 1、串行垃圾收集器 串行垃圾收集器运行示意图 1&#xff09;、编写测试代码 2&#xff09;、设置垃圾回收为串行收集器 3&#xff09;、启动程序&#xff…

激活Windows过程及报错解决: 0x803f7001 在运行Microsoft Windows 非核心版本的计算机上, 运行“ slui.exe 0x2a 0x803f7001 “以显示错误文本

激活Windows过程及报错问题解决: 0x803f7001 在运行Microsoft Windows 非核心版本的计算机上&#xff0c;运行“ slui.exe 0x2a 0x803f7001 “以显示错误文本。 前言 最近在激活Windows过程中&#xff0c;遇到了报错: 0x803f7001 在运行Microsoft Windows 非核心版本的计算机上…

超详细 | 哈里斯鹰优化算法原理、实现及其改进与利用(Matlab/Python)

测试函数为F9 在MATLAB中执行程序结果如下&#xff1a; 在Python中执行程序结果如下&#xff1a; 哈里斯鹰优化算法(Harris Hawks Optimization , HHO)是 Heidari等[1]于2019年提出的一种新型元启发式算法&#xff0c;设计灵感来源于哈里斯鹰在捕食猎物过程中的合作行为以及突…

基础算法(3):排序(3)插入排序

1.插入排序实现 插入排序的工作原理是&#xff1a;通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已经排序的序列从后向前扫描&#xff0c;找到位置并插入&#xff0c;类似于平时打扑克牌时&#xff0c;将牌从大到小排列&#xff0c;每次摸到一张牌就插入到正确的位…

香港威雅报告:香港威雅学校入选英国《优秀学校指南》

今天&#xff0c;我们很荣幸地和大家分享一个特别的消息——香港威雅已接受了英国领先的学校审查机构——《优秀学校指南》&#xff08;The Good Schools Guide&#xff09;的全面评审。这是一家值得信赖的权威评审机构&#xff0c;相关工作人员来访并审查了我们的学校&#xf…

【Monitor, Maintenance Operation, Script code/prgramme】

Summary of M,M&O,Program JD) Monitor & M&O Symbio信必优) Job chance/opportunities on Dec 12th, 20231.1) Content 招聘JD job description:1.2) suggestions from Ms Liang/Winnie on Wechat app1.3) Java微服务是什么&#xff1f;1.3.1) [URL Java 微服务](…