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

C++入门-----初始化列表

学习目标

    • 1. 初始化列表
      • 1.1 是什么
      • 1.2 为什么
      • 1.3 怎么做
    • 2. 注意的点
    • 3. explicit关键字

1. 初始化列表

1.1 是什么

与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。

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

1.2 为什么

如果该类增加一个常量

class Date
{
//省略
private:
	int _year;
	int _month;
	int _day;
	const int _t;
}

要如何对_t进行初始化?如果是调用构造函数,那么在进入构造函数的时候建立栈帧,_t也随之被定义,常量被定义后是无法进行修改的,因为常量的初始化只能有一次。

所以就引用了初始化列表,在其定义的时候便进行初始化:

class Date
{
public:
	//初始化列表,成员变量定义的地方
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
		,_t(10)
	{}
private:
	int _year;
	int _month;
	int _day;
	const int _t;
};
int main()
{
	Date d1(200011);
	return 0;
}

也可以只处理_t

Date(int year, int month, int day)
		:_t(10)
{}

C++中没有规定一定要使用初始化列表,这样初始化的方式和构造函数的区别就是:

int x = 1;//定义的时候初始化
int x;//先定义再初始化
x = 1;

1.3 怎么做

类中包含以下成员,必须放在初始化列表位置进行初始化

1. 引用成员变量
2. const成员变量
3. 自定义类型成员(且该类没有默认构造函数时)

引用和const成员变量没有默认构造函数的自定义类型成员,都在定义的时候必须初始化。

建议是自定义类型成员都用初始化列表。当然也可以在构造函数和内置类型一起初始化。

  • 举例

如下面的例子:

定义一个A类及其对象_a,分别写出其拷贝构造,默认构造和赋值操作符函数。让_a在Date的构造函数中一起初始化,然后观察打印的结果:

  1. 当没有写初始化列表时:
class A
{
public:
	A(const A& _a)
	{
		cout << "A的拷贝构造" << endl;
	}
	A(int _a = 0)
	{
		cout << "A的默认构造" << endl;
	}
	A& operator=(const A& _a)
	{
		cout << "A的赋值操作符" << endl;
		return *this;
	}
 };
class Date
{
public:
	Date(int year, int month, int day, const A& a)
	{
		_a = a;
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
	A _a;
	//const int _t;
};
int main()
{
	Date d1(2000,1,1,10);
	return 0;
}

在这里插入图片描述

可以看见,调用了两次A的默认构造和一次A的赋值操作符

  1. 当写了初始化列表时
	Date(int year, int month, int day, const A& a)
		:_year(year)
		, _month(month)
		, _day(day)
		, _a(a)
	{}

在这里插入图片描述
这时候是一次默认构造一次拷贝构造,没有赋值运算符。

  • 原因解析
  1. 没有写初始化列表

在这里插入图片描述

  1. 写了初始化列表

在这里插入图片描述

写了初始化列表以后就只有初始化的默认构造和对象与对象之间的拷贝构造
减少了赋值、拷贝操作。

  • 结论

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

2. 注意的点

  • 注意

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

观察下列代码

class A
{
public:
  A(int a)
   :_a1(a)
   ,_a2(_a1)
 {} 
  void Print() {
    cout<<_a1<<" "<<_a2<<endl;
}
private:
  int _a2;
  int _a1;
};
int main() {
  A aa(1);
  aa.Print();
}

结果会输出1和随机值。

由于生命顺序是先_a2再_a1,所以其定义的顺序也应该是这样,所以先执行_a2(_a1)此时_a1还未初始化所以_a2保存的是随机值,然后再执行_a1(a),_a1变成1。

3. explicit关键字

  • 作用

单参构造函数,没有使用explicit修饰,具有类型转换作用;用explicit修饰构造函数,将会禁止构造函数的隐式转换。

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。

例如

class A
{
public:
	A(const A& _a)
	{
		cout << "A的拷贝构造" << endl;
	}
	A(int _a = 0)
	{
		cout << "A的默认构造" << endl;
	}
private:
	int _a;
 };
int main()
{
	A a1(2000);
	A a2 = 2000;
	return 0;
}

注意到这句代码

A a2 = 2000;

2000是int类型,a2是A类,为什么可以相互转换?这里其实可以类比于这句代码

int* p = NULL;
int a = (int)p;

期间生成了一个临时变量,把(int)p的值给到该临时变量,然后再赋值给a。

上面的代码也同理,用一个整形变量给A类型对象赋值,实际编译器背后会用2000构造一个无名对象,最后用无名对象给d1对象进行赋值

2000构造出来的临时无名对象A(2000)是一个构造过程,再用这个对象拷贝构造给a2。编译器会默认将连续构造的函数过程合二为一,也就是说上述两个构造过程最终合并成一个默认构造。

explicit修饰构造函数,禁止类型转换—explicit去掉之后,代码可以通过编译

explicit A(int _a = 0)
{
	cout << "A的默认构造" << endl;
}

在这里插入图片描述

相关文章:

  • JavaScript—正则
  • 草莓熊python turtle绘图代码
  • 5.3 马氏链-常返和非常返(Durrett)
  • 【已解决】利用 Java 多线程并发编程提高数据处理效率
  • Spring日志引用原理
  • 项目实战 | 基于RK3566开发板实现USB摄像头推流(ffmpeg+nginx)
  • 进程的程序替换
  • 9. SQL中Insert into/Update/Delete的用法
  • 【webGoat】Path traversal
  • 前后端分离的项目——图书管理系统
  • 【C++修炼秘籍】类和对象(一)
  • 【已解决】利用 Java 多线程并发编程处理数据的实践记录
  • Roson的Qt之旅 #125 QNetworkCookie(网络Cookie)
  • SpringMVC入门案例的步骤
  • 【云原生 | 33】Docker快速部署主流编程语言C/C++
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • 【剑指offer】让抽象问题具体化
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • 4个实用的微服务测试策略
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • centos安装java运行环境jdk+tomcat
  • java小心机(3)| 浅析finalize()
  • JSONP原理
  • Laravel核心解读--Facades
  • nginx 配置多 域名 + 多 https
  • Promise初体验
  • redis学习笔记(三):列表、集合、有序集合
  • scala基础语法(二)
  • SpringBoot几种定时任务的实现方式
  • XML已死 ?
  • 浮现式设计
  • 小程序01:wepy框架整合iview webapp UI
  • 小程序开发之路(一)
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • 关于Android全面屏虚拟导航栏的适配总结
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • 进程与线程(三)——进程/线程间通信
  • ​Distil-Whisper:比Whisper快6倍,体积小50%的语音识别模型
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • #if 1...#endif
  • $refs 、$nextTic、动态组件、name的使用
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (二)换源+apt-get基础配置+搜狗拼音
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • (转)chrome浏览器收藏夹(书签)的导出与导入
  • (转)大型网站的系统架构
  • (转载)CentOS查看系统信息|CentOS查看命令
  • .gitignore文件—git忽略文件
  • .NET 2.0中新增的一些TryGet,TryParse等方法
  • /run/containerd/containerd.sock connect: connection refused