👉 欢迎阅读这篇文章 👇目录1、构造函数延伸2、类型转换3、static成员4、友元5、内部类6、匿名对象7、对象拷贝时的编译器优化1、构造函数延伸我们上一篇文章中介绍了构造函数,我们在实现构造函数的时候,初始化成员变量主要使用函数体赋值,构造函数的初始化还有另一种方式——初始化列表,初始化列表的使用方式是以一个冒号开始,接着是一个以逗号分割的数据成员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。classDate{public:Date(intyear=1,intmonth=1,intday=1):_year(year),_month(month),_day(day){}private:int_year;int_month;int_day;};每个成员变量在初始化列表中只能出现一次,在语法上可以将初始化列表理解为每个成员变量定义初始化的地方。引用成员变量、const成员变量、没有默认构造的类类型变量,必须放在初始化位置进行初始化。(可以理解为这三个定义和初始化要同时进行,所以只能在初始化列表定义的同时就初始化了)classTime{public://没有默认构造函数Time(inthour):_hour(hour){cout<<"Time()"<<endl;}private:int_hour;};classDate{public:Date(int&x,intyear=1,intmonth=1,intday=1):_year(year),_month(month),_day(day),_ref(x),_t(12),_n(1){// error C2512: “Time”: 没有合适的默认构造函数可⽤// error C2530 : “Date::_ref” : 必须初始化引⽤// error C2789 : “Date::_n” : 必须初始化常量限定类型的对象}private://声明int_year;int_month;int_day;//下面这三个可以理解为定义和初始化要同时进行,所以只能在初始化列表定义的同时就初始化了int&_ref;//引用Time _t;//没有默认构造的类类型变量constint_n;//const成员变量};C++11支持在成员变量声明的时候,给缺省值,这个缺省值主要是给没有显式在初始化列表初始化的成员使用的。尽量使用初始化列表初始化,因为那些不在初始化列表初始化的成员,如果这个成员声明的时候给了缺省值,那么初始化列表就会用这个缺省值初始化。如果声明的时候没有给缺省值,而且没有显式在初始化列表初始化的成员对于内置类型成员,是否初始化取决于编译器,C++没有规定。对于自定义类类型成员,会调用这个成员类型的默认构造函数,如果没有默认构造函数,就会报错。classTime{public://没有默认构造函数Time(inthour):_hour(hour){cout<<"Time()"<<endl;}private:int_hour;};classDate{public:Date(int&x)//成员声明的时候给了缺省值,那么初始化列表就会用这个缺省值初始化。:_ref(x),_t(12){cout<<"Date(int& x)"<<endl;}private://声明int_year=2026;//给缺省值int_month=10;//给缺省值int_day;//没有给缺省值,而且没有显式在初始化列表初始化的成员,内置类型成员,是否初始化取决于编译器,C++没有规定。//下面这三个可以理解为定义和初始化要同时进行,所以只能在初始化列表定义的同时就初始化了int&_ref;//引用——必须在初始化列表中初始化//Time _t;//没有默认构造的类类型变量——没有给缺省值,而且没有显式在初始化列表初始化的成员,自定义类型成员,会调用它的默认构造。——没有默认构造,报错Time _t;//没有默认构造的类类型变量——没有给缺省值,但是显式在初始化列表初始化的成员,会在初始化列表中初始化。constint_n=10;//const成员变量,声明给缺省值};intmain(){intx=0;Dated1(x);return0;}初始化列表总结⽆论是否显式写初始化列表,每个构造函数都有初始化列表;⽆论是否在初始化列表显式初始化成员变量,每个成员变量都要⾛初始化列表初始化;成员变量走初始化列表的步骤(如下图)初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆关。建议声明顺序和初始化列表顺序保持⼀致。练习下⾯程序的运⾏结果是什么()A. 输出 1 1B. 输出 2 2C. 编译报错D. 输出 1 随机值E. 输出 1 2F. 输出 2 1classA{public:A(inta):_a1(a),_a2(_a1){}voidPrint(){cout<<_a1<<" "<<_a2<<endl;}private:int_a2=2;int_a1=2;};intmain(){Aaa(1);aa.Print();}已知初始化列表中按照成员变量在类中声明顺序进⾏初始化那么就会先初始化_a2,在初始化列表中显式写了,那么就按初始化列表中给的值初始化,给了_a1,这时_a1还未进行初始化,是随机值,所以_a2就是随机值。再初始化_a1,在初始化列表中显式写了,那么就按初始化列表中给的值初始化,给了1,所以_a1就是1。输出1、随机值。通过上方的练习题我们就可以感受到如果声明顺序和初始化列表顺序不保持⼀致,那么就有可能出现随机值。2、类型转换C++支持内置类型隐式类型转换为类类型对象,需要有相关的以内置类型为参数的构造函数。构造函数前⾯加explicit就不再⽀持隐式类型转换。classA{public://以内置类型为参数的拷贝构造//构造函数前加explicit就不再支持隐式转换//explicit A(int a)A(inta):_a1(a){}A(inta1,inta2):_a1(a1),_a2(a2){}voidPrint(){cout<<_a1<<endl;}private:int_a1;int_a2;};classArray{public:voidPushBack(constA&a){}};intmain(){//构造Aaa1(2);aa1.Print();//类型转换//语法逻辑上:构造(用内置类型2进行构造出临时对象)+拷贝构造+优化->构造A aa2=2;aa2.Print();//引用的是A类型的临时变量,要const引用constA&aa3=1;//使用场景Array arr;constA&aa4=2;arr.PushBack(aa4);//传参更方便(内置类型“2”隐式转换成自定义类型A类型的变量传参给PushBack)arr.PushBack(2);//构造Aaa5(2,3);//多个参数的类型转换,将内置类型用{}括起来A aa6={1,2};constA&aa7={1,2};Array arr2;arr2.PushBack(aa7);arr2.PushBack({2,3});}类类型的对象之间也可以隐式转换,需要相应的构造函数⽀持。classA{public://以内置类型为参数的拷贝构造//构造函数前加explicit就不再支持隐式转换//explicit A(int a)A(inta):_a1(a){}A(inta1,inta2):_a1(a1),_a2(a2){}voidPrint(){cout<<_a1<<endl;}intGet()const{return_a1+_a2;}private:int_a1;int_a2;};classB{public://构造函数——将A类型对象a的a1和a2之和拷贝给B类型——用于支持类与类之间的构造B(constA&a):_b(a.Get())//a是const A*类型的,所以Get需要是const成员函数{}private:int_b;};intmain(){//构造Aaa1(2);aa1.Print();//用aa1构造出B类型临时对象,拷贝给bb1B bb1=aa1;constB&bb2=aa1;}3、static成员用static修饰的成员变量,称为静态成员变量,静态成员变量一定要在类外进行初始化。静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。突破类域可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。静态成员也是类的成员,受public、protected、private 访问限定符的限制。静态成员变量不能在声明位置给缺省值,因为缺省值是给构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。classA{public://以内置类型为参数的构造函数//构造函数前加explicit就不再支持隐式转换//explicit A(int a)A(inta):_a1(a){}A(inta1,inta2):_a1(a1),_a2(a2){}voidPrint(){cout<<_a1<<endl;}staticintGetcount(){//return _a1 + _a2; static修饰的成员函数没有this指针,不可以访问非静态成员return_scount;//static静态成员函数可以访问其他静态成员,不可以访问非静态成员}intGet()const{//return _scount;returnGetcount();}private:int_a1;int_a2;public:staticint_scount;//声明静态成员变量};intA::_scount=2;//静态成员变量要在类外初始化intmain(){Aaa1(1);A aa2=2;cout<<A::_scount<<endl;//突破类域访问静态成员变量cout<<aa1._scount<<endl;cout<<A::Getcount()<<endl;//突破类域访问静态成员函数cout<<aa1.Getcount()<<endl;cout<<aa1.Get()<<endl;//非静态成员函数可以访问静态成员和静态成员函数return0;}// 实现一个类,计算程序中创建出了多少个类对象?classA{public://构造函数A(){++_scount;}//拷贝构造A(constA&a1){++_scount;}//析构~A(){--_scount;}staticintGetACount(){return_scount;}private:staticint_scount;};intA::_scount=0;intmain(){cout<<A::GetACount()<<endl;A a1,a2;Aa3(a1);cout<<A::GetACount()<<endl;cout<<a1.GetACount()<<endl;return0;}求1+2+3+…nclasssum{public:sum(){_ret+=i;i++;}staticintGetret(){return_ret;}private:staticint_ret;staticinti;};intsum::_ret=0;intsum::i=1;classSolution{public:intSum_Solution(intn){sum arr[n];//变长数组,实例化n次,执行n次构造函数,构造函数哪进行相加returnsum::Getret();}};4、友元友元提供了一种突破类访问限定符封装的方式,友元分为友元函数和友元类。在函数声明或者类声明前加friend,并且把友元声明放到⼀个类的⾥⾯。外部友元函数可访问类的私有和保护成员,友元函数仅仅是类的一种声明,不是类的成员函数。友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制。一个函数可以是多个类的友元函数。友元类中的成员函数可以是另一个类的友元函数,可以访问另一个类中的保护和私有成员。友元类的关系是单向的,不具有交换性,比如A是B的友元,但是B不是A的友元。友元类的关系不能传递如果A是B的友元, B是C的友元,但是A不是C的友元。有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多⽤。5、内部类如果一个类定义在另一个类的内部,这个类就是内部类。内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制访问限定符限制,所以外部类定义的对象中不包含内部类。内部类默认是外部类的友元类。内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使⽤,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地⽅都⽤不了。//内部类classA{private:staticint_k;int_h=1;public:classB{public:voidfoo(constA&a){cout<<_k<<endl;//OKcout<<a._h<<endl;//OK}int_b1;};};intA::_k=0;intmain(){cout<<sizeof(A)<<endl;A::B b;A aa;b.foo(aa);return0;}求1+2+3+…n//改造classSolution{public:classsum{public:sum(){_ret+=i;i++;}staticint_ret;staticinti;};intSum_Solution(intn){sum arr[n];//变长数组,实例化n次,执行n次构造函数,构造函数哪进行相加returnsum::_ret;}};intsum::_ret=0;intsum::i=1;6、匿名对象用类型(实参)定义出来的对象叫匿名对象。匿名对象⽣命周期只在当前⼀⾏,⼀般临时定义⼀个对象当前⽤⼀下即可,就可以定义匿名对象。//匿名对象classA{public:A(inta=0):_a(a){cout<<"A(int a)"<<endl;}~A(){cout<<"~A()"<<endl;}private:int_a;};classSolution{public:intSum_Solution(intn){//...returnn;}};voidPushBack(A aa){}intmain(){A aa1;//匿名对象,生命周期只有这一行,可以观察会进行析构A();A(1);//可以传参数构造Solution so;so.Sum_Solution(1);Solution().Sum_Solution(2);//利用匿名对象可以直接这样使用Aaa3(3);PushBack(aa3);PushBack(A(3));//与上方等价return0;}7、对象拷贝时的编译器优化现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传返回值的过程中可以省略的拷⻉。如何优化C++标准并没有严格规定,各个编译器会根据情况⾃⾏处理。当前主流的相对新⼀点的编译器对于连续⼀个表达式步骤中的连续拷⻉会进⾏合并优化,有些更新更"激进"的编译器还会进⾏跨⾏跨表达式的合并优化。在接收返回值的时候,尽量用拷贝构造接收返回值,而不是赋值重载,这样编译器有更大的空间去优化。
👉 欢迎阅读这篇文章 👇目录1、构造函数延伸2、类型转换3、static成员4、友元5、内部类6、匿名对象7、对象拷贝时的编译器优化1、构造函数延伸我们上一篇文章中介绍了构造函数,我们在实现构造函数的时候,初始化成员变量主要使用函数体赋值,构造函数的初始化还有另一种方式——初始化列表,初始化列表的使用方式是以一个冒号开始,接着是一个以逗号分割的数据成员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。classDate{public:Date(intyear=1,intmonth=1,intday=1):_year(year),_month(month),_day(day){}private:int_year;int_month;int_day;};每个成员变量在初始化列表中只能出现一次,在语法上可以将初始化列表理解为每个成员变量定义初始化的地方。引用成员变量、const成员变量、没有默认构造的类类型变量,必须放在初始化位置进行初始化。(可以理解为这三个定义和初始化要同时进行,所以只能在初始化列表定义的同时就初始化了)classTime{public://没有默认构造函数Time(inthour):_hour(hour){cout<<"Time()"<<endl;}private:int_hour;};classDate{public:Date(int&x,intyear=1,intmonth=1,intday=1):_year(year),_month(month),_day(day),_ref(x),_t(12),_n(1){// error C2512: “Time”: 没有合适的默认构造函数可⽤// error C2530 : “Date::_ref” : 必须初始化引⽤// error C2789 : “Date::_n” : 必须初始化常量限定类型的对象}private://声明int_year;int_month;int_day;//下面这三个可以理解为定义和初始化要同时进行,所以只能在初始化列表定义的同时就初始化了int&_ref;//引用Time _t;//没有默认构造的类类型变量constint_n;//const成员变量};C++11支持在成员变量声明的时候,给缺省值,这个缺省值主要是给没有显式在初始化列表初始化的成员使用的。尽量使用初始化列表初始化,因为那些不在初始化列表初始化的成员,如果这个成员声明的时候给了缺省值,那么初始化列表就会用这个缺省值初始化。如果声明的时候没有给缺省值,而且没有显式在初始化列表初始化的成员对于内置类型成员,是否初始化取决于编译器,C++没有规定。对于自定义类类型成员,会调用这个成员类型的默认构造函数,如果没有默认构造函数,就会报错。classTime{public://没有默认构造函数Time(inthour):_hour(hour){cout<<"Time()"<<endl;}private:int_hour;};classDate{public:Date(int&x)//成员声明的时候给了缺省值,那么初始化列表就会用这个缺省值初始化。:_ref(x),_t(12){cout<<"Date(int& x)"<<endl;}private://声明int_year=2026;//给缺省值int_month=10;//给缺省值int_day;//没有给缺省值,而且没有显式在初始化列表初始化的成员,内置类型成员,是否初始化取决于编译器,C++没有规定。//下面这三个可以理解为定义和初始化要同时进行,所以只能在初始化列表定义的同时就初始化了int&_ref;//引用——必须在初始化列表中初始化//Time _t;//没有默认构造的类类型变量——没有给缺省值,而且没有显式在初始化列表初始化的成员,自定义类型成员,会调用它的默认构造。——没有默认构造,报错Time _t;//没有默认构造的类类型变量——没有给缺省值,但是显式在初始化列表初始化的成员,会在初始化列表中初始化。constint_n=10;//const成员变量,声明给缺省值};intmain(){intx=0;Dated1(x);return0;}初始化列表总结⽆论是否显式写初始化列表,每个构造函数都有初始化列表;⽆论是否在初始化列表显式初始化成员变量,每个成员变量都要⾛初始化列表初始化;成员变量走初始化列表的步骤(如下图)初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆关。建议声明顺序和初始化列表顺序保持⼀致。练习下⾯程序的运⾏结果是什么()A. 输出 1 1B. 输出 2 2C. 编译报错D. 输出 1 随机值E. 输出 1 2F. 输出 2 1classA{public:A(inta):_a1(a),_a2(_a1){}voidPrint(){cout<<_a1<<" "<<_a2<<endl;}private:int_a2=2;int_a1=2;};intmain(){Aaa(1);aa.Print();}已知初始化列表中按照成员变量在类中声明顺序进⾏初始化那么就会先初始化_a2,在初始化列表中显式写了,那么就按初始化列表中给的值初始化,给了_a1,这时_a1还未进行初始化,是随机值,所以_a2就是随机值。再初始化_a1,在初始化列表中显式写了,那么就按初始化列表中给的值初始化,给了1,所以_a1就是1。输出1、随机值。通过上方的练习题我们就可以感受到如果声明顺序和初始化列表顺序不保持⼀致,那么就有可能出现随机值。2、类型转换C++支持内置类型隐式类型转换为类类型对象,需要有相关的以内置类型为参数的构造函数。构造函数前⾯加explicit就不再⽀持隐式类型转换。classA{public://以内置类型为参数的拷贝构造//构造函数前加explicit就不再支持隐式转换//explicit A(int a)A(inta):_a1(a){}A(inta1,inta2):_a1(a1),_a2(a2){}voidPrint(){cout<<_a1<<endl;}private:int_a1;int_a2;};classArray{public:voidPushBack(constA&a){}};intmain(){//构造Aaa1(2);aa1.Print();//类型转换//语法逻辑上:构造(用内置类型2进行构造出临时对象)+拷贝构造+优化->构造A aa2=2;aa2.Print();//引用的是A类型的临时变量,要const引用constA&aa3=1;//使用场景Array arr;constA&aa4=2;arr.PushBack(aa4);//传参更方便(内置类型“2”隐式转换成自定义类型A类型的变量传参给PushBack)arr.PushBack(2);//构造Aaa5(2,3);//多个参数的类型转换,将内置类型用{}括起来A aa6={1,2};constA&aa7={1,2};Array arr2;arr2.PushBack(aa7);arr2.PushBack({2,3});}类类型的对象之间也可以隐式转换,需要相应的构造函数⽀持。classA{public://以内置类型为参数的拷贝构造//构造函数前加explicit就不再支持隐式转换//explicit A(int a)A(inta):_a1(a){}A(inta1,inta2):_a1(a1),_a2(a2){}voidPrint(){cout<<_a1<<endl;}intGet()const{return_a1+_a2;}private:int_a1;int_a2;};classB{public://构造函数——将A类型对象a的a1和a2之和拷贝给B类型——用于支持类与类之间的构造B(constA&a):_b(a.Get())//a是const A*类型的,所以Get需要是const成员函数{}private:int_b;};intmain(){//构造Aaa1(2);aa1.Print();//用aa1构造出B类型临时对象,拷贝给bb1B bb1=aa1;constB&bb2=aa1;}3、static成员用static修饰的成员变量,称为静态成员变量,静态成员变量一定要在类外进行初始化。静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。突破类域可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。静态成员也是类的成员,受public、protected、private 访问限定符的限制。静态成员变量不能在声明位置给缺省值,因为缺省值是给构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。classA{public://以内置类型为参数的构造函数//构造函数前加explicit就不再支持隐式转换//explicit A(int a)A(inta):_a1(a){}A(inta1,inta2):_a1(a1),_a2(a2){}voidPrint(){cout<<_a1<<endl;}staticintGetcount(){//return _a1 + _a2; static修饰的成员函数没有this指针,不可以访问非静态成员return_scount;//static静态成员函数可以访问其他静态成员,不可以访问非静态成员}intGet()const{//return _scount;returnGetcount();}private:int_a1;int_a2;public:staticint_scount;//声明静态成员变量};intA::_scount=2;//静态成员变量要在类外初始化intmain(){Aaa1(1);A aa2=2;cout<<A::_scount<<endl;//突破类域访问静态成员变量cout<<aa1._scount<<endl;cout<<A::Getcount()<<endl;//突破类域访问静态成员函数cout<<aa1.Getcount()<<endl;cout<<aa1.Get()<<endl;//非静态成员函数可以访问静态成员和静态成员函数return0;}// 实现一个类,计算程序中创建出了多少个类对象?classA{public://构造函数A(){++_scount;}//拷贝构造A(constA&a1){++_scount;}//析构~A(){--_scount;}staticintGetACount(){return_scount;}private:staticint_scount;};intA::_scount=0;intmain(){cout<<A::GetACount()<<endl;A a1,a2;Aa3(a1);cout<<A::GetACount()<<endl;cout<<a1.GetACount()<<endl;return0;}求1+2+3+…nclasssum{public:sum(){_ret+=i;i++;}staticintGetret(){return_ret;}private:staticint_ret;staticinti;};intsum::_ret=0;intsum::i=1;classSolution{public:intSum_Solution(intn){sum arr[n];//变长数组,实例化n次,执行n次构造函数,构造函数哪进行相加returnsum::Getret();}};4、友元友元提供了一种突破类访问限定符封装的方式,友元分为友元函数和友元类。在函数声明或者类声明前加friend,并且把友元声明放到⼀个类的⾥⾯。外部友元函数可访问类的私有和保护成员,友元函数仅仅是类的一种声明,不是类的成员函数。友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制。一个函数可以是多个类的友元函数。友元类中的成员函数可以是另一个类的友元函数,可以访问另一个类中的保护和私有成员。友元类的关系是单向的,不具有交换性,比如A是B的友元,但是B不是A的友元。友元类的关系不能传递如果A是B的友元, B是C的友元,但是A不是C的友元。有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多⽤。5、内部类如果一个类定义在另一个类的内部,这个类就是内部类。内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制访问限定符限制,所以外部类定义的对象中不包含内部类。内部类默认是外部类的友元类。内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使⽤,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地⽅都⽤不了。//内部类classA{private:staticint_k;int_h=1;public:classB{public:voidfoo(constA&a){cout<<_k<<endl;//OKcout<<a._h<<endl;//OK}int_b1;};};intA::_k=0;intmain(){cout<<sizeof(A)<<endl;A::B b;A aa;b.foo(aa);return0;}求1+2+3+…n//改造classSolution{public:classsum{public:sum(){_ret+=i;i++;}staticint_ret;staticinti;};intSum_Solution(intn){sum arr[n];//变长数组,实例化n次,执行n次构造函数,构造函数哪进行相加returnsum::_ret;}};intsum::_ret=0;intsum::i=1;6、匿名对象用类型(实参)定义出来的对象叫匿名对象。匿名对象⽣命周期只在当前⼀⾏,⼀般临时定义⼀个对象当前⽤⼀下即可,就可以定义匿名对象。//匿名对象classA{public:A(inta=0):_a(a){cout<<"A(int a)"<<endl;}~A(){cout<<"~A()"<<endl;}private:int_a;};classSolution{public:intSum_Solution(intn){//...returnn;}};voidPushBack(A aa){}intmain(){A aa1;//匿名对象,生命周期只有这一行,可以观察会进行析构A();A(1);//可以传参数构造Solution so;so.Sum_Solution(1);Solution().Sum_Solution(2);//利用匿名对象可以直接这样使用Aaa3(3);PushBack(aa3);PushBack(A(3));//与上方等价return0;}7、对象拷贝时的编译器优化现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传返回值的过程中可以省略的拷⻉。如何优化C++标准并没有严格规定,各个编译器会根据情况⾃⾏处理。当前主流的相对新⼀点的编译器对于连续⼀个表达式步骤中的连续拷⻉会进⾏合并优化,有些更新更"激进"的编译器还会进⾏跨⾏跨表达式的合并优化。在接收返回值的时候,尽量用拷贝构造接收返回值,而不是赋值重载,这样编译器有更大的空间去优化。