3-6:类与对象下篇——构造函数中的初始化列表匿名对象和explicit关键字

Posted 快乐江湖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了3-6:类与对象下篇——构造函数中的初始化列表匿名对象和explicit关键字相关的知识,希望对你有一定的参考价值。

(1)真的是初始化吗

上一篇在说构造函数的时候,我们把构造函数的作用称之为在对象被实例化的同时初始化,也就是下面这样

class Date
{
public:
	Date(int year=1998,int month=12,int day=20)
	{
		_year=year;
		_month=month;
		_day=day;
	}

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

int main()
{
	Date d1(2000,2,2);
}

上述这样操作的确“初始化”了,因为我们看到它确实给d1对象了一个值。
但是明确点说,这样的初始化顶多称之为“伪初始化”

因为 初始化只能初始化一次,但是构造函数内可以多次赋值,所以这样的方式称为构造函数体赋值

一个典型的例子是const对象,如下,如果把一个int修饰为const,那么这个const int必须在定义的时候就初始化,任何后续的赋值将不被允许

int main()
{
	//定义的时候就必须初始化
	const int a=10;
	//这种方式错误
	const int a;
	a=10;

}

返回到类,如果在之前的成员中加上一个const成员,如果报错说明这种方式就不是初始化

class Date
{
public:
	Date(int year=1998,int month=12,int day=20,int n=100)
	{
		_year=year;
		_month=month;
		_day=day;
		_n=n;
	}

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

int main()
{
	Date d1(2000,2,2,200);
}

答案显而易见。
在这里插入图片描述
所以对于这类成员,在对象生成的时候同时就要进行初始化,所以引入真正的初始化的方式——初始化列表

(2)初始化列表

A:概述

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员列表由“成员变量”后面跟上一个放在括号中的“初值或表达式构成”
(初始化列表就是不在括号内进行初始化)

就拿上面报错的那个例子,使用初始化列表应该是下面这样的


class Date
{
public:
	Date(int year, int month, int day,int n)//这样写
	
	
		:_year(year)
		,_month(month)
		, _day(day)
		,_n(n)
	{}
	void print()
	{
		std::cout << _year << "-" << _month << "-" << _day << "-" << _n << std::endl;
	}
private:
	int _year;
	int _month;
	int _day;

	const int _n;
};

int main()
{
	Date d1(2000, 2, 2,200);
	d1.print();
}

在这里插入图片描述

  • 每个成员变量在初始化列表只能出现一次(因为初始化本质就是只能初始化一次)

所以这样的初始化才叫真正的初始化,或许我写成下面这样,大家体会才能更深
在这里插入图片描述

B:哪些成员必须在初始化列表进行初始化

既然本段标题发出这样的疑问了,那么就表明有些必须在初始化列表(括号外)初始化,有些可以不在初始化列表初始化。很明显上面的const的成员必须在初始化列表初始化,那么这个例子中其他变量可以放在括号内,也就是可以写成这样

class Date
{
public:
	Date(int year = 1998, int month = 12, int day = 20, int n = 20)
		:_n(n)//const类必须放在初始化列表
	{
		_year = year;
		_month = month;
		_day = day;
	
	}
	void print()
	{
		std::cout << _year << "-" << _month << "-" << _day << "-" << _n << std::endl;
	}
private:
	int _year;
	int _month;
	int _day;

	const int _n;
};

int main()
{
	Date d1(2000, 2, 2);
	d1.print();
}

在这里插入图片描述
除了const外,引用成员初始化必须放在初始化列表
下面的例子中引用了year

class Date
{
public:
	Date(int year = 1998, int month = 12, int day = 20)
		:_ref(year)//引用year
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		std::cout << _year << "-" << _month << "-" << _day << "-" << _ref << std::endl;
	}
private:
	int _year;
	int _month;
	int _day;

	int& _ref;//引用
};

int main()
{
	Date d1;
	d1.print();
}

在这里插入图片描述

