【C++程序员的自我修炼】初始化列表

一半烟火以谋生

一半诗意以谋爱


契子✨ 

我们之前已经讲过了构造函数的初始化:前期回顾-构造函数

但是难免会遇到连构造函数都不好解决的问题

比如:

错误示范

class A
{
public:
	A(int n)
	{
		this->_a = n;
		this->_b = n;
	}
private:
	const int _a;
	int _b;
};

因为 const 修饰的变量具有常性,也就是只能读不能写

所以 const 修饰的变量只能在定义的时候初始化,我们可以在声明的时候给缺省值来解决这个问题

const int _a = 10;

但是铁铁们知道这么做的本质是什么吗?

接下来我将带大家介绍:


初始化列表介绍

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个 成员变量 后面跟一个放在括号中的初始值或表达式
以下就是我们的要传参初始化列表: 
class A
{
public:
	A(int n)
		:_a(n)
		,_b(n)
	{
	}
private:
	int _a ;
	int _b;
};
当然我们也可以给 缺省值 进行无参的初始化定义
class A
{
public:
	A(int n = 4)
		:_a(n)
		,_b(n)
	{
	}
private:
	int _a ;
	int _b;
};
我们还可以在初始化列表中进行 malloc 的动态空间开辟操作
class A
{
public:
	A(int n = 4)
		:_a(n)
		, _b((int*)malloc(40))
	{
		memset(_b, 0, 40);
	}
private:
	int _a;
	int* _b;
};

注意:

初始化列表本质可以理解为每个对象中成员定义的地方
所有的成员可以在初始化列表初始化,也可以在函数体内初始化

比如:以上代码我在初始化列表中定义了 10 个 int 类型的空间,并在函数体内全部初始化为 0

我们这里规定一下:在构造函数进行初始化操作叫做在函数体内部进行初始化,在初始化列表初始化时叫做在对象定义的时候进行初始化

可能你会问:为什么要专门规定一个对象的成员变量初始化的地方呢?普通的变量不管是在函数体内部初始化还是在初始化列表初始化,都没有什么影响

但是有些特殊的成员变量,比如:

引用成员变量

const成员变量

没有默认构造函数的自定义类型成员变量

是不能在函数体内部初始化的

 

初始化列表的必用场景 

引用成员变量

错误示范

class A
{
public:
	A(int n = 4)
	{
		_a = n;
	}
private:
	int& _a ;
};

为什么呢?因为引用必须在定义的时候初始化:

定义一般变量时:

<1>声明:声明变量类型和名字 

<2>定义:根据类型分配内存地址空间 

<3>初始化:将初始值拷贝到变量的内存地址空间中

定义引用类型时:

将引用绑定到初始化对象

因此定义引用类型时必须有初始值对象,也就是必须在定义的时候初始化

正确写法

class A
{
public:
	A(int n = 4)
		:_a(n)
	{
	}
private:
	int& _a;
};

因为初始化列表本质可以理解为每个对象中成员定义的地方,所以用初始化列表写

const 成员变量

因为 const 修饰的变量具有常性,所以必须在定义的时候初始化:

class A
{
public:
	A(int n = 4)
		:_a(n)
	{
	}
private:
	const int _a;
};

自定义类型成员(且该类没有默认构造函数)

错误示范

class A
{
public:
	A(int a,int b,int c)
	{
		this->_a = a;
		this->_b = b;
		this->_c = c;
	}
private:
	int _a;
	int _b;
	int _c;
};

class B
{
public:
	B(int n = 4)
	{
		_cc = n;
        _aa(0, 0, 0);
		_bb(1, 1, 1);
	}
private:
	A _aa;
	A _bb;
	int _cc;
};

在构造函数的章节我们提过:

  • 自动生成的构造会处理自定义类型,它会去调用自定义类型的默认构造

所以自定义类型会先调自己的默认拷贝构造,而这里我们恰恰没写呢?这就是报错的原因

没事~交给我们的初始化列表即可

正确写法

class A
{
public:
	A(int a, int b, int c)
	{
		this->_a = a;
		this->_b = b;
		this->_c = c;
	}
private:
	int _a;
	int _b;
	int _c;
};

class B
{
public:
	B(int n = 4)
		:_aa(0, 0, 0)
		,_bb(1, 1, 1)
	{
		_cc = n;
	}
private:
	A _aa;
	A _bb;
	int _cc;
};

这样我们就成功初始化了呢~

初始化的一些小细节

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

我们来看一下,之前写的日期类 Date :

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值


简单来说:若是没有用初始化列表,在函数内的 初始化 语句并不是真正的初始化,而是相当于初赋值,只有最后的赋值等式才会初始化生效

	Date(int year, int month, int day)
	{
		_year = year;
		_year = 10;
		_month = month;
		_month = 20;
		_day = day;
		_day = 30;
	}

比如说以上代码:最后初始化成功的就是:_year = 10,_month = 20,_day = 30

如果用我们的初始化列表的话

	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
	}

以上是正确的写法,但是不能初始化两次

错误示范

	Date(int year, int month, int day)
		:_year(year)
		, _year(10)
		,_month(month)
		,_day(day)
	{
	}

但是初始化后,仍可以赋初值

	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
		this->_year = 10;
	}

总结

每个成员变量在初始化列表中初始化只能初始化一次
类中包含以下成员,必须放在初始化列表位置进行初始化:
<1>引用成员变量
<2>const成员变量
<3>自定义类型成员(且该类没有默认构造函数时)

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

一定会先使用初始化列表初始化

注意:初始化列表在对象定义时,自动调用初始化列表,即使不写系统也会默认生成

如果我们不显式写出初始化列表,编译器会自动生成,对于内置类型,我们会对他进行初始化,对于自定义类型,会去调用它的默认构造函数

 这里跟构造函数一样~

内置类型传缺省值和初始化列表联系

回顾一下我们构造函数里讲过的给内置类型传缺省值

这里传的缺省值是给谁用的呢?

此时的你心里估计已有了明确的答案 -- 初始化列表

#include<iostream>
#include<assert.h>
#include<cstdlib>
using std::cout;
using std::endl;

class A
{
public:
	A()
		: _b(5)
	{
	}
	void Print();
private:
	int _a = 10;
	int _b = 20;
};

void A::Print()
{
	cout << _a <<" "<<_b << endl;
}

int main()
{
	A aa;
	aa.Print();
	system("pause");
	return 0;
}

看看以上的程序的结果是什么呢?


首先我们赋予了内置类型缺省值,编译器会将缺省值给初始化列表

由 初始化列表 初始化,也就是 ~  10 20

但是我们的初始化列表已将 _b 初始化为 5,说明不接受 _b 的缺省值

所以正确的结果是:10 5


成员变量的顺序

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

次序无关
#include<iostream>
#include<cstdlib>
using std::cout;
using std::endl;

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();
	system("pause");
	return 0;
}

 看看以上的代码分析程序:

A、输出1  1
B、程序崩溃
C、编译不通过
D、输出1  随机值

我们可以看到程序运行的结果为 D:
<1>根据成员变量声明的顺序,理应先初始化 _a2 但是 _a1 还没有初始化所以是随机值

<2>接下来到 _a1 初始化,就是传参初始化,所以是 1


先介绍到这里啦~

有不对的地方请指出💞

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

烟雨长虹,孤鹜齐飞

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值