深入篇【C++】类与对象:再谈构造函数之初始化列表与explicit关键字

news2024/9/30 1:28:55

深入篇【C++】类与对象:再谈构造函数之初始化列表与explicit关键字

  • Ⅰ.再谈构造函数
    • ①.构造函数体赋值
    • ②.初始化列表赋值
      • 【<特性分析>】
        • 1.至多性
        • 2.特殊成员必在性
        • 3.必走性:定义位置
        • 4.一致性
        • 5.不足性
  • Ⅱ.explicit关键字
    • ①.隐式类型转化
    • ②.作用

在这里插入图片描述

Ⅰ.再谈构造函数

我们知道在创建对象时,编译器会通过调用构造函数,给对象中各个成员变量一个合适的初始值。

也就是我们可以通过构造函数来给对象中的成员变量赋值。不过给成员变量赋值其实有两种方式,一种就是在函数体内进行赋值,另一种是在初始化列表赋值。函数体内赋值我们是知道什么意思,那什么叫初始化列表呢?
我们知道创建一个对象,什么表示对象创建出来了呢?
对象实例化表示对象已经创建出来,这是对象整体定义的地方,然后对象就会调用构造函数进行初始化。
对象定义的地方是对象实例化,实例化后就会调用构造函数初始化。
那想一想对象成员变量是在哪里定义的呢?
对象实例化只是对对象整体定义的地方,而初始化列表才是对象成员变量定义的地方。
只有定义完后才可以初始化。
所以初始化列表是对象成员定义的地方。

①.构造函数体赋值

对象实例化后就会调用构造函数初始化对象。
在函数体内部进行赋值初始化。

