【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


先介绍到这里啦~

有不对的地方请指出💞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/626467.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Valkey开源社区再添新成员!阿里云与Redis核心贡献者再聚首

1. 背景 2024年3月21日&#xff0c;Redis Inc. 宣布正式修改开源Redis core的服务协议至RSALv2与SSPLv1的双Dual-License&#xff0c;并于 Redis 7.4 版本后关闭开源BSD软件分发协议。同时&#xff0c;Redis Inc. 也关闭了自2020年7月买断Redis社区后形成的以Core Member&…

软件设计:UML 模型图总结

1. 相关链接 参考教程&#xff1a; https://sparxsystems.com/resources/tutorials/ https://sparxsystems.com/enterprise_architect_user_guide/15.2/model_domains/whatisuml.html Unified Modeling Language (UML) description, UML diagram examples, tutorials and r…

浅谈Java IO流

Java中的IO流&#xff08;Input/Output streams&#xff09;是Java程序用来处理数据输入和输出的核心工具集。IO流抽象了数据流动的概念&#xff0c;允许Java程序与外部世界进行数据交换&#xff0c;无论是从文件、网络、键盘输入还是向屏幕、文件或网络发送数据。Java IO流按照…

切换plesk面板语言

近期购入了Hostease的Windows虚拟主机产品&#xff0c;由于进入他们主机Plesk面板后查看全都是英文的&#xff0c;对于英文也不是很懂&#xff0c;尤其是像这种专业 词汇的更不明白。因此这边咨询了Hostease的技术支持&#xff0c;寻求帮助了解到可以Plesk面板可以切换语言的&a…

SAP HANA锁表查询,解锁操作

锁表查看 --锁表检查语句 SELECT C.CONNECTION_ID,PS.STATEMENT_STRINGFROM M_CONNECTIONS C JOIN M_PREPARED_STATEMENTS PSON C.CONNECTION_ID PS.CONNECTION_ID AND C.CURRENT_STATEMENT_ID PS.STATEMENT_IDWHERE C.CONNECTION_STATUS RUNNINGAND C.CONNECTION_TYPE Re…

Canvas使用详细教学:从基础绘图到进阶动画再到实战(海报生成、Flappy Bird 小游戏等),掌握绘图与动画的秘诀

一、Canvas基础 1. Canvas简介 Canvas是HTML5引入的一种基于矢量图形的绘图技术&#xff0c;它是一个嵌入HTML文档中的矩形区域&#xff0c;允许开发者使用JavaScript直接操作其内容进行图形绘制。Canvas元素不包含任何内在的绘图能力&#xff0c;而是提供了一个空白的画布&a…

【开发规范】Mapstruct 转换器使用教程

【开发规范】Mapstruct 转换器使用教程 一、对象转化二、推荐使用 MapStruct三、MapStruct 介绍3.1 概念3.2 特点3.3 使用场景3.4 使用教程3.4.1 导入依赖3.4.2 编写 Entity 和 DetailInfo3.4.3 编写转换器&#xff08;※&#xff09;3.4.4 使用转换器3.4.5 结果3.4.6 扩展&…

MySQL死锁与死锁检测

一、什么是MySQL死锁 MySQL中死锁是指两个或多个事务在互相等待对方释放资源&#xff0c;导致无法继续执行的情况。 MySQL系统中当两个或多个事务在并发执行时&#xff0c;就可能会遇到每项事务都持有某些资源同时又请求其他事务持有的资源&#xff0c;从而形成事务之间循环等…

java学习之路-多态

文章目录 目录 文章目录 前言 1.多态 1.1 多态的概念 1.2 多态实现条件&#xff08;重点&#xff09; 多态实现的栗子 1.3重写 重写的规则 重写和重载的区别 1.4静态和动态绑定 1.5向上转型和向下转型 1.向上转型 2.向下转型 1.6多态的优点 前言 本文内容&#xff1a;多…

OpenHarmony开源三方库的cmake在IDE上直接引用的问题

前言 DevEco Studio的native工程的C/C部分当前只支持cmake脚本的编译&#xff0c;工程的目录结构如下图所示 在工程中引用第三方库有如下三种方式&#xff0c; 一、find_package模式 通过find_package&#xff0c;可以在指定目录下去搜索已安装的库&#xff08;三方库构建完后…

【Linux】详解如何利用共享内存实现进程间通信

一、共享内存&#xff08;Shared Memory&#xff09;的认识 共享内存&#xff08;Shared Memory&#xff09;是多进程间共享的一部分物理内存。它允许多个进程访问同一块内存空间&#xff0c;从而在不同进程之间共享和传递数据。这种方式常常用于加速进程间的通信&#xff0c;因…

Linux - 线程

目录 一.Linux线程的概念 1.1什么是线程 1.2 线程的优点 1.3 线程的缺点 1.4 线程异常 1.5 线程用途 二. Linux进程VS线程 2.1 进程和线程 三. Linux线程控制 3.1 POSIX线程库 3.2 创建线程 3.3 进程ID和线程ID 3.4 线程ID及进程地址空间布局 3.5 线程终止 3.6 线…