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

这篇具有很好参考价值的文章主要介绍了C++ | 谈谈构造函数的初始化列表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

一、引入

  • 我们知道,对于下面这个类A的成员变量_a1_a2属于【声明】,还没有在内存中为其开辟出一块空间以供存放,真正开出空间则是在【定义】的时候,那何时定义呢?也就是使用这个类A去实例化出对象的时候
  • 这个对象的空间被开出来了,难道里面的成员变量就一定开出空间了吗?这一点我们很难去通过调试观察
class A {
public:
	int _a1;	//声明
	int _a2;
};
int main(void)
{
	A aa;	//	对象整体的定义,每个成员什么时候定义?
	return 0;
}
  • 如果现在我在类A中加上一个const成员变量的话,初始化的时候似乎就出现了问题
const int _x;	

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

  • 在搞清楚上面的问题之前你要明白const修饰的变量有哪些特点
const int i;
  • 可以看到我在这里定义了一个整型变量i,它前面是用const进行修饰的,不过编译后报出了错误说【必须初始化常量对象】,因为对于const修饰的变量在声明的时候是必须要去进行初始化的,也就是要给到一个值

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

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

  • 之前有讲过,若是我们自己不去实现构造函数的话,类中会默认提供一个构造函数来初始化成员变量,对于【内置类型】的变量不会处理,对【自定义类型】的变量会去调用它的构造函数。那么对于这里的_a1_a2_x都属于内置类型的数据,所以编译器不会理睬,可是呢const修饰的变量又必须要初始化,这个时候该怎么办呢╮(╯▽╰)╭

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

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

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

但是现在我想问一个问题:如果不使用这个办法呢?你有其他方法吗?难道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;
};
  • 上面这个Date类是我们之前写过的,这里有一个它的有参构造函数,虽然在这个构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化。构造函数体中的语句只能将其称为【赋初值】,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值

三、语法格式及使用

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

  • 下面就是它的具体用法,这样便可以通过外界传入一些参数对年、月、日进行初始化
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;
}

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

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


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

  • 还是看回到我们上面的这个类A,知道了【初始化列表】这个东西,此时就不需要再声明的部分给缺省值了,直接使用初始化列表即可。不过可以看到,对于_a1_a2我给到了缺省值,写了初始化列表后,它们还会被初始化吗?
class A {
public:
	A()
		:_x(1)
	{}
private:
	int _a1 = 1;	//声明
	int _a2 = 1;

	const int _x;
};

也通过调试来看一下

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

  • 可以看到,即使在初始化列表没有给到_a1_a2的初始化,还是会通过给到的默认缺省值去进行一个初始化。根据上面所学,我给出以下的结论

    1. 哪个对象调用构造函数,初始化列表是它所有成员变量定义的位置
    2. 不管是否显式在初始化列表写,编译器都会为每个变量在初始化列表进行初始化

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

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

	const int _x;
};

一样通过调试来看看

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

  • 可以观察到,最后初始化完后的结果为1、1、1,最令你困惑的应该就是这个_a2了,因为我在声明的时候给到了缺省值,然后初始化列表去进行定义的时候又去进行了一次初始化,最后的结果以初始化列表的方式为主
  • 这里要明确的一个概念是,缺省参数只是一个备份,若是我们没有去给到值初始化的话,编译器就会使用这个初始值,若是我们自己给到了明确的值的话,不会去使用这个缺省值了【如果不清楚看看C++缺省参数】

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

  • 可以看到对于构造函数我不仅写了【初始化列表】,而且在函数体内部还对_a1_a2进行了++和- -,那此时会有什么变化呢?
class A {
public:
	A()
		:_x(1)
		,_a2(1)
	{
		_a1++;
		_a2--;
	}
private:
	int _a1 = 1;	//声明
	int _a2 = 2;

	const int _x;
};

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

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