```handlebars

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

虽然上述构造函数调用之后,对象种已经有一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。
因为初始化只能初始化一次,但构造函数体内可以多次赋值。
本质上来说是因为对象成员变量不在函数体内定义,所以赋值后也不叫作初始化,只有在定义的地方赋值才可以叫做初始化,而对象成员变量定义的地方其实是初始化列表。

②.初始化列表赋值

初始化列表:以一个冒号开始,接着就是以一个逗号分割数据成员列表,每个成员变量后面跟上一个括号,括号里是初始值或者表达式。


class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)//这个就叫做初始化列表
		:_year(year)//以一个冒号开始,注意后面没有分号
		,_month(month)//逗号分割
		,_day(day)//每个成员变量后面都有一个括号,括号里是初始值或表达式
	{
	}
private:
	int _year;
	int _month;
	int _day;
};

【<特性分析>】

1.至多性

每个成员变量在初始化列表中至多出现一次,也可以不出现。
因为定义完后再初始化,而初始化只能初始化一次。
不出现的话那就会在函数体内进行初始化。

2.特殊成员必在性

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

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

我们一个一个分析,为什么上面三个成员必须放在初始化列表初始化。
引用成员变量和const成员变量有什么特别之处呢?为什么会被要求放在初始化列表初始化呢?
引用成员变量和const成员变量都有一个特点:那就是在定义的时候必须初始化。
不然编译器会报错,而初始化列表正是变量定义的地方,在定义的地方给初始值才能成功的对引用成员变量和const成员变量初始化。如果在函数体内部进行赋值初始值,那这样不是初始化,因为函数体内部不是它们定义的地方,仅仅给个赋值是不能完成初始化的。


class B
{
public:
	
	B(int a, int ref)//初始化列表:成员变量定义的地方
		:_ref(ref)//引用
		, _n(1)//const修饰的
		
	{
	}
private:
	int& _ref;//引用成员变量
	//这两个特征就是必须在定义的时候就初始化
	const int _n;//const修饰的成员变量
};

第三种成员变量是自定义类型成员,并且当类中没有默认构造函数时,自定义类型成员必须放在初始化列表初始化,这是为什么呢?

class A
{
public:
	A(int a=0 )//有默认构造函数
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}
private:
	int _a;
};
class B
{
public:
	//初始化列表:对象的成员定义的地方
	B(int a, int ref)
		:_ref(ref)//引用
		, _n(1)//const修饰的
	{
	}
private:
	A _obj;//有默认构造函数时,可以不用初始化
	int& _ref;
	const int _n;
	int _x = 1;
};

我们知道编译器生成的默认构造的工作是对自定义类型初始化,对内置类型不做处理。
所以当有默认构造函数时,自定义类型我们就不用去再初始化了。因为没有参数我也可以调用构造函数初始化。那没有默认构造函数呢?我们是不是就得手动给自定义类型成员变量进行初始化。但要注意的是函数体内不允许没有默认构造的自定义类型成员变量初始化,必须在初始化列表初始化。

在这里插入图片描述
道理其实是一样的,自定义类型在定义的时候也要进行初始化。
那怎么初始化呢? —调用构造函数。
如果有默认构造函数,那就可以之间使用默认构造函数初始化。
如果没有默认构造函数,那就必须在初始化列表进行初始化,因为初始化列表是成员变量定义的地方,如果要求在定义的时候进行初始化,那么必须得在初始化列表进行,函数体内部不是定义的地方,只是可以进行赋初始值,如果在函数体内部进行"初始化"其实是定义和赋初始值分开了,没有做到定义只是进行赋初始值。
而对于那些没有要求在定义时必须初始化的变量,既可以在初始化列表进行初始化,也可以在函数体内部进行初始化,可以做到定义和赋初始值分开。

class A
{
public:
	A(int a )//这个不是默认构造,这个是需要传参的构造函数
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}
private:
	int _a;
};
class B
{
public:
	//初始化列表:对象的成员定义的地方
	B(int a, int ref)
		:_ref(ref)//引用
		, _n(1)//const修饰的
		,_obj(a)//自定义类型,无默认构造。
	{
		
	}
private:
	A _obj;//没有默认构造函数时,必须在初始化列表进行初始化。
	int& _ref;//引用
	const int _n;//const修饰的
	int _x = 1;
	
};

要区分默认成员函数和默认构造函数:
1.默认成员函数是C++规定的几种特殊的成员函数,是不写编译器可以自动生成的,有默认构造函数,默认拷贝函数,默认赋值函数等等。而默认构造函数是包含在内的。
2.默认构造函数有三种:总的特征就是不用传参就可以使用的函数。
无参的构造函数+全缺省的构造函数+编译器生成的默认构造函数都叫做默认构造函数。
3.并且默认构造函数只能有一个。

所以当自定义类型成员变量类型没有默认构造函数时(三种默认构造函数,自己写的带有参数构造函数)在函数体内部赋初始值是无法通过的,必须在初始化列表显式初始化,去调用自己写的构造函数初始化。

也就是当没有提供默认构造函数的自定义类型,必须在初始化列表初始化。

3.必走性:定义位置

尽量使用初始化列表初始化,为什么呢?
因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会显示有初始化列表初始化,并且所有成员变量都会经过初始化列表的。因为初始化列表是成员变量定义的地方。
每个成员变量在初始化列表至多出现一次,但也可以不出现,不出现的意思是不给定义的变量赋初始值,但这个变量是在初始化列表定义的,只不过没有给初始值,其实所有的成员变量都会走初始化列表。

class B
{
public:
	//初始化列表:对象的成员定义的地方
	B(int a, int ref)
		:_ref(ref)//引用
		, _n(1)//const修饰的
		,_obj(a)
		,_x(2)//对于那些没有要求必须在定义时初始化的既可以在初始化列表初始化
	{
	     //_x=2;
		//也可以在函数体内部初始化
	}
private:
	A _obj;
	int& _ref;
	const int _n;
	int _x ;
	//初始化列表没有显式定义_x就会使用这个缺省值
};

比如上面的内置类型_x既可以在初始化列表初始化,又可以在函数体内部初始化,在初始化列表初始化就是定义时就初始化了,而在函数体内部初始化就是在初始化列表定义完后到函数体内赋初始值完成初始化。

class B
{
public:
	//初始化列表:对象的成员定义的地方
	B(int a, int ref)
		:_ref(ref)//引用
		, _n(1)//const修饰的
		,_obj(a)
		
	{
	  
	}
private:
	A _obj;
	int& _ref;
	const int _n;
	int _x=1 ;//这个1是缺省值,缺省值是给初始化列表的
	

你们还记得缺省值吧,C++给默认构造函数打补丁就规定了可以在成员变量声明时给缺省时,这样对于内置类型,也可以完成初始化了,那现在看来这个缺省值是如何完成初始化的呢?
其实这个缺省值就是给初始化列表用的,因为每个成员变量都会走初始化列表,初始化列表会将缺省值赋值给已经定义好的成员变量,这样成员变量就完成了初始化了。
当显式的初始化时,初始化列表会优先选取显式给的初始值,而放弃掉缺省值,当没有显式的初始化时,初始化列表就会将缺省值赋值给成员变量 。

4.一致性

成员变量在类中声明次序就是在其初始化列表中的初始化顺序。
声明的顺序对应着要初始化的顺序,必须要一致,不然会出现问题。
比如下面这个问题:

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();
}

这个结果是什么呢?
在这里插入图片描述
为什么呢?注意到成员变量声明的次序,_a2先声明,_a1后声明,则表明_a2先进行初始化,_a1后进行初始化
在这里插入图片描述

5.不足性

初始化列表的使用虽然很方便,但也有它不足之处,比如当有些赋值后的成员需要检查是否赋值成功,再比如要求对数组进行初始化,初始化列表就无法完成这样的工作。

class Stack
{
public:
	Stack(int capacity=10)
		:_a((int*)malloc(sizeof(int)* capacity))
		,_top(0)
		,_capacity(capacity)
	{
		if (_a == nullptr)
		{
			perror("malloc");

			//要求数组初始化一下
			memset(_a, 0, sizeof(int) * capacity);
		}
	}
private:
	int* _a;
	int _top;
	int _capacity
};

再比如写一个动态二维数组:初始化列表就无法完成这样的工作,必须借助函数体来解决,所以总有一些工作是初始化列表完成不了的,这时就需要和函数体一起协同工作了。

class AA
{
public:
	AA(int row = 10, int col = 5)
		:_row(row)
		, _col(col)
	{
		_a = (int**)malloc(sizeof(int*) * row);
		for (int i = 0; i < row; i++)
		{
			_a[i] = (int*)malloc(sizeof(int) * col);
		}
	}
private:
	int** _a;
	int _row;
	int _col;
};

Ⅱ.explicit关键字

①.隐式类型转化

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

这其实就是隐式类型转化,看下面代码:

int main()
{
	int i = 0;
	double d1 = i;//这里发生了什么呢?
}

这里i是int类型,d1是double类型将i赋值给d1,会发生类型转化。发生什么样子的转化呢?
其实在这个过程中会生成一个临时变量,将i赋给临时变量,临时变量再赋给d1.
在这里插入图片描述

class A
{
public:
	A(int a)
		:_a(a)
	{

	}
	A(const A& a1)
	{
		_a = a1._a;
	}
private:
	int _a;
};
int main()
{
	A aa1(1);
	A aa2 = 4;
}

对象aa1调用构造函数初始化成1,那对象aa2在干嘛呢?
对象aa2其实是将整形的4赋给aa2,这里就涉及了隐式类型转化,将int类型隐式转换成A类型。
这里其实是4调用构造函数,构造一个A类型的临时对象,然后临时对象再拷贝构造给aa2。只不过编译器将这两步优化成一步,优化成直接构造。

如果不相信的话可以看看下面的代码:

int main()
{
	A aa1(1);
	A& aa2 = 4;
}

用对象aa2来引用4可以吗?在这里插入图片描述
这里肯定不行呀,这两个类型都不一样肯定不能引用。
但只要给对象aa2前面加上const修饰那么这样就可以引用了,这是为什么呢?
在这里插入图片描述

不知道你还记得临时变量具有常性这个特点吗?
正是因为这个特点,加上const修饰后就允许引用了,因为4会调用构造函数,构造出一个A类型的临时变量,而因为临时变量具有常性,相比较正常的A类型权限缩小了,所以不能相互引用,但一旦加上const修饰后,权限相同,就可以相互引用了。
所以在隐式类型转化时会产生一个临时变量的。

②.作用

如果不想在调用构造函数时构造出一个临时变量的话,就可以用关键字explicit。

使用explicit后构造函数就无法构造出临时变量了,这样的隐式类型转化就不会发生了。

class A
{
public:
	explicit A(int a)
		:_a(a)
	{

	}
	A(const A& a1)
	{
		_a = a1._a;
	}
private:
	int _a;
};
int main()
{
	A aa1(1);
	A aa2 = 4;
}

在这里插入图片描述

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

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

相关文章

Spring Cloud Alibaba 集成 sentinel ,sentinel控制台不能检测到服务,但是在命令行配置启动参数就能看到服务

问题背景 Spring Cloud Alibaba 集成 sentinel &#xff0c;sentinel代码写的限流降级的功能都是好的&#xff0c;但是sentinel控制台不能检测到服务&#xff0c;在程序启动时配置JVM启动参数&#xff08;-Dcsp.sentinel.dashboard.serverlocalhost:18080 -Dproject.namename-…

分公司的负责人要如何承担责任

一、分公司的负责人要如何承担责任 1、分公司的负责人不需要承担责任&#xff0c;因为没有法人资格&#xff0c;没有独立的财产权。根据相关法律规定&#xff0c;分公司的债务由总公司承担连带责任&#xff0c;当然可以先由分公司的全部财产承担。 2、法律依据&#xff1a;《…

小白量化《穿云箭集群量化》(9)用指标公式实现miniQMT全自动交易

小白量化《穿云箭集群量化》&#xff08;9&#xff09;用指标公式实现miniQMT全自动交易 在穿云箭量化平台中&#xff0c;支持3中公式源码运行模式&#xff0c;还支持在Python策略中使用仿指标公式源码运行&#xff0c;编写策略。 我们先看如何使用指标公式源码。 #编程_直接使…

【2023秋招】每日一题:P1087-美团3-18真题 + 题目思路 + 所有语言带注释

2023大厂笔试模拟练习网站&#xff08;含题解&#xff09; www.codefun2000.com 最近我们一直在将收集到的各种大厂笔试的解题思路还原成题目并制作数据&#xff0c;挂载到我们的OJ上&#xff0c;供大家学习交流&#xff0c;体会笔试难度。现已录入200道互联网大厂模拟练习题&…

【C生万物】 指针和数组笔试题汇总 (下)

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; &#x1f449; 专栏&#xff1a;《C生万物 | 先来学C》&#x1f448; 前言&#xff1a; 承接上文&#xff0c;继续进行指针和数组的练习。 目录 Part2:指针笔试题 1.做题 …

硬件系统工程师宝典(24)-----如何能够正确理解三极管?

各位同学大家好&#xff0c;欢迎继续做客电子工程学习圈&#xff0c;今天我们继续来讲这本书&#xff0c;硬件系统工程师宝典。上篇我们说到不同材质的磁珠&#xff0c;频率阻抗特性不同&#xff0c;根据磁珠的频率阻抗特性曲线&#xff0c;磁珠在低频时电感起作用&#xff0c;…

Java开源工具库使用之压测JMeter

文章目录 前言一、概念原理1.1 Jmeter 原理1.2 性能测试术语1.3 web项目性能标准 二、常用元件2.1 线程组2.2 取样器2.3 控制器2.4 定时器2.5 前置/后置处理器2.6 配置元件2.7 监听器 三、插件3.1 第三方插件3.2 录制插件 四、JSR223 脚本及函数4.1 JSR223 脚本4.2 函数变量 五…

职场难题:克服压力、提升自我能力,勇闯职场巅峰

在当今激烈的职场竞争中&#xff0c;职场难题时常出现&#xff0c;如何进行有效沟通、如何应对工作压力、如何提升职业能力等&#xff0c;这些问题都是需要克服的问题。作为一名在职场中求生存&#xff0c;求发展的职业人士&#xff0c;我们必须学会适应这些挑战&#xff0c;掌…

Java基础面试题突击系列1

&#x1f469;&#x1f3fb; 作者&#xff1a;一只IT攻城狮 &#xff0c;关注我不迷路 ❤️《java面试核心知识》突击系列&#xff0c;持续更新… &#x1f490; 面试必知必会学习路线&#xff1a;Java技术栈面试系列SpringCloud项目实战学习路线 &#x1f4dd;再小的收获x365天…

面了个 Java 实习生,小伙很优秀!

大家好&#xff0c;我是鱼皮&#xff0c;前几天给自己的公司面试了一位 Java 暑期实习生&#xff0c;候选人目前是大三。 整个过程我都录屏了&#xff0c;并且在征得候选人的同意后&#xff0c;把面试过程分享出来。一方面是希望对其他在学编程找工作的小伙伴有一些启发和参考…

强化学习代码规划之深度学习预备

现在到了自动编码器和解码器&#xff0c;同样&#xff0c;先练几遍代码&#xff0c;再去理解 import torch import torch.nn as nn import torch.utils.data as Data import torchvision import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from matpl…

文心一言和ChatGPT最全对比

文心一言和ChatGPT都是基于深度学习技术的自然语言处理模型&#xff0c;有各自的优势和使用场景&#xff0c;无法简单地比较 ChatGPT 和文心一言哪一个功能更强大&#xff0c;它们各自具有优势和局限性&#xff0c;需要根据具体需求进行选择&#xff0c;以下一些具体对比&#…

HttpRunner 接口自动化测试进阶

这里我们介绍一下通过调试源码的方式来做接口测试&#xff1a; 1、获取源码 github下载或pycharm的git checkout功能去获取&#xff0c;这里直接下载 通过ide打开解压后的源码包 源码包含三层目录结构&#xff1a;docs帮助文档、httprunner核心库、tests自测文件 2、源码结…

小白量化《穿云箭集群量化》(8) 实盘核聚变氢弹策略

小白量化《穿云箭集群量化》&#xff08;8&#xff09; 核聚变策略 上一篇介绍了超级订单SuperOrder功能在股票上买入策略&#xff0c;这篇介绍MetaTrader5期货外汇的双向交易策略。 交易策略比较有名的是马丁策略&#xff0c;马丁策略是单向策略。 我们设计了双向策略原子弹策…

新来的实习生太牛了,还是我们太弱了?...

前几天有个朋友向我哭诉&#xff0c;说她在公司工作&#xff08;软件测试&#xff09;了7年了&#xff0c;却被一个实习生代替了&#xff0c;该何去何从&#xff1f; 这是一个值得深思的问题&#xff0c;作为职场人员&#xff0c;我们确实该思考&#xff0c;我们的工作会被实习…

Qt布局管理器

一、布局管理器 1.1、布局管理器的作用 布局管理器是摆放控件的辅助工具&#xff0c;主要解决组件的位置和大小无法自适应父窗口变化的问题&#xff0c;主要功能如下&#xff1a; 自动调整控件的位置&#xff0c;包括控件之间的间距、对齐等当用户调整窗口大小时&#xff0c;位…

【洛谷】P1404 平均数

【洛谷】P1404 平均数 题目描述 给一个长度为 n n n 的数列&#xff0c;我们需要找出该数列的一个子串&#xff0c;使得子串平均数最大化&#xff0c;并且子串长度 ≥ m \ge m ≥m。 输入格式 第一行两个整数 n n n 和 m m m。 接下来 n n n 行&#xff0c;每行一个整数 …

激光点云3D目标检测算法之CenterPoint

激光点云3D目标检测算法之CenterPoint 本文首发于公众号【DeepDriving】&#xff0c;欢迎关注。 前言 CenterPoint是CVPR 2021的论文《Center-based 3D Object Detection and Tracking》中提出的一个激光点云3D目标检测与跟踪算法框架&#xff0c;与以往算法不同的是&#xff…

一大波特斯拉人形机器人上线,马斯克震撼官宣2款新车!

来源 | 新智源 ID | AI-era 【导读】这次特斯拉股东日&#xff0c;虽没有新车&#xff0c;但马斯克确定Cybertruck今年一定会来。 特斯拉股东日&#xff0c;依旧没有新车。 万众瞩目的马斯克登台继续画饼&#xff0c;「我不官宣新车&#xff0c;不过新车年销量会超过500万」…

【云原生】k8sPod基础概念

k8sPod基础概念 一、Pod概述1、pod概念2、Pod资源限制 二、Pod的两种使用方式三、资源共享1、创建Pod的方式2、Pod功能 四、底层容器Pause1、Pause共享资源2、Pause主要功能3、Pod与Pause结构设计用意 五、镜像的拉取策略1、Pod容器镜像拉取策略2、Pod重启策略 六、容器的分类1…