第6讲:类和对象(下))
C初阶长期更新第6讲类和对象下跟着潼心走轻松拿捏C困惑通通走一去不回头~欢迎开始今天的学习内容你的支持就是博主最大的动力。博主主页潼心1412o-CSDN博客前言今天我们结束类和对象的内容gogogo出发喽~6.1 构造函数的初始化有两种方式前面我们主要使用的方式是在函数体内直接赋值第二种方式就是初始化列表#includeiostream using namespace std; class Time { public: Time(int hour1) : _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) { } void Print() const { cout _year / _month / _day endl; } private: int _year; int _month; int _day; Time _t; // 没有默认构造 int _ref; // 引⽤ const int _n; // const }; int main() { int i 0; Date d1(i); d1.Print(); return 0; }每个成员变量在初始化列表最多出现一次引用成员变量 const成员变量 没有默认构造的类类型变量必须在初始化列表位置进行初始化C11支持在变量声明的的位置给缺省值所有成员变量都会走初始化列表且初始化顺序以声明顺序进行class MyQueue { public: MyQueue(int n4) :_pushst(n) ,_popst(n) ,_size(0) { } private: Stack _pushst; Stack _popst; int _size; }; //单链表 struct SListNode { SListNode* _next; int _val; }; class SList { public: private: SListNode* _head nullptr; }; //双向循环链表 struct DListNode { DListNode* _prev; DListNode* _next; int _val; }; class DList { public: DList(int n4) :_head((DListNode*)malloc(sizeof(DListNode)*4)) { if (_head nullptr) { perror(malloc fail!); return; } _head-_next _head-_prev _head; } private: DListNode* _head; };总而言之显示在初始化列表初始化的成员变量按初始化列表未显示在初始化列表的成员变量若类中声明位置有缺省值则按缺省值初始化声明无缺省值内置类型成员编译器随机0或随机值自定义类型调用成员的默认构造函数6.2 类型转换C支持内置类型和类类型的隐式转换需要有相关内置类型为参数的构造函数构造函数前加explicit就不再支持隐式类型转换类类型之间也可转换需提供对应构造函数class A { public: A(int aa) :_a1(aa) { cout A(int aa) endl; } A(int a1, int a2) :_a1(a1) , _a2(a2) { } A(const A aa) :_a1(aa._a1) , _a2(aa._a2) { cout A(const A aa) endl; } void Print() { cout _a1 _a2 endl; } private: //缺省值 int _a1 0; int _a2 0; }; int main() { A aa1 1; aa1.Print(); //临时对象具有常性 必须加const //const A r1 1; //const A r2 {1,2}; return 0; }实际上这里发生了两次构造第一次拷贝是1传参构造一个A类型的临时对象第二次则是临时对象进行拷贝构造给aa1但是编译器会进行优化直接为一次构造比如运行以上代码在VSCode经过优化只会打印A(int aa)而非还有一句A(const A aa)6.3 static成员static修饰的成员变量叫做静态成员变量静态成员变量属于整个类所有对象而不属于某个对象在类内进行声明类外进行初始化//计算一个程序出现了多少个类对象 class A { public: A(int x 0) { _acount; } A(const A a) { _acount; } static int Getacount() { return _acount; } private: //声明 static int _acount; }; //初始化 int A::_acount 0; int main() { cout A::Getacount() endl; return 0; }下面看道题支持变长数组代码如下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[n]; return Sum::GetRet(); } };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]; return Sum::GetRet(); } }; int main() { Solution s; cout s.Sum_Solution(10) endl; return 0; }6.4 友元友元提供了一种突破得访问限定符封装的方式分为友元函数和友元类在类中进行友元函数友元类的声明并在前面加上friend外部友元函数可访问类的私有和保护成员友元函数仅仅是⼀种声明他不是类的成员函数。友元函数可以在类定义的任何地⽅声明不受类访问限定符限制。⼀个函数可以是多个类的友元函数。友元类中的成员函数都可以是另⼀个类的友元函数都可以访问另⼀个类中的私有和保护成员。友元类的关系是单向的不具有交换性⽐如A类是B类的友元但是B类不是A类的友元。•友元类关系不能传递如果A是B的友元 B是C的友元但是A不是C的友元。有时提供了便利。但是友元会增加耦合度破坏了封装所以友元不宜多⽤。//前置声明 class B; class A { public: friend void func(const A aa, const B bb); private: int _a1 1; int _a2 2; }; class B { public: friend void func(const A aa, const B bb); private: int _b1 3; int _b2 4; }; void func(const A aa, const B bb) { }6.5 内部类一个类定义在类的内部就叫内部类内部类是一个独立的类跟定义在全局的类相比只是多了类域和访问限定符的限制外部类定义的对象中不包含内部类内部类默认是外部类的友元内部类本质也是⼀种封装当A类跟B类紧密关联A类实现出来主要就是给B类使⽤那么可以考虑把A类设计为B的内部类如果放到private/protected位置那么A类就是B类的专属内部类其他地⽅都⽤不了。class A { public: class B { public: friend void func(const A aa, const B bb); private: int _b1 3; int _b2 4; }; friend void func(const A aa, const A::B bb); private: int _a1 1; int _a2 2; }; void func(const A aa, const A::B bb) { cout aa._a1 endl; cout bb._b1 endl; } void Test03() { A a; A::B b; func(a,b); }再看上面那道题class Solution { public: class Sum { public: Sum() { _ret _i; _i; } static int GetRet() { return _ret; } }; int Sum_Solution(int n) { Sum* arr new Sum[n]; return Sum::GetRet(); } private: static int _i; static int _ret; }; int Solution::_i 1; int Solution::_ret 0; void Test04() { Solution s; cout s.Sum_Solution(10) endl; }6.6 匿名对象顾名思义匿名对象就是没有名字的对象与有名对象生命周期存在于整个类域相对匿名对象的生命周期只在一行void Test05() { //有名对象 A a1; A a2(2); //匿名对象 A(); A(4); }void Test04() { Solution s; cout s.Sum_Solution(10) endl; //两种写法等效 cout Solution().Sum_Solution(10) endl; }6.7 拷贝构造编译器优化问题现代编译器会为了尽可能提⾼程序的效率在不影响正确性的情况下会尽可能减少⼀些传参和传返回值的过程中可以省略的拷⻉。如何优化C标准并没有严格规定各个编译器会根据情况⾃⾏处理。当前主流的相对新⼀点的编译器对于连续⼀个表达式步骤中的连续拷⻉会进⾏合并优化有些更新更激进的编译器还会进⾏跨⾏跨表达式的合并优化。linux下可以将下⾯代码拷⻉到test.cpp⽂件编译时⽤g test.cpp -fno-elideconstructors的⽅式关闭构造相关的优化。#includeiostream using namespace std; class A { public: A(int a 0) :_a1(a) { cout A(int a) endl; } A(const A aa) :_a1(aa._a1) { cout A(const A aa) endl; } A operator(const A aa) { cout A operator(const A aa) endl; if (this ! aa) { _a1 aa._a1; } return *this; } ~A() { cout ~A() endl; } private: int _a1 1; }; void f1(A aa){} A f2() { A aa; return aa; } int main() { // 传值传参 // 构造拷⻉构造 A aa1; f1(aa1); cout endl; // 隐式类型连续构造拷⻉构造-优化为直接构造 f1(1); // ⼀个表达式中连续构造拷⻉构造-优化为⼀个构造 f1(A(2)); cout endl; cout *********************************************** endl; // 传值返回 // 不优化的情况下传值返回编译器会⽣成⼀个拷⻉返回对象的临时对象作为函数调⽤表达式的返回值 // ⽆优化 vs2019 debug // ⼀些编译器会优化得更厉害将构造的局部对象和拷⻉构造的临时对象优化为直接构造vs2022 debug f2(); cout endl; // 返回时⼀个表达式中连续拷⻉构造拷⻉构造-优化⼀个拷⻉构造 vs2019 debug // ⼀些编译器会优化得更厉害进⾏跨⾏合并优化将构造的局部对象aa和拷⻉的临时对象和接收返回值对象aa2优化为⼀个直接构造。vs2022 debug A aa2 f2(); cout endl; // ⼀个表达式中开始构造中间拷⻉构造赋值重载-⽆法优化vs2019 debug // ⼀些编译器会优化得更厉害进⾏跨⾏合并优化将构造的局部对象aa和拷⻉临时对象合并为⼀个直接构造vs2022 debug aa1 f2(); cout endl; return 0; }好了今天的学习内容就到这里啦谢谢你的陪伴我是潼心下次再见~如果这篇文章对你有帮助的话。请务必给主播一个一键三连球球了这对主播很重要~博主主页潼心1412o-CSDN博客