ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C++类和对象(四)—— 初始化列表、类型转换、static、友元、内部类、匿名对象与编译器优化

C++类和对象(四)—— 初始化列表、类型转换、static、友元、内部类、匿名对象与编译器优化 文章目录1. 再探构造函数1.1 初始化列表1.1.1 初始化列表形式1.1.2 初始化列表的本质1.1.3 初始化列表的先后顺序1.1.4 初始化列表的缺省值1.1.5 两个缺省值对比1.1.6 一定要在初始化列表中显式写的变量1.1.7 初始化列表的总结2 类型转换2.1 单参数隐式类型转换2.2 多参数隐式类型转换2.3 类类型之间的隐式类型转换2.4 expilcit --- 禁用隐式类型转换3 static成员3.1 static成员变量3.1.1 static成员变量初始化的位置3.1.2 static成员变量的从属3.1.3 static成员变量的访问3.1.4 static成员变量不能给缺省值3.2 static成员函数3.2.1 static 成员函数的特点3.2.2 static成员函数和非static成员函数3.2.3 static成员函数的类外访问方式4 友元4.1 友元函数4.1.1 友元函数的作用4.1.2 友元声明4.1.3 友元函数的一对多4.2 友元类4.2.1 友元类的作用4.2.2 友元类没有双向性4.2.3 友元类没有传递性4.2.4 友元类的问题5 内部类5.1 内部类的特点5.2 内部和外部类的关系5.3 内部类的用处6 匿名对象6.1 匿名对象和有名对象6.2 匿名对象的作用6.3 匿名对象和临时对象7 对象拷贝时的编译器优化7.1 两次连续构造7.1.1 正常优化7.1.2激进优化7.2 三次连续构造这里是think的博客希望可以一起交流知识一起think今天我们来学习C类和对象四一起来think1. 再探构造函数1.1 初始化列表1.1.1 初始化列表形式classA{public:A(inta0):_a1(a),_a2(2),_a3(4){coutA(int a)endl;}private:int_a11;int_a2;int_a3;};这里的:_a1(a),_a2(2),_a3(4)就是初始化列表,可以看到初始化列表是以冒号开始对成员变量进行初始化其中以逗号分隔各个成员变量其中可以换行可以不换行建议换行防止一行太长。初始化列表中可以用值初始化成员变量也可以用表达式初始化:p ((int*)malloc(sizeof(int)*6));1.1.2 初始化列表的本质初始化列表可以认为是成员变量定义初始化的地方即private:int_a11;int_a2;int_a3;这里是类成员变量中的声明语法上认为初始化列表是定义时初始化的地方所以既然是定义时初始化的地方那么每一个成员变量都要经过初始化列表。1.1.3 初始化列表的先后顺序初始化列表是定义时初始化的地方但是初始化的顺序并不是按照初始化列表来的而是按照声明顺序来的。1.1.4 初始化列表的缺省值private:int_a11;int_a2;int_a3;这个int _a1 1;就是缺省值即如果初始化列表中没有这个成员变量初始化列表就会用这个来初始化。1.1.5 两个缺省值对比classA{public:A(inta0):_a1(a),_a2(2),_a3(4){coutA(int a)endl;}private:int_a11;int_a2;int_a3;};intmain(){Aa(1);return0;}可以看到初始化列表的缺省值和函数形参的缺省值是比较容易混淆的函数形参的缺省值是在该处没有参数时使用缺省值传递给形参而初始化列表的缺省值则是在没有显示写的时候用缺省的初始化成员变量。1.1.6 一定要在初始化列表中显式写的变量对于const 成员变量引用成员变量没有默认构造的类类型变量一定要在初始化列表中显式写对于前面两个我们都知道是只能在定义时初始化之后就不能修改对应的值了。对于没有默认构造的类类型变量除了在没有默认构造函数的类中补上默认构造函数外解决方法就是在初始化列表中显式初始化了。1.1.7 初始化列表的总结初始化列表分为显式写和不显式写的显式写的就用显式写的来初始化成员变量不显式写的如果有声明处缺省值a就用缺省值没有缺省值b如果是内置类型x一般编译器不做处理但是也可能初始化为0如果是自定义类型y调用它的默认构造函数如果没有默认构造函数就报错。2 类型转换#includeiostreamusingnamespacestd;classA{public:A(inta1):_a1(a1){}//explicit A(int a1, int a2)A(inta1,inta2):_a1(a1),_a2(a2){}voidPrint(){cout_a1 _a2endl;}intGet()const{return_a1_a2;}private:int_a11;int_a22;};classB{public:B(constAa):_b(a.Get()){}private:int_b0;};intmain(){A aa11;aa1.Print();constAaa21;A aa3{2,2};B baa3;constBrbaa3;return0;}2.1 单参数隐式类型转换A aa1 1;语法就是1隐式类型转换为A类型的临时对象这个过程又叫做构造再拷贝构造给aa1因为构造再拷贝构造太繁琐了可以优化所以一般现在的编译都会优化为直接构造即1直接构造aa1。其中const A aa2 1;这里就不会优化了因为没有连续的构造这里就只是1隐式类型转换为A的临时对象构造然后aa2引用这个临时对象。2.2 多参数隐式类型转换在C11中才有多参数隐式类型转换其中单参数是没有括号的而多参数的则是由一个大括号优化也是和单参数一样的。2.3 类类型之间的隐式类型转换classB{public:B(constAa):_b(a.Get()){}private:int_b0;};B baa3;constBrbaa3;return0;这里其实说明了不只是整型才可以隐式类型转换如果参数中有其他类类型的对象类类型之间也是可以隐式类型转换的至于优化和整型是一样的。2.4 expilcit — 禁用隐式类型转换explicit A(int a1, int a2)加上这个关键字后就不能在隐式类型转换了如A aa3 { 2,2 };必须要显式写成这样A aa3(2,2);3 static成员3.1 static成员变量3.1.1 static成员变量初始化的位置static成员要在类外初始化同时初始化的时候不需要加上static声明时加即可因为static即表示该变量存入静态区后续初始化就没有必要再加了。#includeiostreamusingnamespacestd;classA{public:A(inta1):_a1(a1){}staticintGet(){return_a2;}private:int_a11;staticint_a2;};intA::_a2100;intmain(){coutA::Get()endl;return0;}3.1.2 static成员变量的从属static成员变量不同于其他的成员变量static成员变量不属于某一个对象而是被类的所有对象所共享并且static成员变量也不在对象中而是在静态区中。可以认为static成员变量是公共的而其他成员变量是私有的3.1.3 static成员变量的访问static成员变量是公共的那么可以A::_a2;也可以A a; a._a2;(pubilc下才可以这样访问否则要写一个函数来访问这也说明static成员变量受访问限定符的限制当然static成员函数也受限制)3.1.4 static成员变量不能给缺省值由于static成员变量属于类本身所有对象共享同一份而普通成员变量的缺省值是对象初始化时使用的规则每创建一个对象都会根据这个规则初始化自己的成员变量。因此 static 成员变量不适合使用普通成员的缺省初始化方式。3.2 static成员函数3.2.1 static 成员函数的特点普通成员函数属于类但是依赖对象执行所以有 this 指针。static 成员函数属于类但是不依赖对象所以没有 this 指针。3.2.2 static成员函数和非static成员函数static成员函数因为没有this指针所以不能使用除了static成员变量的其他成员变量同时也不能使用非static成员函数。但是非static成员函数可以使用static成员变量和static成员函数。3.2.3 static成员函数的类外访问方式和static成员变量一样static成员函数也是有两种访问方式A::Get();也可以A a; a.Get();作为成员static成员也是受访问限定符的限制的。4 友元4.1 友元函数#includeiostreamusingnamespacestd;classB;classA{// 友元声明friendvoidfunc(constAaa,constBbb);private:int_a11;int_a22;};classB{// 友元声明friendvoidfunc(constAaa,constBbb);private:int_b13;int_b24;};voidfunc(constAaa,constBbb){coutaa._a1endl;coutbb._b1endl;}intmain(){A aa;B bb;func(aa,bb);return0;}在函数声明前面加上一个friend放入类中就成为了这个类的友元函数。4.1.1 友元函数的作用友元函数可以访问该类的私有保护成员但是它不是成员函数也没有this指针。4.1.2 友元声明友元声明可以在类的任意位置不受访问限定符的限制。4.1.3 友元函数的一对多一个函数可以是多个类的友元即我可以是A的朋友也可以是B的朋友。4.2 友元类#includeiostreamusingnamespacestd;classA{// 友元声明friendclassB;private:int_a11;int_a22;};classB{public:voidfunc1(constAaa){coutaa._a1endl;cout_b1endl;}voidfunc2(constAaa){coutaa._a2endl;cout_b2endl;}private:int_b13;int_b24;};intmain(){A aa;B bb;bb.func1(aa);bb.func1(aa);return0;}4.2.1 友元类的作用友元类可以访问外部类的私有和保护成员即是友元类中的所有成员函数都是外部类的友元函数都可以访问外部类的私有和保护成员4.2.2 友元类没有双向性友元类是外部类的朋友可以访问外部类的私有和保护成员但是如果友元类中没有写外部类是它的朋友那么外部类不能访问友元类的私有和保护成员。类比就是你是我的朋友你可以来我家玩但是可能你可能没有把我当成朋友可能不让我去你家玩。4.2.3 友元类没有传递性A是B的友元类B是C的友元类不可以说A是C的友元。4.2.4 友元类的问题如果过多使用友元类会导致耦合性太强可能我这里用了你的成员你这里用了我的成员导致我改一个类的时候很有可能另一个类使用了我改的这个部分导致还需要再改这个类才能保证运行正确。当然同时也会导致封装性被破坏因为友元类不就是可以访问其他类的私有和保护成员吗过多使用导致访问限定符用处减少封装性被破坏。5 内部类#includeiostreamusingnamespacestd;classA{private:staticint_k;int_h1;public:classB// B默认就是A的友元{public:voidfoo(constAa){cout_kendl;//OKcouta._hendl;//OK}int_b1;};};intA::_k1;intmain(){coutsizeof(A)endl;A::B b;A aa;b.foo(aa);return0;}5.1 内部类的特点在计算外部类的大小时是不会加上内部类的大小的因为内部类是独立的类受到的限制就是实例化内部类对象时会受到外部类类域的限制如A::B b;同时内部类也是受到内部类访问限定符的限制的。5.2 内部和外部类的关系内部类默认是外部类的友元 但是外部类默认不是内部类的友元。友元单向5.3 内部类的用处内部类也是一种封装如果A类和B类联系紧密A类实现出来主要是给B类使用的那么设计成内部类是比较合适的并且如果不想内部类被外部使用也可以用访问限定符来限制。6 匿名对象#includeiostreamusingnamespacestd;classA{public:A(inta0):_a(a){coutA(int a)endl;}~A(){cout~A()endl;}private:int_a;};classSolution{public:intSum_Solution(10){//...return1;}};intmain(){A aa1;//A aa1();A();A(1);Aaa2(2);Solution sol;sol.Sum_Solution(10);Solution().Sum_Solution(10);return0;}6.1 匿名对象和有名对象有名对象就是A aa1;,匿名对象就是A()有必要说明一点有名对象不可以A aa1();因为会和函数声明产生歧义如void func()匿名对象可以加上括号因为不会产生歧义。6.2 匿名对象的作用匿名对象的作用其实就是让使用更加简单了。Solution sol;sol.Sum_Solution(10);Solution().Sum_Solution(10);本来要写两行的现在只用写一行了6.3 匿名对象和临时对象匿名对象是临时对象的一种生命周期在这一行所以临时使用一下变量就可以用匿名对象。7 对象拷贝时的编译器优化#includeiostreamusingnamespacestd;classA{public:A(inta0):_a1(a){coutA(int a)endl;}A(constAaa):_a1(aa._a1){coutA(const A aa)endl;}Aoperator(constAaa){coutA operator(const A aa)endl;if(this!aa){_a1aa._a1;}return*this;}~A(){cout~A()endl;}private:int_a11;};voidf1(A aa){}Af2(){A aa;returnaa;}intmain(){A aa1;f1(aa1);coutendl;f1(1);f1(A(2));coutendl;cout***********************************************endl;f2();coutendl;A aa2f2();coutendl;aa1f2();coutendl;return0;}7.1 两次连续构造7.1.1 正常优化A aa1; f1(aa1);先构造再拷贝构造由于这里是在两行的所以正常优化和激进优化都没有将这个优化掉。但是不排除特别激进的编译器会优化掉因为后面没有地方再使用aa1可能编译器会依据这个来优化f1(1);先1隐式类型转换为临时对象构造临时对象再拷贝构造给aa连续构造拷贝构造编译器之间优化为1构造aa。f1(A(2));先构造匿名对象匿名对象再拷贝构造aa优化为2构造aa。7.1.2激进优化f2();aa2先构造再传值返回aa2即aa2拷贝构造临时对象其实这里不属于连续的构造加拷贝构造因为是两行正常优化编译器是不优化的但是激进编译器还是会优化为构造即不构造aa了直接构造临时对象激进编译器就算你在中途改变了值也是可以优化的是比较激进的但是结果还是正确的。Af2(){A aa;aa;//如果重载了后置returnaa;}7.2 三次连续构造A aa2 f2();不优化是先构造aa再拷贝构造临时对象再拷贝构造aa2正常优化是构造aa再拷贝构造aa2激进优化是直接构造aa2。aa1 f2();不优化就是构造拷贝构造加上赋值重载正常优化就是不优化的情况激进优化是构造加赋值重载直接构造临时对象了赋值重载不算构造的一种所以编译器没有优化。
返回列表