c语言初始化队列(【C++】– 初始化列表)
目录
一 、用初始化列表初始化对象
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版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!