首页IT科技c语言初始化队列(【C++】– 初始化列表)

c语言初始化队列(【C++】– 初始化列表)

时间2025-09-19 10:26:12分类IT科技浏览4804
导读:目录...

目录

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

1.初始化列表用法

2.初始化列表特性

二                        、explicit关键字

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.初始化列表用法

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

(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;//先声明_a2 int _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类对象                       ,才能调用f aa4.f(); return 0; }

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

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

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

声明:本站所有文章                       ,如无特殊说明或标注                        ,均为本站原创发布                。任何个人或组织,在未征得本站同意时               ,禁止复制        、盗用        、采集                       、发布本站内容到任何网站                、书籍等各类媒体平台                       。如若本站内容侵犯了原著者的合法权益                        ,可联系我们进行处理        。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
织梦文章标题显示不全(织梦栏目列表、文章、TAG列表、自由列表、搜索列表等分页样式修改)