四、注意事项【⭐】

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

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
    • 可以看到,若是一个成员变量在初始化列表的地方出现了两次,编译器在编译的时候就会报出【xxx已初始化

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

  1. 类中包含以下成员,必须放在初始化列表位置进行初始化:
  • const成员变量
    • 这个在前面已经说到过了,const修饰的成员变量和构造函数对于内置类型不做处理产生了一个冲突,因此祖师爷就提出了【初始化列表】这个概念
  • 引用成员变量
    • 第二点就是对于引用成员变量,如果有点忘记了看看C++引用
    • 通过编译可以看出,这个引用型成员变量_z需要被初始化,它必须要引用一个值

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

  • 没有默认构造的自定义类型成员(写了有参构造编译器就不会提供默认构造)
    • 此时,我又写了一个类B,将它定义出的对象作为类A的成员变量,在类B中,有一个无参的默认构造,也写了相关的初始化列表去初始化_b
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;
};
  • 通过调试来观察就可以看到,完全符合我们前面所学的知识,若是当前类中有自定义类型的成员变量,那在为其进行初始化的时候会去调用它的默认构造函数

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

  • 但是现在我对这个构造函数做了一些改动,将其变为了有参的构造函数,此时编译时就报出了【没有合适的默认构造函数可用
  • 我们知道默认构造有:无参、全缺省和编译器自动生成的,都是不需要我们手动去调的。可以看到若是我在这里将其改为全缺省的话,就不会出问题了,因为它属于默认构造函数

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

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

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

C++ | 谈谈构造函数的初始化列表
通过调试来看看编译器是如何走的

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

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

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

  • 也就是我们之前写过的Stack和MyQueue类
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;
};
  • 此处我们主要观察Stack类的构造函数,因为在MyQueue中我没有写构造函数,为的就是使用它默认生成的构造函数去进行初始化。对于【内置类型】不做处理,不过我这里给到了一个缺省值,对于【自定义类型】会去调用它的默认构造

class MyQueue{
public:
	//默认生成构造函数

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

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

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

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

  • 可以观察到在初始化MyQueue类的对象时,因为内部有两个Stack类型的对象,所以就会去调用两次Stack类默认构造来进行初始化
  • 那此时我若是将这个默认构造(全缺省构造)改为有参构造吗,它还调得动吗?
Stack(size_t capacity)
  • 可以看到,此时就报出了我们上面有类似遇到过的【无法引用默认构造函数】,为什么呢?原因就在于我们写了,编译器自动生成的也就不存在了,但是我又没有传入对应的参数

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

  • 此时就可以使用到我们本模块所学习的【初始化列表】了,将需要定义的值放在初始化列表,相当于就是为Stack类传入了一个有参构造的参数,不过对于没有写在这里的_t,依旧会使用我给到的初始值1
MyQueue()
	:_pushST(10)
	,_popST(10)
{}

可以通过调试再来看看

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

  • 当然,如果你觉得不想要这个固定的10作为栈容量的话,也可以将这个MyQueue的构造函数设定为有参,自己传递进去也是可以的

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

  • 最后再来看一下无参构造,也是默认构造的一种,在这里编译器也会去走MyQueue的初始化列表进行初始化
//无参构造
MyQueue()
{}

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

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

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

  1. 初始化列表也不一定能完成所有的工作,要灵活运用

💬 有同学说:这初始化列表感觉也太强大了吧,那我们之后就一直使用初始化列表吧!

