【C++】类和对象(三)
1.构造函数初始化列表构造函数初始化可以使用初始化列表。初始化列表是以一个冒号开始接着是一个以逗号分隔的数据成员列表每个“成员变量”后面跟一个放在括号中的初始值或表达式。每个成员变量在初始化列表只能出现一次。从语法上可以将初始化列表看作每个成员变量定义初始化的地方。引用成员变量、const成员变量、没有默认构造的类成员变量必须放在初始化列表位置进行初始化否则会编译报错。必须在定义的地方初始化C11支持在成员变量声明的位置给缺省值这个缺省值主要是给没有显式在初始化列表初始化的成员使用的。尽量使用初始化列表进行初始化因为那些不在初始化列表初始化的成员也会走初始化列表因为初始化列表是每个成员定义的地方。如果这个成员在声明位置给了缺省值初始化列表会用这个缺省值进行初始化如果没有给缺省值对于没有显式在初始化列表初始化的内置类型成员是否初始化取决于编译器C没有规定。对于没有显式在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数如果没有默认构造会编译错误。初始化列表中按照成员变量在类中的声明顺序进行初始化声明的顺序是在类中存放的顺序与成员在初始化列表出现的先后顺序无关。建议声明顺序与初始化列表顺序保持一致。初始化列表总结无论是否写初始化列表每个构造函数都有初始化列表无论是否在初始化列表显式初始化成员变量每个成员变量必须走初始化列表.#includeiostreamusingnamespacestd;classTime{public:Time(inthour):_hour(hour){coutTime()endl;}private:int_hour;};classDate{public:Date(intx,intyear1,intmonth1,intday1):_year(year),_month(month),_day(day),_t(12),_ref(x),_n(1){}voidPrint()const{cout_year-_month-_dayendl;}private:int_year;int_month;int_day;Time _t;int_ref;constint_n;};intmain(){inti0;Dated1(i);d1.Print();return0;}#includeiostreamusingnamespacestd;classTime{public:Time(inthour):_hour(hour){coutTime()endl;}private:int_hour;};classDate{public:Date():_month(2){coutDate()endl;}voidPrint(){cout_year-_month-_dayendl;}private:// 这里不是初始化这里给的是缺省值这个缺省值是给初始化列表的// 如果初始化列表没有显式初始化默认就会用这个缺省值初始化int_year1;int_month1;int_day;Time _t1;constint_n1;int*_ptr(int*)malloc(12);};intmain(){Date d1;d1.Print();return0;}2.类型转换C支持内置类型隐式类型转换为类类型对象需要有相关内置类型为参数的构造函数构造函数前面加 explicit 就不再支持隐式类型转换类类型的对象之间也可以隐式转换需要相应的构造函数支持#includeiostreamusingnamespacestd;classA{public:A(inta1):_a1(a1){}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的临时对象再用这个临时对象拷贝构造aa3// 编译器遇到连续构造拷贝构造 - 优化为直接构造A aa11;aa1.Print();constAaa21;// C11之后才支持多参数转化A aa3{2,2};// aa3隐式类型转换为b对象// 原理跟上面类似B baa3;constBrbaa3;return0;}3.static成员用static修饰的成员变量称为静态成员变量静态成员变量一定要在类外进行初始化。静态成员变量为当前类所有对象所共享不属于某个具体的对象不存在对象中存放在静态区。用static修饰的成员函数称之为静态成员函数静态成员函数没有this指针。静态成员函数中可以访问其他的的静态成员但是不能访问非静态的因为没有this指针。非静态的成员函数可以访问任意的静态成员变量和静态成员函数。突破类域就可以访问静态成员可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。静态成员也是类的成员受public、protected、private访问限定符的限制静态成员变量不能在声明位置给缺省值初始化因为缺省值是给构造函数初始化列表的静态成员变量不属于某个对象不走构造函数初始化列表。#includeiostreamusingnamespacestd;classA{public:A(){_scount;}A(constAa){_scount;}~A(){--_scount;}staticintGetACount(){return_scount;}private:// 类里面声明staticint_scount;};// 类外面初始化intA::_scount0;intmain(){coutA::GetACount()endl;A a1,a2;Aa3(a1);coutA::GetACount()endl;couta1.GetACount()endl;return0;}4.友元友元提供了一种突破类访问限定符封装的方式友元分为友元函数和友元类在函数声明或者类声明前面加 friend 并把友元声明放到一个类里面任意位置外部友元函数可访问类的私有和保护成员友元函数仅仅是一种声明他不是类的成员函数友元函数可以在类定义的任何地方声明不受类访问限定符限制一个函数可以是多个类的友元函数友元类中的成员函数都可以是另一个类的友元函数都可以访问另一个类中的私有和保护成员友元类的关系是单向的不具有交换性比如A类是B类的友元但是B类不是A类的友元友元类关系不能传递比如A是B的友元B是C的友元但 A不是C的友元友元提供了便利但会增加耦合度破坏了封装所以不宜多用#includeiostreamusingnamespacestd;// 前置声明否则A的友元函数声明编译器不认识BclassB;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;}#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.func2(aa);return0;}5.内部类如果一个类定义在另一个类的内部就叫做内部类。内部类是一个独立的类跟定义在全局相比它只是受外部类类域限制和访问限定符限制所以外部类定义的对象中不包含内部类。内部类默认是外部类的友元类内部类的本质也是一种封装当A类和B类紧密关联A类实现出来主要就是给B类使用那么可以考虑把A类设计为B的内部类如果放到protected/private位置那么A类就是B类的专属内部类其他地方都用不了。#includeiostreamusingnamespacestd;classA{public:classB{public:voidfoo(constAa){cout_kendl;couta._hendl;}int_b1;};private:staticint_k;int_h1;};intA::_k1;intmain(){coutsizeof(A)endl;// sizeof(A)不包含B的大小A::B b;// 指定类域A aa;b.foo(aa);return0;}6.匿名对象用类型(实参)定义出来的对象叫匿名对象类型 对象名(实参) 定义的叫有名对象。匿名对象生命周期只在当前一行一般定义一个对象当前用一下即可就可以定义成匿名对象#includeiostreamusingnamespacestd;classA{public:A(inta0):_a(a){coutA(int a)endl;}~A(){cout~A()endl;}private:int_a;};classSolution{public:intSum_Solution(intn){returnn;}};intmain(){A aa1;// 不可以这样定义对象因为编译器无法识别是函数声明还是对象定义//A aa1();// 可以这样定义匿名对象匿名对象的特点是不用取名字// 但是他的生命周期只有这一行下一行会自动调用析构函数A();A(1);// 匿名对象在这样的场景下会很方便Solution().Sum_Solution(10);return0;}7.对象拷贝时的编译器优化现代编译器为了提高程序运行效率在不影响正确性的情况下会尽可能减少一些传参和传值返回的过程中可以省略的拷贝。