  • 之所以输出随机值是因为引用的是形参,在调用下次print时已经销毁了。

还有就是自定义类型也必须放在初始化列表

下面增加一个类,这个类的构造函数没有设置默认值,在Date类中增加它的自定义类型,然后不去赋值

class Time
{
public:
	Time(int hour)
		:_hour(hour)
	{}

privateint _hour;
};

结果报错,显示没有合适的默认构造函数
在这里插入图片描述

  • 这一点需要说明,如果用户没有显示定义构造函数,那么C++会默认生成构造函数,这样的话这个程序是可以编译通过的,只不过是随机值
    在这里插入图片描述

(当然如果我给Time的hour初始值,这个构造函数就正确了,也就可以编译通过了,这里就不演示了)

所以这种情况下,就要我们手动传参,必须放在初始化列表,写法如下
在这里插入图片描述

(3)小结

之前我们的构造函数是放在函数体内的,但是这样的初始化本质上是赋值,像const这类成员,不可能存在const int a a=10这样的操作,而必须是const int a=10,也就是定义的时候初始化,所以为了解决这样的问题,**在C++中我们要使用初始化列表对这些变量进行初始化。**对其他变量没有要求

但是在日常开发中,还是推荐不管三七二十一任何变量都是用初始化列表,尤其是在对自定义类型进行初始化时,如果使用赋值的方式将会比使用初始化列表麻烦很多
在这里插入图片描述

最后:一定要保证,成员变量在勒种的声明和次序和初始化列表中的初始化顺序一致 。否则就会出现下面的这样的错误
这段代码按照正常的逻辑角度上讲,应该输出的是1, 1

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

但是其结果却为
在这里插入图片描述
出现随机值的原因已在上方用红色字体标注。这里声明次序先是_a2,再是_a1,所以初始化时也是这样的顺序,导致_a2在初始化时获得了随机值

(4)匿名对象

一般实例化对象时都是这样进行的

class A
{
public:
	A(int a)
	:_a(a);
	{}
private:
	int _a;
}

int main()
{
	A aa(1);
}

这样实例化,那么这个对象的生命周期就是main函数内

而如果这样实例化的对象,它的生命周期仅仅在一行,从下一行开始它的生命周期也就结束了,也就去调用它的析构函数了

class A
{
public:
	A(int a)
	:_a(a);
	{}
private:
	int _a;
}

int main()
{
	A aa(1);//声明周期是main函数
	A(1);//匿名对象,生命周期仅仅在一行
}

(5)explicit关键字

如果初始化时只有一个参数,将会发生隐式类型转换

下面的案例中,直接使用了一个整形给一个对象进行赋值,是可以编译通过的

class Date
{
public:
	Date(int year)
		:_year(year)
	{}
private:
	int _year;
};
int main()
{
	Date d1(2020);
	d1 = 2019;//直接用一个整形给对象赋值
}

但是我们在赋值运算符重载那一节,讲过将“=”重载后,采用赋值号可以进行类似的赋值操作,但是在这里并没有对“=”进行重载,它却赋值成功了

实际上,上述例子中,编译器会偷偷的用2019创造出一个匿名对象,最后用匿名对象给d1进行赋值,这从表面看,就好像发挥了隐式转换的作用

如果使用explicit关键字修饰构造函数,将会禁止这种单参数的构造函数的隐式转换
在这里插入图片描述

以上是关于3-6:类与对象下篇——构造函数中的初始化列表匿名对象和explicit关键字的主要内容,如果未能解决你的问题,请参考以下文章

C++类与对象第四篇:(初始化列表构造匿名对象隐式类型转换友元static成员内部类)

类与对象(下篇)

类与对象(下篇)

类与对象(下篇)

C++ ----类与对象(下)

[ C++ ] 类与对象(下) 初始化列表,友元,static成员,内部类