  • 没错,初始化列表确实是C++在类和对象这一块的亮点,但是也有其无法做到的事情。比方说我们在下面这个Stack类的有参构造中使用到了初始化列表,其中数组_a的空间采用【malloc】的形式进行开辟,不过呢我们知道【malloc】在开辟的过程中总会有失败的可能性,所以需要检查,但是初始化列表可做不了这样的事情,这一部分我们需要放在{}内部进行
  • 不仅如此,如果大家学习过数据库相关知识的话,可以知道这个_a可能需要去连接一下数据库,那对于这种工作来说也是初始化列表无法完成的
class Stack {
public:
	Stack(int capacity = 10)
		: _a((int*)malloc(sizeof(int)))
		, _top(0)
		, _capacity(capacity)
	{
		if (nullptr == _a)
		{
			perror("fail malloc");
			exit(-1);
		}
		memset(_a, 0, sizeof(int) * capacity);
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
  • 再举个例子,有时候会有这样的需求,我们得在构造函数中开辟出一个二维数组的空间,那二维数组我们知道需要知道其【行】和【列】,这两个参数我们可以在初始化对象的时候进行传入,此时初始化列表就起到了很好的作用,但是呢,我们开辟一个二维数组的空间也可以在初始化列表中进行吗。这很明显是做不到的
  • 这一块工作交给构造函数自身的初始化部分就很好,首先开辟出一个一维数组的空间,里面存放的都是一个个指针,即指针数组,然后再为这一个个指针开辟空间即可
class AA
{
public:
	AA(int row, int col)
		: _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;
};

因此从上面我们可以看出,初始化列表并无法完成所有的工作,所以需要根据我们的需求来进行灵活变通

  1. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
  • 最后再来看第五点,你认为下面这段代码最后打印的结果会是多少呢?1 1 吗?
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++ | 谈谈构造函数的初始化列表

  • 通过调试可以发现,似乎是先初始化的_a2再去初始化的_a1,对于【内置类型】我们可以知道是编译器是不会去进行初始化的,那若是一开始使用_a1去初始化_a2的时候,那_a2就会是一个随机值,但是_a1却使用传入进来的形参a进行了初始化,那它的值就是1

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

  • 此时我们只需要让_a1先进行初始化即可,就不会造成随机值的现象了

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

五、总结与提炼

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

  • 面对必须在声明时期初始化的成员函数,我们引入了初始化列表这个东西,知道了祖师爷在构造函数中还做了这么个小文章😄。有了它,我们就再也不用担心成员变量不会被初始化的问题了,无论是你是否给到缺省值,编译器都会去走一遍构造函数的初始化列表,若是没有在定义处给到初始值,就会采用缺省值;若是给到了初始值就会采用这个值
  • 不仅如此,初始化列表还有很多的注意事项:
    • 首先就是每个成员只能初始化一次,可以不要初始化多次哦
    • 其次就是对于三类成员一定要在初始化列表进行初始化:包括const修饰的成员变量、引用类型成员、无默认构造函数的自定义成员变量
    • 然后尽量使用初始化列表初始化,因为无论如何编译器一定会走初始化列表,声明时期的缺省值其实就是给到初始化列表使用的
    • 接着我们在使用初始化列表的时候也要注意使用的场景,并不是所以的工作都可以交给它来完成的,也有一些其无法完成的事情
    • 最后就是初始化列表中的初始化顺序,与定义处的顺序是无关的,和声明处的顺序有关
  • 初始化列表是构造函数这一块的难点,也是祖师爷面对C++某些地方缺陷设计出来的,搞懂之后就会豁然开朗了

以上就是本文要介绍的所有内容,感谢您的阅读🌹

C++ | 谈谈构造函数的初始化列表文章来源地址https://www.toymoban.com/news/detail-410104.html

到了这里,关于C++ | 谈谈构造函数的初始化列表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】类和对象④(类的默认成员函数:取地址及const取地址重载 | 再谈构造函数:初始化列表,隐式类型转换,缺省值)

    🔥 个人主页: Forcible Bug Maker 🔥 专栏: C++ 目录 前言 取地址及const取地址操作符重载 再谈构造函数 初始化列表 隐式类型转换 explicit 成员变量缺省值 结语 本篇主要内容:类的六个默认成员函数中的 取地址 及 const取地址重载 , 构造函数 初始化列表 , 隐式类型转换

    2024年04月26日
    浏览(36)
  • 【C++那些事儿】深入理解C++类与对象:从概念到实践(下)| 再谈构造函数(初始化列表)| explicit关键字 | static成员 | 友元

    📷 江池俊:个人主页 🔥 个人专栏:✅C++那些事儿 ✅Linux技术宝典 🌅 此去关山万里,定不负云起之望 1.1 构造函数体赋值 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。 虽然上述构造函数调用之后,对象中已经有了一个初始值,但是

    2024年03月21日
    浏览(43)
  • c++类和对象(拷贝构造、运算符重载、初始化列表、静态成员、友元等)

    拷贝构造函数的特征: 1、拷贝构造函数是构造函数的一个重载形式; 2、拷贝构造函数的参数只有一个且必须是同类类型对象的引用, 使用传值方式编译器直接报错 ,因为会引发无穷递归调用。 在c++中自定义类型 传值传参 的时候要调用拷贝构造函数。 3、若未显式定义,

    2024年02月15日
    浏览(30)
  • 【C++】const、static关键字和构造函数初始化

    💗个人主页💗 ⭐个人专栏——C++学习⭐ 💫点击关注🤩一起学习C语言💯💫 目录 1. const修饰成员函数 1.1 语法格式 1.2 权限放大缩小 1.3 思考 1.4 解答 2. 再谈构造函数 2.1 构造函数体赋值 2.2 初始化列表 2.3 explicit 3. static成员 3.1 静态变量 3.2 静态函数 3.3 静态成员变量

    2024年02月19日
    浏览(38)
  • C++ 学习 ::【基础篇:13】:C++ 类的基本成员函数:类类型成员的初始化与构造函数问题

    本系列 C++ 相关文章 仅为笔者学习笔记记录,用自己的理解记录学习!C++ 学习系列将分为三个阶段: 基础篇、STL 篇、高阶数据结构与算法篇 ,相关重点内容如下: 基础篇 : 类与对象 (涉及C++的三大特性等); STL 篇 : 学习使用 C++ 提供的 STL 相关库 ; 高阶数据结构与算

    2024年02月08日
    浏览(48)
  • 【C++干货基地】面向对象核心概念 const成员函数 | 初始化列表 | explicit关键字 | 取地址重载

    🎬 鸽芷咕 :个人主页  🔥 个人专栏 : 《C++干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活!   哈喽各位铁汁们好啊,我是博主鸽芷咕《C++干货基地》是由我的襄阳家乡零食基地有感而发,不知道各位的城市有没有这种实惠又全面的零食基地呢?C++ 本身作

    2024年04月23日
    浏览(36)
  • C++之初始化列表详细剖析

    初始化列表:以一个 冒号开始 ,接着是一个以 逗号分隔的数据成员列表 ,每个 \\\"成员变量\\\" 后面跟一个 放在括号中的初始值或表达式。 不知道大家有没有想过这样一个问题,成员函数明明可以在函数内部对成员变量进行赋值,那为什么还要搞出初始化列表这个东西呢?这个

    2024年02月06日
    浏览(45)
  • C++:初始化列表,static成员,友元,内部类

    个人主页 : 个人主页 个人专栏 : 《数据结构》 《C语言》《C++》 本篇博客作为C++:初始化列表,static成员,友元,内部类的知识总结。 初始化列表:以冒号开始,接着是一个以逗号分隔的数据成员列表,每个“成员变量”后面跟一个放在括号中的初始值或表达式。 初始化

    2024年02月07日
    浏览(43)
  • C++——初始化列表 | explicit关键字 | static成员

    🌸作者简介: 花想云 ,在读本科生一枚,致力于 C/C++、Linux 学习。 🌸 本文收录于 C++系列 ,本专栏主要内容为 C++ 初阶、C++ 进阶、STL 详解等,专为大学生打造全套 C++ 学习教程,持续更新! 🌸 相关专栏推荐: C语言初阶系列 、 C语言进阶系列 、 数据结构与算法 本章我们

    2023年04月11日
    浏览(40)
  • 【C++基础(六)】类和对象(下)--初始化列表,友元,匿名对象

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C++初阶之路⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习C++   🔝🔝 关于类和对象的大致内容已经结束 本篇文章主要是介绍一些冗杂的细节 虽然本节的内容属于对类和对象锦上添花 但在很多特定的场所下,还是

    2024年02月14日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包