一. 再探构造函数1.1 初始化列表的理解1. 之前我们实现构造函数时初始化成员变量主要使用函数体内赋值构造函数初始化还有一种方式就是初始化列表初始化列表的使用方式是以一个冒号开始接着是一个以逗号分隔的数据成员列表每个“成员变量”后面跟一个放在括号中的初始值或表达式。2. 每个成员变量在初始化列表中只能出现一次语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。3.引用成员变量const成员变量没有默认构造的类类型变量必须放在初始化列表位置进行初始化注意这里显式和隐式都可以显式是直接在初始化列表隐式是在声明时给缺省值否则会编译报错。注意初始化列表是它们定义的地方#includeiostream using namespace std; class Time { public: Time(int hour) :_hour(hour) { cout Time() endl; } private: int _hour; }; class Date { public: Date(int x,int year 1, int month 1, int day 1) :_year(year) ,_month(month) ,_day(day) ,_t(12) ,_ref(x) ,_n(1) { // } private: int _year; int _month; int _day; Time _t; //没有默认构造 int _ref; //引用 const int _n;//const }; int main() { int y 0; //对象整体定义 Date d1(y); y; return 0; }4. C11支持在成员变量声明的位置给缺省值这个缺省值主要是给没有显式在初始化列表初始化的成员使用的。如果既没有显式初始化也没有缺省值那它就是随机值了class Date { public: pricate: //声明缺省值;注意这里不是定义 //注意以下这个写法是C11才开始支持的 int _year1; int _month1; int _day1; }; int main() { Date d1; return 0; }5.尽量使用初始化列表初始化因为那些你不在初始化列表初始化的成员也会走初始化列表如果这个成员在声明位置给了缺省值初始化列表会用这个缺省值初始化。如果你没有给缺省值对于没有显式在初始化列表初始化的内置类型成员是否初始化取决于编译器C并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数如果没有默认构造会编译错误。6. 初始化列表中按照成员变量在类中声明顺序进行初始化跟成员在初始化列表出现的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。总结一般情况下建议尽量使用初始化列表显式初始化如果没在初始化列表初始化的值尽量给缺省值。1.那什么情况下会混着用呢#includeiostream using namespace std; class Stack { public: Stack(int n 4) { cout Stack(int n) endl; } }; class Myqueue { public: private: //混用 int _size 0; Stack _st1; Stack _st2; }; int main() { Myqueue q1; return 0; }2.并不是光有初始化列表就能解决所有问题函数体有时也脱离不开。class Myqueue { public: private: //混用 int _size 0; Stack _st1; Stack _st2; }; class A { public: A(int n10) :_a((int* )malloc(sizeof(int)*n))//你malloc会不会失败你要不要检查 ,_size(0) { //此时只有函数体才能做这种检查 if (_a nullptr) { cout malloc fail endl; exit(-1); } memset(_a, 0, sizeof(int) * n);//初始化 } private: int* _a; int _size; }; int main() { //Myqueue q1; A aa; return 0; }1.2 练习题下面程序的运行结果是 A.输出1 1 B.输出2 2 C.编译报错 D.输出1 随机值 E.输出1 2 F.输出2 1#includeiostream using namespace std; class A{ public: A(int a) :_a1(a) ,_a2(_a1) {} void Print(){ cout_a1 _a2endl; } private: int _a22; int _a12; }; int main() { A aa(1); aa.Print(); }答案D。首先已经在初始化列表进行了显式的初始化那么跟缺省值就没有关系了所以选项里所有涉及到2都可以排除。根据主函数中的“A aa(1)”可知1传给a由于在声明时_a2先声明_a1后声明那在初始化列表中先执行“_a2(_a1)”但是_a1是随机值所以_a2也是随机值随后再执行_a1(a)此时a值为1故_a1的值也为1。二. 类型转换1. C支持内置类型隐式类型转换为类类型对象需要有相关内置类型为参数的构造函数。之前我们学的都是内置类型和内置类型之间的转换。有一定关联才能转换例如整型家族之间整型和浮点都是表示数据大小整型和指针之间指针是地址的编号那言外之意就是没有关联的类型之间不可以转换例如浮点数和指针之。#includeiostream using namespace std; class A { public: A(int a) :_a1(a) , _a2(_a1) { } private: int _a2 2; int _a1 2; }; void func(const A aa) { // } class Stack { public: void Push(const A a) { } }; int main() { //1.语法上调用的是构造 A a1(1); //2.也可以这样写语法上是隐式类型转换,因为构造函数用int做参数去构造一个A对象 //1先构造一个临时对象(构造)这个临时对象再去拷贝构造给a2(拷贝构造) A a2 1; const A ref2 a1; //这里ref3引用的是中间产生的临时变量 const A ref3 1; func(a1); func(1); Stack st1; A a3(3); st1.Push(a3); //有了隐式类型转换可以下面这样写 st1.Push(3); return 0; }2. 构造函数前面加explicit就不再支持隐式类型转换。#includeiostream using namespace std; class A { public: //构造 //构造函数explicit就不再支持隐式类型转换 //explicit A(int a1) A(int a1) :_a1(a1) { cout A(int a1) \n; } //拷贝构造 A(const A aa) { cout A(const A aa) \n; } A(int a1,int a2) :_a1(a1) ,_a2(a2) { } private: int _a2 2; int _a1 2; }; int main() { //构造 A a1(1); //2为参数构造临时对象临时对象再去拷贝构造a2-优化用2直接去拷贝构造a2(优化为直接构造) A a22; //注意这里是临时对象是没办法被优化的因为ref1不可能引用3,而是引用一个A类型的对象 const A ref1 3; //不仅可以上面的单参数也可以下面的多参数 A a3(1, 1); A a4 { 1,1 }; const A ref2 { 1,1 }; return 0; }3. 类类型的对象之间也可以隐式转换需要相应的构造函数支持。#includeiostream using namespace std; class A { public: //构造 A(int a1) :_a1(a1) { cout A(int a1) \n; } //拷贝构造 A(const A aa) { cout A(const A aa) \n; } A(int a1,int a2) :_a1(a1) ,_a2(a2) { } int Get()const { return _a1 _a2; } private: int _a2 2; int _a1 2; }; class B { public: B(const A a) :_b(a.Get()) { } private: int _b 0; }; int main() { B b1a3; const B ref3 a3; return 0; }三. static成员3.1 基础概念的理解1. 用static修饰的成员变量称之为静态成员变量静态成员变量一定要在类外进行初始化。2. 静态成员变量为所有类对象所共享不属于某个具体的对象不存在对象中存放在静态区。3. 用static修饰的成员函数称之为静态成员函数静态成员函数没有this指针。4. 静态成员函数中可以访问其他的静态成员但是不能访问非静态的因为没有this指针。5. 非静态的成员函数可以访问任意的静态成员变量和静态成员函数。#includeiostream using namespace std; class A { public: A(int a0) :_a1(a) ,_a2(a) { _count; } A(const A t) { _count; } static int GetCount() { //_a1;没有this指针不能访问非静态的 return _count; } private: int _a1 1; int _a2 1; public: //它存在于静态区(注意不在对象里存着)是类专属的并且受到类域和访问限定符的限制的全局变量 //声明 static int _count; }; //定义 int A::_count 0; int mian() { A aa1; cout sizeof(aa1) endl; A* ptr nullptr; A aa2 1; //如果是公有的情况下指定类域可以访问 cout ptr-_count endl; cout aa1._count endl; cout A::_count endl; cout A::GetCount() endl; cout aa2.GetCount() endl; cout ptr-GetCount() endl; return 0; }6. 突破类域和访问限定就可以访问静态成员可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。7. 静态成员也是类的成员受public、protected、private访问限定符的限制。8. 静态成员变量不能在声明位置给缺省值初始化因为缺省值是个构造函数初始化列表的静态成员变量不属于某个对象不走构造函数初始化列表。3.2 练习题--编程题3.2.1 题目描述求123...n要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句A?B:C。数据范围0n200进阶空间复杂度O(1)时间复杂度O(n)。3.2.2 示例示例1输入5返回值153.2.3 代码求解根据题目要求循环、递归、等差公式都被限制使用所以我们这里只能使用刚才的那种方法--静态成员。#includeiostream using namespace std; class Sum { public: Sum() { _ret _i; _i; } static int GetRet() { return _ret; } private: static int _i; static int _ret; }; int Sum::_i 1; int Sum::_ret 0; class Solution { public: int Sum_Solution(int n) { Sum* arr new Sum[n]; delete[] arr; //上面也可写成Sum arr[n]; 但是VS不支持变长数组 return Sum::GetRet(); } }; int main() { Solution s; cout s.Sum_Solution(10) endl; return 0; }3.3 小知识1.设已经有ABCD4个类的定义程序中ABCD构造函数的调用顺序为 2.设已经有ABCD4个类的定义程序中ABCD析构函数的调用顺序为 A. D B A C C. C D B A E. C A B DB. B A D C D. A B D C F. C D A BC c; int main() { A a; B b; static D d; return 0; }答案E B 。第一问全局变量在main函数之前就要初始化那此时就得调构造所以C必然是第一个先调用构造的。记住局部的静态变量是在第一次运行它的时候才会初始化无论是变量还是对象都一样。所以在main函数内部会按照顺序调用构造函数依次为A B D故第一问选E。第二问C和D的话是等main函数结束之后才析构但是在main函数结束之后局部的静态的会先析构所以D比C先析构。另外根据先定义的后析构可知B在A前所以顺序依次为B A D C故选B。四. 友元1. 友元提供了一种突破类访问限定符封装的方式友元分为友元函数和友元类在函数声明或者类声明前面加friend并且把友元声明放到一个类的里面。2. 外部友元函数可访问类的私有和保护成员友元函数仅仅是一种声明它不是类的成员函数。3. 友元函数可以在类定义的任何地方声明不受类访问限定符限制。4. 一个函数可以是多个类的友元函数。#includeiostream using namespace std; //前置声明否则A的友元函数声明编译器不认识B class B; class A { //友元声明 friend void func(const A aa, const B bb); private: int _a1 1; int _a2 2; }; class B { //友元声明 friend void func(const A aa, const B bb); private: int _b1 3; int _b2 4; }; void func(const A aa,const B bb) { cout aa._a1 endl; cout bb._b1 endl; } int main() { A aa; B bb; func(aa, bb); return 0; }5. 友元类中的成员函数都可以是另一个类的友元函数都可以访问另一个类中的私有和保护成员。6. 友元类的关系是单向的不具有交换性比如A类是B类的友元但是B类不是A类的友元。例如下面的例子D可以访问C但是C不能访问D7. 友元类关系不能传递如果A是B的友元B是C的友元但是A不是C的友元。#includeiostream using namespace std; class A { //友元声明 friend class B; private: int _a1 1; int _a2 2; }; class B { public: void func1(const A aa) { cout aa._a1 endl; cout _b1 endl; } void func2(const A aa) { cout aa._a2 endl; cout _b2 endl; } private: int _b1 3; int _b2 4; }; int main() { A aa; B bb; bb.func1(aa); bb.func1(aa); return 0; }8. 有时提供了便利。但是友元会增加耦合度破坏了封装所以友元不宜多用。五. 内部类1. 如果一个类定义在另一个类内部这个类就叫做内部类。内部类是一个独立的类跟定义在全局相比它只是受外部类类域限制和访问限定符限制所以外部类定义的对象中不包含内部类。2. 内部类默认是外部类的友元类。3. 内部类本质也是一种封装当A类和B类紧密关联A类实现出来主要就是给B类使用那么可以考虑把A类设计为B的内部类如果放到private/protected位置那么A类就是B类的专属内部类其他地方都用不了。#includeiostream using namespace std; class A { private: static int _k; int _h; public: class B//B默认是A的友元 { public: void foo(const A a) { cout _k endl; cout a._h endl; } }; }; int A::_k 1; int main() { cout sizeof(A) endl; A::B bb; return 0; }六. 匿名对象1. 用类型实参定义出来的对象叫做匿名对象相比之前我们定义的类型 对象名实参定义出来的叫有名对象。2. 匿名对象生命周期只在当前一行一般临时定义一个对象当前用一下即可就可以定义匿名对象。有名对象的生命周期在当前域但是匿名对象的生命周期只在当前行class Solution { public: // }; //const引用会延长匿名对象的生命周期 void Func(const Solution sSolution(),int i1) { } int main() { Solution s;//有名对象 cout s.Sum_Solution(10) endl; //生命周期只在当前这一行 Solution();//匿名对象 coutSolution().Sum_Solution(10)endl; return 0; }七. 对象拷贝时的编译器优化1. 现代编译器会为了尽可能提高程序的效率在不影响正确性的情况下会尽可能减少一些传参和传返回值的过程中可以省略的拷贝。2. 如何优化C标准并没有严格规定各个编译器会根据情况自行处理。当前主流的相对新一点的编译器对于连续一个表达式步骤中的连续拷贝会进行合并优化有些更新更“激进”的编译器还会进行跨行跨表达式的合并优化。C11C17开始部分场景规定了编译器通常在标准规定就尽量优化其次标准没有规定主流编译器也在优化。以上就是我们今天的内容下一节我将讲述“内存管理”和“模板”这两块的内容大家敬请期待~喜欢的大佬们可以一键三连~互三必回