【C++】-- 初始化列表

article/2025/9/10 21:12:44

目录

一、用初始化列表初始化对象

1.初始化列表用法

2.初始化列表特性

二、explicit关键字

1.内置类型的隐式转换

2.如何避免单参构造函数初始化发生隐式类型转换

三、匿名对象

1.匿名对象定义 

2.匿名对象应用场景

创建一个类对象时,编译器通过调用构造函数,给类对象中各个成员变量赋初值:

class Date
{
public://构造函数Date(int year = 2022, int month = 4, int day = 19){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};

但上述赋初值不能称作类对象成员的初始化,因为构造函数体内可以多次赋值:

class Date
{
public://构造函数Date(int year = 2022, int month = 4, int day = 19){_year = year;_month = month;_day = day;_year = 2023;//构造函数体内允许对成员变量进行多次赋值}private:int _year;int _month;int _day;
};

而初始化列表能只能初始化一次。

一、用初始化列表初始化对象

1.初始化列表用法

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

class Date
{
public://构造函数Date(int year = 2022, int month = 4, int day = 19):_year(year) //初始化列表初始化,_month(month),_day(day){}private:int _year;int _month;int _day;
};

2.初始化列表特性

(1)初始化列表能只能初始化一次,多次初始化会报错:

class Date
{
public://构造函数Date(int year = 2022, int month = 4, int day = 19):_year(year) ,_month(month),_day(day),_month(month) //初始化列表多次初始化{}private:int _year;int _month;int _day;
};

编译器也允许构造函数赋初值和初始化列表初始化混用:

class Date
{
public://构造函数Date(int year = 2022, int month = 4, int day = 19):_year(year)  //两者混用,_month(month){_day = day;}private:int _year;int _month;int _day;
};

混用时初始化列表初始化和构造函数赋初值不冲突:

class Date
{
public://构造函数Date(int year = 2022, int month = 4, int day = 19): _year(year) //两者不冲突, _month(month){_day = day;_year = 2023;}private:int _year;int _month;int _day;
};

但混用时初始化列表初始化还是要遵循只能初始化一次成员变量的原则:

class Date
{
public://构造函数Date(int year = 2022, int month = 4, int day = 19): _year(year) //初始化列表初始化, _month(month), _year(2023)  //_year在初始化列表里被初始化了两次,不允许{_day = day;}private:int _year;int _month;int _day;
};

(2)const成员变量、引用成员变量、没有默认构造函数的自定义类型成员只能在初始化列表初始化。

①const成员变量必须在定义的时候初始化

class Date
{
public://构造函数Date(int year = 2022, int month = 4, int day = 19): _year(year) , _month(month), _n(2) //const成员变量必须使用初始化列表进行初始化{_day = day;//_n = 2; //const成员变量不能在函数体内初始化}private:int _year;int _month;int _day;const int _n = 1;
};

②引用成员变量必须在定义的时候初始化

class Date
{
public://构造函数Date(int year = 2022, int month = 4, int day = 19): _year(year) , _month(month),_ref(year)//引用成员变量要在初始化列表初始化{_day = day;//_ref = year; //引用成员变量不能在函数体内初始化}private:int _year;int _month;int _day;int& _ref;
};

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

#include <iostream>
using namespace std;class A
{
public://默认构造函数是不用传参就可以调用的构造函数,有3种://1.无参默认构造函数//2.带参全缺省的默认构造函数//3.我们不写,编译器自动生成的默认构造函数A(int x)//不属于以上任何一种,所以A类的对象没有默认构造函数{cout << "A(int x)" << endl;_x = x;}private:int _x;
};class Date
{
public://构造函数Date(int year = 2022, int month = 4, int day = 19): _year(year) , _month(month), _a(20)//没有默认构造函数的自定义类型成员变量必须在初始化列表进行初始化{_day = day;}private:int _year;int _month;int _day;A _a;
};

const成员变量、引用成员变量、没有默认构造函数的自定义类型成员变量必须在初始化列表内初始化的原因:

①初始化列表是对象的成员变量定义的地方。

②对象的内置类型成员变量在初始化列表定义时没有要求必须初始化,因此既可以在初始化列表进行初始化,也可以在构造函数体内初始化。

③而const成员变量、引用成员变量、没有默认构造函数的自定义类型成员变量不能先定义再初始化,它们在初始化列表内定义,并且必须在定义时就初始化,因此必须在初始化列表内初始化。

(3) 尽量使用初始化列表初始化,因为不管是否使用初始化列表,虽然对于内置类型没有差别,但是对于自定义类型成员变量,一定会先使用初始化列表初始化。

为什么会先使用初始化列表初始化?

如下,Date类没有默认构造函数,因为26行的构造函数不属于默认构造函数中的任意一种,在对Date类的对象d进行初始化时,会调用Date类的默认构造函数,所以对象d的day实参12和hour实参12都没有被传进去,_t作为Date类的自定义类型成员变量会调用Time类的默认构造函数,_hour默认传参为0,因此打印_hour的值也为0,d的参数没有传成功:

#include<iostream>
using namespace std;class Date; // 前置声明
class Time
{friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:Time(int hour = 0): _hour(hour){cout << _hour << endl;}private:int _hour;
};class Date
{
public:Date(int day, int hour){}private:int _day;Time _t;
};int main()
{Date d(12, 12);return 0;
}

假如Date类的构造函数不使用初始化列表进行初始化,使用函数体内初始化时,要把Date类的构造函数的形参hour的值给d,那么就必须构造一个Time类对象t,对该对象传参传hour,再使用赋值运算符重载函数将对象t拷贝给_t:

#include<iostream>
using namespace std;class Date; // 前置声明
class Time
{friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:Time(int hour = 0): _hour(hour){cout << _hour << endl;}private:int _hour;
};class Date
{
public://自定义类型,不使用初始化列表,就需要使用构造函数 + operator=Date(int day, int hour){//函数体内初始化Time t(hour);//调用Time类的构造函数_t = t;_day = day;}private:int _day;Time _t;
};int main()
{Date d(12, 12);cout << 4 << endl;return 0;
}

这还不如直接使用使用初始化列表初始化呢,还不需要赋值运算符重载函数:

class Date
{
public://自定义类型,使用初始化列表,只需要构造函数Date(int day, int hour):_t(hour){_day = day;}private:int _day;Time _t;
};

因此,建议尽量直接使用初始化列表进行初始化。 

(4)成员变量初始化的顺序就是成员变量在类中的声明次序,与初始化列表中的先后次序无关。

如下代码,类成员变量中先声明了_a2,再声明了_a1,因此初始化的顺序是先初始化_a2,再初始化_a1:

#include <iostream>
using namespace std;
class A
{
public:A(int a): _a1(a), _a2(_a1){}void Print(){cout << _a1 << " " << _a2 << endl;}
private:int _a2;//先声明_a2int _a1;//后声明_a1
};int main() {A aa(1);aa.Print();
}

先声明_a2就会先初始化_a2,用_a1初始化_a2,由于此时_a1还是随机值,因此_a2的值也是随机值,_a1使用a的值1进行初始化,因此,_a1的值为1:

所以,建议类中的成员变量声明的顺序和初始化列表中初始化的顺序一致。 

二、explicit关键字

1.内置类型的隐式转换

int i = 0;
double d = i;//隐式类型转换

根据监视可以看出: 

double d = i;并不是将i直接赋值给d,而是用i创建一个临时变量,再把临时变量的值给d,那么d改变的是临时变量的值,而不是i的值,因为程序执行完毕后,i的值并未发生改变。

如果d作为引用,那么必须加上const关键字进行修饰,因为d不是i的引用,是临时变量的引用,而临时变量具有常性,不允许引用权限放大。

int i = 0;
const double& d = i;//d引用了临时变量,临时变量具有常性,所以d也必须具有常性

2.如何避免单参构造函数初始化发生隐式类型转换

正常的类对象初始化如下面的aa1,也可以使用拷贝构造初始化,如aa2。由于c++支持隐式类型转换,因此也支持单参数构造函数初始化,如aa3:

#include<iostream>
using namespace std;class A
{
public :A(int a):_a(a){}private:int _a;
};int main()
{A aa1(1);//构造aa1对象A aa2(aa1);//拷贝构造,程序没写拷贝构造,编译器会自动生成拷贝构造函数,对内置类型完成浅拷贝A aa3 = 3;//单参数的构造函数,会发生隐式类型转换return 0;
}

那么

A aa3 = 3;

是如何支持类型转换的呢?

对于自定义类型A,aa3是A类型,3是整形。编译器会先拿A构造一个临时对象temp,3作为参数传给这个临时对象temp,再拿aa3(temp)去拷贝构造,发生隐式类型转换,即先构造,再拷贝构造:

//A aa3 = 3;  
A temp(3);   //先构造
A aa3(temp); //再拷贝构造

不过现在的编译器已经优化过了,会直接调用构造函数A aa(3)。

如果不想让单参数的构造函数发生隐式类型转换,可以使用explicit关键字修饰构造函数,表明该构造函数是显式的,而不是隐式的,就会避免发生不期望的类型转换,使用场景如下:

#include<iostream>
using namespace std;class A
{
public:A(int a):_a(a){}private:int _a;
};int main()
{A aa1(1);//构造aa1对象A aa2(aa1);//拷贝构造,程序没写拷贝构造,编译器会自动生成拷贝构造函数,对内置类型完成浅拷贝A aa3 = 'x';//先拿A构造一个临时对象temp,字符x作为参数传给这个临时对象temp,会发生隐式类型转换,再拿aa3(temp)去拷贝构造return 0;
}

aa3作为A类的对象,构造时传参应该传int型,但却传了char型,由于发生隐式类型转换,因此编译也没毛病,但是它传参就是不伦不类。这时候可以给A的构造函数加上explicit声明不让该单参构造函数发生隐式类型转换,编译就会报错:

class A
{
public:explicit A(int a):_a(a){}private:int _a;
};

