| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> C++学习之路-构造函数的初始化列表 -> 正文阅读 |
|
[C++知识库]C++学习之路-构造函数的初始化列表 |
构造函数:初始化列表一、何为初始化列表定义:一种便捷的初始化类内成员变量的方式。
我们可以轻而易举的看出两者的区别:构造函数的书写有些差异。初始化列表所在的构造函数没有函数体,在函数之后以“:”连接。
m_age(age)中的m_age是即将被初始化的成员变量,括号内的age是构造函数的形参。当初始化多个成员变量时,用逗号分离。大括号还是有的,只是函数体内部没有代码。 二、初始化列表的本质初始化列表的本质,就是生成函数体代码。这样写:
实际上是执行的:
初始化成员列表的写法,比普通的函数体内部赋值看起来更加好看,但是这两种写法的效率是一模一样的。读到这里就会有疑问,仅仅便捷到这种程度,没必要弄一个初始化列表出来吧?那我们下面介绍一下,初始化列表比普通函数体内部实现更便捷的应用场合。 三、初始化列表的优势我们知道优势是相对的。初始化列表的问世就是为了便捷以往初始化成员变量的方式,那相对于普通的构造函数初始化有什么便捷之处呢?
可以将表达式作为形式参数,初始化成员变量
可以将形参经过函数,然后将函数的返回值作为初始化成员变量的值。这种情况还是比较常见的,我们在有些时候,需要对初始化的参数做一定的计算,然后作为成员变量的初始值。 四、初始化列表中列表顺序问题我们初始化列表的时候,被初始化的成员变量之间用逗号分隔:
m_age(age) 然后再 m_height(height)。我们有没有想过是不是先初始化 m_age然后在初始化 m_height呢?那我们就来验证一下:
我们初始化m_age选用m_height的值作为初始化值,然后运行main函数,打印两个成员变量的值,结果如下
发现m_age并没有被初始化。这样的结果也不奇怪,因为如果是先初始化m_age,传入的形参是m_height,而此时m_height还没有被赋值,自然m_age就得不到正常数值的初始化了。到这里其实并不能证明,初始化成员列表的书写顺序就是初始化的顺序。下面继续一个例子:
如果按照我们上面的分析,列表的书写顺序决定了初始化的顺序。这也的书写顺序,会先初始化m_height,然后将m_height的值作为形参,传递给m_age,进而m_age得到初始化。那事实是这样吗?打印结果如下:
咦?怎么回事?按理说应该都打印60才对。这个初始化的顺序难道不是列表的书写顺序吗? 答案是:初始化列表中,列表的顺序是没有意义的,初始化成员变量的顺序,只跟成员变量在内存中的地址值有关。即:在类中声明时,写在前面的成员变量被先初始化,写在后面的成员变量被后初始化,因此,对于本类来说,永远先初始化m_age,后初始化m_height。所以谜底揭开了。
也就是说,由于成员变量m_age写在前面,所以初始化列表中对于m_age的初始化操作先执行,也就是说先执行这句m_age(m_height),所以初始化列表中的书写顺序是意义的。 五、初始化列表与默认参数的配合使用首先回顾一下默认参数是啥(可以看我之前的博文)。默认参数的作用是:声明对象时,不传递参数也有默认参数传递参数。
假如我的对象声明这样写:直接将 age = 10 和 height = 50 传递给成员变量进行初始化
假如我的对象声明这样写:直接将 age = 12 和 height = 50 传递给成员变量进行初始化
假如我的对象声明这样写:直接将 age = 12 和 height = 60 传递给成员变量进行初始化
我们发现了,初始化列表与默认参数搭配使用最大的好处就是:我写一个构造函数,相当于写了三个构造函数。这种搭配要记住,要时常使用,可提高代码的精简 六、初始化列表的注意之处
我们已经知道了,初始化列表其实就相当于把代码插入到构造函数的函数体内:(两种写法完全等价)
按照我们之前的逻辑,好像初始化列表就是为了替代函数体代码而出现的。所以认为有了初始化列表就不能在函数体内部写代码,但其实是可以在函数体内写代码的。比如下方代码:
我在初始化列表里通过传递形age,初始化了成员变量m_age,但我又有了另外一个需求,在函数体内部对m_age进行了额外的处理,也就是额外的初始化。那么问题来了:最终成员变量是按照初始化列表里的来呢,还是按照函数体里的来呢? 回答这个问题很简单,就看是先执行初始化列表的操作,还是先执行函数体里的操作呗!
答案是,先执行初始化列表里的操作,即把初始化列表里做的事插入到函数体的最顶端,先执行。既然是先执行,那最终的结果必然是依据后执行的结果了。所以,m_age最终被初始化为10。
我在类中,写了两个构造函数(一个是有参的构造函数,另一个是无参的构造函数)。我们经常会在类中写多种构造函数,以适应不同的初始化成员变量需求。当声明无参的构造函数时,就赋值0给成员变量,当声明有参的构造函数时,就将参数传给成员变量,这很合理。如下方代码所示:
但是,不觉得这样太麻烦了吗,因为两个构造函数体内部都是给成员变量赋值。两个成员变量还可以接受,如果是10个成员变量,就会很麻烦了。这时候或许你会想到一个解决这个问题的方法:在有参的构造函数赋予默认参数,这样不就解决了上面的两个构造函数的问题吗?为何要学习构造函数调用构造函数呢?
那我说你的格局就小了!因为构造函数虽然最大的作用就是初始化成员变量,但是它还可以执行别的代码,函数体内还可以执行别的操作。因此构造函数的互相调用是有应用场景的。就像下方代码所示:
然后我在main函数中,示例无参的对象,调用无参的构造函数。理论上会进入无参构造函数里,然后执行有参构造函数,然后将0赋值给成员变量。实际上这个过程也是这样运行的,但是为什么打印的乱码呢?
直接说答案:因为构造函数的互相调用必须在初始化列表里调用,即正确写法:
下面解释,为什么构造函数必须放在初始化列表里,而不能放在函数体内部(为什么放在函数体内部不行?)
这里需要回顾一个重点的知识。对象调用成员函数的时候,会有一个默认的this指针,出现在函数体内部。这个this指针指向这个对象
我们想起来了this指针的事,然后从本质解释一下:为什么调用构造函数,写在函数体内,main函数打印出来的是乱码呢?
所以,写在函数体内部的构造函数,会创建一个临时对象,然后临时对象的地址赋值给调用函数的this指针。这时,main函数中声明的对象,没有将地址传递给构造函数的this指针,那么person.m_age也就不会被赋值了。 而正确的调用:将构造函数写在初始化列表里,会将main函数中对象的person对象传给被调用的构造函数,进而赋值给this指针。 总结:构造函数调用构造函数是一种比较特殊的调用,被调用者必须放在初始化列表里面。构造函数调用普通的类内成员函数,可以写在函数体内部,但是构造函数必须写在初始化列表里。 七、构造函数的声明和实现分离时,初始化列表需写在实现里假设构造函数的声明和实现是分离的(我们创建类时,基本上都是分离的),有一个非常关键的点,就是初始化列表的操作只能写在实现中,不可以写在声明中。 下面的类中,声明和实现是分开的。然而我将初始化列表写在了声明中,这也的书写方式是错误的。
而,这样的书写方式才是正确的:
假如,默认参数和初始化列表搭配起来使用,且恰好此时构造函数的声明和实现是分离的,那我们应该怎么做呢?下面的程序会报错,这是因为当声明和实现分离时,默认参数只能写在声明里。
所以,必须改成下面的形式
那想没想过,为什么默认参数又要写在构造函数的声明里呢? 因为我在调用函数的时候,编译器是先看声明,再看实现,如果声明中没有默认参数,那么编译器就不会把默认参数值push出来(看我的博文),而是直接调用函数,直接执行函数体代码,这样的话就会报错。 搭配使用时,我们要记住两件事:当构造函数的声明和实现是分离的时候,默认参数只能写在声明里,而初始化列表只能写在实现处,切记切记。 八、子类调用父类的构造函数子类的构造函数会默认调用父类的无参构造函数(前提是父类中写了无参的构造函数)
如果子类的构造函数显式的调用了父类的有参构造函数,就不会默认的调用父类的无参构造函数了
如果父类中没有无参的构造函数,只有有参的构造函数。那么子类必须主动调用有参的构造函数,否则编译器会报错。
如果父类没有写构造函数,那么子类如何调用父类的构造函数呢?(那我就不调用了)。网上有的博客说,如果没有写构造函数,就会默认生成一个,然后调用。这句话是错的。根本不会默认生成。
因为子类就是要继承父类的东西。如果子类对象想要初始化父类成员变量怎么办,那就必须得调用父类的构造函数。特别的就是父类中如果有private成员,子类根本无法初始化,只能通过父类初始化,也就是只能通过调用父类的构造函数来初始化。就像下面的例子一样。
所以说,子类调用父类的构造函数最大的价值其实是,初始化父类中的private成员。 总结一下子类调用父类构造函数的规则:
九、初始化列表总结
|
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/11 6:15:36- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |