C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++构造函数

C++之谈谈构造函数的初始化列表

作者:烽起黎明

构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用,这篇文章详细介绍了构造函数的初始化列表,文章中有详细的示例代码,感兴趣的同学可以参考阅读

一、引入

class A {
public:
	int _a1;	//声明
	int _a2;
};
int main(void)
{
	A aa;	//	对象整体的定义,每个成员什么时候定义?
	return 0;
}

如果现在我在类A中加上一个const成员变量的话,初始化的时候似乎就出现了问题

const int _x;	

在这里插入图片描述

const int i;

在这里插入图片描述

现在我们就可以来聊聊有关上面的成员变量_x为什么没有被初始化的原因了👇

💬有同学说:这还不简单,给个缺省值不就好了

这位同学说的不错,这个办法确实是可以解决我们现在的问题,因为C++11里面为内置类型不初始化打了一个补丁,在声明的位置给到一个初始化值,就可以很好地防止编译器不处理的问题

在这里插入图片描述

但是现在我想问一个问题:如果不使用这个办法呢?你有其他方法吗?难道C++11以前就那它没办法了吗?

底下的同学确实想不出什么很好的解决办法,于是这个时候就要使用到本模块要学习的【初始化列表】了

二、初始化的概念区分

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

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

三、语法格式及使用

【初始化列表】:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
int main(void)
{
	Date d(2023, 3, 30);
	return 0;
}

可以通过调试来观察一下它到底是怎么走的

在这里插入图片描述

接下去我再来说说这一块的难点所在,准备好头脑风暴🌊

class A {
public:
	A()
		:_x(1)
	{}
private:
	int _a1 = 1;	//声明
	int _a2 = 1;

	const int _x;
};

也通过调试来看一下

在这里插入图片描述

好,接下去难度升级,请问初始化列表修改成这样后三个成员变量初始化后的结果会是什么呢? 会是1、2、1吗?

class A {
public:
	A()
		:_x(1)
		,_a2(1)
	{}
private:
	int _a1 = 1;	//声明
	int _a2 = 2;

	const int _x;
};

一样通过调试来看看

在这里插入图片描述

这里要明确的一个概念是,缺省参数只是一个备份,若是我们没有去给到值初始化的话,编译器就会使用这个初始值,若是我们自己给到了明确的值的话,不会去使用这个缺省值了【如果不清楚看看C++缺省参数】

接下去难度继续升级,请问下面这样初始化后的结果是多少?

class A {
public:
	A()
		:_x(1)
		,_a2(1)
	{
		_a1++;
		_a2--;
	}
private:
	int _a1 = 1;	//声明
	int _a2 = 2;

	const int _x;
};

如果对于上面的原理搞清楚了,那看这个就相当于是再巩固了一遍。也是一样,无论是否给到缺省值都会去初始化列表走一遍,若是构造函数内部有语句的话就会执行

在这里插入图片描述

四、注意事项【⭐】

清楚了初始化列表该如何使用,接下去我们来说说其相关的注意事项

在这里插入图片描述

const成员变量

引用成员变量

在这里插入图片描述

没有默认构造的自定义类型成员(写了有参构造编译器就不会提供默认构造)

class B {
public:
	B()
		:_b(0)
	{}
private:
	int _b;
};
class A {
public:
	A()
		:_x(1)
		,_a1(3)
		,_a2(1)
		,_z(_a1)
	{
		_a1++;
		_a2--;
	}
private:
	int _a1 = 1;	//声明
	int _a2 = 2;

	const int _x;
	int& _z;
	B _bb;
};

在这里插入图片描述

在这里插入图片描述

💬那对于有参构造该如何去初始化呢?

还是可以利用到我们的【初始化列表】

在这里插入图片描述
通过调试来看看编译器是如何走的

在这里插入图片描述

看完了上面这一种,我们再来看看稍微复杂一些的自定义类型是否也遵循这个规则

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10) 	//全缺省构造
	{
		cout << "Stack()构造函数调用" << endl;
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	//....
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
class MyQueue{
public:
	//默认生成构造函数

private:
	Stack _pushST;
	Stack _popST;
	size_t _t = 1;
};

int main(void)
{
	MyQueue mq;
	
	return 0;
}

可能读者有所忘却,我们再通过调试来看一下

在这里插入图片描述

Stack(size_t capacity)

在这里插入图片描述

MyQueue()
	:_pushST(10)
	,_popST(10)
{}

可以通过调试再来看看

在这里插入图片描述

在这里插入图片描述

//无参构造MyQueue(){<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->}

所以可以看出,对于【内置类型】不做处理,【自定义类型】会调用它的默认构造可以看出其实就是当前类构造函数的初始化列表在起作用

在这里插入图片描述

在看了MyQueue类各种初始化列表的方式后,其实也可以总结出一点,无论如何不管有没有给到缺省值,只要是显式地写了一个构造函数,就可以通过调试去看出编译器都会通过【初始化列表】去进行一个初始化

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

但结果却和我们想象的不一样,_a1是1,_a2却是一个随机值,这是为什么呢?

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

现在你在翻上去把所有的调试图一幅幅看下来就可以发现出初始化列表是存在顺序的,它的顺序不是在列表中谁先谁后的顺序,而是类的成员变量声明的顺序

五、总结与提炼

最后来总结一下本文所学习的内容📖

面对必须在声明时期初始化的成员函数,我们引入了初始化列表这个东西,知道了祖师爷在构造函数中还做了这么个小文章😄。有了它,我们就再也不用担心成员变量不会被初始化的问题了,无论是你是否给到缺省值,编译器都会去走一遍构造函数的初始化列表,若是没有在定义处给到初始值,就会采用缺省值;若是给到了初始值就会采用这个值

不仅如此,初始化列表还有很多的注意事项:

初始化列表是构造函数这一块的难点,也是祖师爷面对C++某些地方缺陷设计出来的,搞懂之后就会豁然开朗了

以上就是C++之谈谈构造函数的初始化列表的详细内容,更多关于C++构造函数的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文