 这时候只能乖乖给aa3传int型参数了。

三、匿名对象

1.匿名对象定义 

没有名字的对象叫做匿名对象,A(3)跟aa1和aa2相比少了个对象名,没有名字,aa1和aa2的生命周期在main函数内,A(3)的生命周期只在当前行:

#include<iostream>
using namespace std;class A
{
public:explicit A(int a):_a(a){cout << "A(int a):"<< a << endl;}A(const A& aa){cout << "A(const A&)" << endl;}~A(){cout << "~A()" << endl;}private:int _a;
};int main()
{A aa1(1);//生命周期在main函数内A aa2(aa1);//生命周期在main函数内A(3);//构造匿名对象,生命周期只在这一行return 0;
}

F10调试:当执行完A(3)还没执行return 0时,aa1和aa2的生命周期还没有结束,不会调用析构函数,此时打印的析构函数只能是匿名对象A(3)的析构函数:

 所以A(3)这一行执行完就调析构函数了。

2.匿名对象应用场景

假设有一个函数f,且A类的构造函数全缺省:

#include<iostream>
using namespace std;class A
{
public:A(int a = 0)//构造函数全缺省:_a(a){cout << "A(int a):"<< a << endl;}A(const A& aa){cout << "A(const A&)" << endl;}~A(){cout << "~A()" << endl;}void f()//f函数{cout << "f()" << endl;}private:int _a;
};int main()
{A aa1(1);//生命周期在main函数内A aa2(aa1);//生命周期在main函数内A(3);//构造匿名对象,生命周期只在这一行return 0;
}

调用f()函数时,需要定义一个A类对象,才能调用A类函数f,这就需要写两行:

int main()
{A aa1(1);//生命周期在main函数内A aa2(aa1);//生命周期在main函数内A(3);//构造匿名对象,生命周期只在这一行A aa4;//需要定义一个A类对象,才能调用faa4.f();return 0;
}

对象aa4 在main函数结束后才会销毁。如果定义对象只是为了调用函数,那么可以考虑直接定义一个匿名对象:

int main()
{A aa1(1);//生命周期在main函数内A aa2(aa1);//生命周期在main函数内A(3);//构造匿名对象,生命周期只在这一行A aa4;//需要定义一个A类对象,才能调用faa4.f();A().f();//定义匿名对象来调用函数f()return 0;
}

这个匿名对象就是为了调用函数f,这个匿名对象后边也没人用它,在当前行调用完f()函数就销毁了。


http://chatgpt.dhexx.cn/article/r7AP53hc.shtml

相关文章

字符串逆序(数组倒序输出)

题目信息&#xff1a; 样例输出 copy edcba 新手导读&#xff1a; 题目描述信息&#xff1a;字符串的倒序输出肯定是字符数组的倒序输出&#xff1b; 输入信息&#xff1a;输入条件是最后字符不为回车&#xff1b;所以应该先用变量存入字符进行判断&#xff0c;再使用这个…

C语言实现字符串倒序

C语言显示字符串倒序 倒序字符串的方法很多&#xff0c;这里给出常见的两种 1.倒序字符串指针法 void reverse_string(char *arr) {int len strlen(arr);char* left arr; //指向头部的指针char* right arr (len - 1); //指向尾部的指针char temp;while(left <…

C语言:输入一串字符串,并倒序输出(vs)

一&#xff1a;问题&#xff1a; 输入一串字符串倒序输出 例&#xff1a;输入abcdefg 输出gfedcba 二&#xff1a;思路&#xff1a; &#xff08;1&#xff09;先输入一串字符串&#xff0c;然后利用for语句倒序输出即可&#xff1b; 三&#xff1a;实现过程&#xff1a; …

字符串倒序输出

题目&#xff1a;输入一个字符串&#xff0c;将该串倒序输出。例如输入字符串hello&#xff0c;倒序输出为olleh。 str1 str(input(请输入字串: )) print(输入的字符是&#xff1a;%s%str1) str2 #定义一个空串用来接收倒序后的字串 for i in str1[::-1]:#对字串进行倒序输出…

字符串的倒叙输出(直接倒叙和单词倒叙)

一.字符串的直接倒叙 输入一个字符串&#xff0c;实现倒叙输出,我想到的方法有两种&#xff1a; 首先把输入的字符串存入一个数组&#xff1b; 1.方法一&#xff1a;直接逆序打印这个数组&#xff0c;即从最后一个元素向前打印&#xff1b; C代码&#xff1a; #include <std…

字符串倒序输出的五种方法

1. 使用数组循环 public static String array(String s){ int lengths.length(); char[] arrays.toCharArray(); for(int i0;i<length/2;i){ array[i]s.charAt(length-1-i); array[length-1-i]s.charAt(i); } return new String(array); } 2. StringBuffer的reverse方法 pu…

idea中git更新代码到本地之后自己写的代码不见了

今天在合并分支的时候不小心选错了合并选项&#xff0c;导致本地刚写的代码都没了 解决办法 1.找到本地历史记录 2.选择自己想要的版本 3.点击RevertSelection恢复

Git篇:使用Git将代码库更新到本地(完整版)

环境&#xff1a;Git已安装&#xff08;皮一下&#xff09; 新建一文件夹 右键&#xff0c;Git Bash Here git init 添加库 git remote add origin 想要更新的源码地址 将库里的代码下载到本地 git pull origin master 到此为止&#xff0c;第一次下载项目流程就结束了&am…

git 查看远程分支更新到本地,本地代码暂存,取出

一 、远程分支更新到本地 1.将一个项目更新到本地 git clone 项目地址 2.进入项目文件夹下 git branch 会显示 3. 在远端新建分支。 新建一个dev分支。大家合并的分支。再给每一个开发者建一个分支。比如我的jhj分支 4 .获取远端的分支&#xff0c;新建成功后在输入下边…

从远程仓库git更新(update)代码,显示更新成功,但是本地代码并没有改变

没有拉取更新成功的原因是本地有修改内容&#xff0c;解决方法如下&#xff1a; 1、在要更新的代码文件所在的文件夹&#xff08;含.git文件&#xff09;中打开gitBash&#xff0c;然后采用以下代码隐藏本地修改。 git stash结果如图&#xff1a; 2、选择IDEA中Git里面的upd…

git更新代码到本地后发现自己的代码不见了

解决办法&#xff1a; 利用webstorm的git插件恢复代码 然后选择你需要恢复的文件点击恢复按钮即可 可以双击文件查看要恢复的文件和本地文件的区别

GitHub上传本地代码/更新本地代码

上传本地代码 第一步&#xff1a;去github上创建自己的Repository&#xff0c;创建页面如下图所示&#xff1a; 红框为新建的仓库的https地址 第二步&#xff1a; echo “# aaa” >> README.md 注意&#xff1a;aaa是新创建的仓库名 第三步&#xff1a;建立git仓…

Git拉取上传更新代码

目录 一、将git上的项目拉取到本地 二、git提交更新代码 一、git上的项目拉取到本地 1.在本地创建一个文件夹 2.在本地文件夹中右键选择git bash here 3.在终端中输入克隆远程库的命令:git clone git上的项目地址 二、git提交更新代码 1.同步远程代码git pull origin mast…

使用Git将代码传送(更新或者下载到本地)到github/gitee

一.上传到Github 1.首先要下载git&#xff0c;正常下载就行了 2.下载git后&#xff0c;在终端输入git init&#xff1a; 如下&#xff1a;我需要将 这个文件夹中的程序上传到github。 那么就在这里输入git init 3.之后再输入git add . 4. 然后在输入git status&#xff0c;…

0801-2execjs安装

删除线格式 这个老哥的教程太靠谱了&#xff0c;我给他点了个赞 重装VNPY 之前安装的不知道为什么没有vnpy文件夹 感谢百度 要不然我重新安不上了 进入盘符 /d 感谢

execjs执行包含中文参数的JavaScript

抓取到了一段包含数据的JavaScript代码&#xff1a; import re import requestsheaders {"user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36","accept": &quo…

python运行execjs中出现编码问题

在破解豆瓣的window.__DATA__时&#xff0c;使用python的execjs执行js&#xff0c;会有这个错误&#xff1a; Exception in thread Thread-1: Traceback (most recent call last):File "E:\codes\python\lib\threading.py", line 917, in _bootstrap_innerself.run()…

解决execjs gbk编码错误

解决execjs subprocess中报错&#xff0c;UnicodeDecodeError: ‘gbk’ codec can’t decode byte 0xaf in position 1527: illegal multibyte sequence 问题描述 如果使用execjs执行含有中文的js文件&#xff0c;将会报错gbk无法解码文件的问题 报错如图所示&#xff1a; 解…

Python 使用execjs调用网页js 进行数据加密

最近做一个数据采集项目的时候需要自动采集网站的招投标数据 &#xff0c;随便打开一个网站 打开开发者模式&#xff0c;输入关键词&#xff0c;点击搜索&#xff0c;获得以下内容 可以看到请求链接和请求类型&#xff0c;请求类型Content-Type:是application/x-www-form-urle…

execjs的学习

execjs的学习 1.基本使用 pip install pyExecJs #安装模块 apt-get install nodejs # execjs模块基于node运行的,&#xff08;此方式是在Linux中使用 windows请安装node包&#xff09;import execjs js""" // 函数 function getnow(){var timestampnew Date()…