CD24.【C++ Dev】类和对象(15) 初始化列表(下)和对象隐式类型转换

目录

1.练习题1:初始化列表中的初始化顺序

2.练习题2:成员变量声明时的缺省参数和初始化列表

问题1.下面代码的运行结果是什么?

分析

问题2:修改上方代码的Myclass带参的构造函数为下方代码,执行myobj2的构造函数时,在执行 _val = 10;前_val的值是多少?

分析

3.类型转换

观察下列代码,问myobj1和myobj2初始化有什么区别?

分析

临时对象具有常性

★结论:对象(初始值)和对象 = 初始值的区别 

4.用好隐式类型转换可以简化代码

5.explicit关键字


承接CD23.【C++ Dev】类和对象(14) 取地址重载函数和初始化列表(上)文章

1.练习题1:初始化列表中的初始化顺序

下面代码的运行结果是什么?

#include <iostream>
using namespace std;
class Myclass
{
public:
	Myclass(int data)
		:_val1(data)
		, _val2(_val1)
	{
	}
	void Print() 
	{
		cout << "_val1:"<<_val1<<endl<< "_val2:" << _val2 << endl;
	}
private:
	int _val2;
	int _val1;
};

int main() 
{
	Myclass myobj(123);
	myobj.Print();
}

分析:

先看答案再推原因:

_val2是随机值,说明myobj中先初始化_val2,再初始化_val1,1._val2(_val1)可以看出,_val2初始化的是_val1的值,但_val1此时还没有初始化,操作系统为_val1内存空间赋的是随机值,导致_val2是随机值

2._val1(data)可以看出:_val1初始化的值是data,而data值为123,因此正常打印"_val1:123"

从反汇编也可以看出:

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

建议:声明的顺序和定义的顺序保持一致

2.练习题2:成员变量声明时的缺省参数和初始化列表

问题1.下面代码的运行结果是什么?

#include <iostream>
using namespace std;
class Myclass
{
public:
	Myclass()
	{
	}

	Myclass(int data)
		:_val(10)
	{

	}
	int GetVal()
	{
		return _val;
	}
private:
		int _val = 0;
};

int main()
{
	Myclass myobj1;
	Myclass myobj2(1);
	cout << myobj1.GetVal() << endl;
	cout << myobj2.GetVal() << endl;
	return 0;
}

分析

显然构造函数Myclass被重载了,Myclass()是默认构造函数(没有传任何参数),Myclass(int data)

默认构造函数(传了参数data)

则Myclass myobj1;初始化时会调用Myclass(),初始化成员变量val时会使用int _val = 0的缺省参数0,

Myclass myobj2(1)传了一个参数,因此初始化时会调用Myclass(int data),导致_va的值l被初始化为10

运行结果:

问题2:修改上方代码的Myclass带参的构造函数为下方代码,执行myobj2的构造函数时,在执行 _val = 10;前_val的值是多少?

Myclass(int data)
{
	_val = 10;
}

分析

由CD23对缺省参数和初始化列表的关系的说明可知:所有成员变量在初始化时都要走初始化列表,_val给了缺省参数0,则在执行_val = 10前,_val的值应该为缺省参数0

可以下断点调试看看,如下:

再看看反汇编代码,更明确:

3.类型转换

观察下列代码,问myobj1和myobj2初始化有什么区别?

#include <iostream>
using namespace std;
class Myclass
{
public:
	Myclass(int data)
		:_val(data)
	{
	}
private:
	int _val;
};

int main()
{
	Myclass myobj1(1);
	Myclass myobj2 = 2;
	return 0;
}

分析

Myclass myobj1(1)是正常初始化,向构造函数Myclass正常传参数,用参数初始化_val的值:

 Myclass myobj2 = 2;在写法上与myobj1不同,2是一个整型,却要赋值给自定义类型myobj2,编译器会做如下处理:隐式类型转换,将整型转换为自定义类型,即用2去构造一个临时对象,再将这个临时对象拷贝构造给myobj2,其实是构造函数+拷贝构造函数(这个是未优化的情况)

画示意图为:

实际上较新的编译器不允许这样做,编译器会进行优化,将连续的构造转换为用2直接构造,不使用拷贝构造函数,可以手动写一个拷贝构造函数看看是否会调用:

#include <iostream>
using namespace std;
class Myclass
{
public:
	Myclass(int data)//构造函数
		:_val(data)
	{
		cout << "Myclass(int data)" << endl;
	}
	Myclass(const Myclass& myobj)//拷贝构造函数
		:_val(myobj._val)
	{
		cout << "Myclass(const Myclass& myobj)" << endl;
	}
private:
	int _val;
};

int main()
{
	Myclass myobj2 = 2;
	return 0;
}

VS2022上的运行结果: 只调用构造函数,属于优化后的

没有优化的运行结果:使用Linux g++关闭优化的指令:

g++ -fno-elide-constructors test.cpp

 (未优化:构造+拷贝构造)

临时对象具有常性

如果改成:

Myclass& myobj2 = 2;

 编译出错:

分析:用2构造临时对象,再对临时对象引用,这里需要注意:临时对象具有常性,引用需要用const修饰,改成下面这样就行了:

const Myclass& myobj2 = 2;

★结论:对象(初始值)和对象 = 初始值的区别 

之前在CC12.【C++ Cont】string类字符串的创建、输入、访问和size函数文章中提到过string类对象的两种初始化方式,如下:

string str1="hello world";
string str2("hello world");

在那篇文章中认为:两种初始化的效果一样,其实初始化的方式是有区别的

1. string str2("hello world");只调用构造函数

2. string str1="hello world";调用了构造函数拷贝构造函数,可能会被编译器优化成直接构造

4.用好隐式类型转换可以简化代码

例如写一个List类,实现向链表中尾插字符串:

#include <iostream>
#include <string>
using namespace std;
class List
{
public:
	void push_back(const string& s)
	{
		//省略具体实现代码
	}
};

int main()
{
	List ls;
    //发生隐式类型转换
    //因为push_back接收的是const string& s,是string对象的引用
    //用const修饰原因:临时对象具有常性
	ls.push_back("teststring1");

	string s("teststring2");
	ls.push_back(s);
	return 0;
}

显然ls.push_back("teststring1")利用了隐式类型转换,只需写一行,在写法上比string s("teststring2");和ls.push_back(s);简洁

5.explicit关键字

如果不想发生隐式类型转换,可以使用explicit关键字,加在构造函数的前面

explicit表明该构造函数是显式的,而非隐式的.当使用explicit修饰构造函数时,它将禁止类对象之间的隐式转换,以及禁止隐式调用拷贝构造函数

代码如下:

#include <iostream>
using namespace std;
class Myclass
{
public:
    //explicit修饰
	explicit Myclass(int data)
		:_val(data)
	{
		cout << "Myclass(int data)" << endl;
	}
	Myclass(const Myclass& myobj)
		:_val(myobj._val)
	{
		cout << "Myclass(const Myclass& myobj)" << endl;
	}
private:
	int _val;
};

int main()
{
	Myclass myobj2 = 2;
	const Myclass& myobj3 = 2;
	return 0;
}

这样编译就会报错,myobj2和myobj3都会报错,都无法创建临时对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangcoder

赠人玫瑰手有余香,感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值