C++ 类和对象(三):构造函数补充、匿名对象、友元、内部类、类的static与const
- 構造函數補充
- 匿名對象
- 友元
- 內部類
- 類的static成員
- 類的const成員
構造函數補充
列表初始化
講列表初始化之前,要先討論一下構造函數里面的語句到底是不是初始化
例子還是上次的日期類
class Date { public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day; };這個構造函數內的賦值語句是初始化嗎?咋一看很可能會覺得是,但是如果這樣寫呢?
class Date { public:Date(int year, int month, int day){_year = year;_month = month;_day = day;_year = 2020;}private:int _year;int _month;int _day; };我往后面加上了一個_year = 2020,那這樣還是初始化嗎?總不可能是先用year初始化_year,再用2020來初始化它,這明顯不成立,因為初始化只能一次,而函數體內的賦值可以多次,所以我們可以將函數體內的賦值理解為賦初值,而非初始化。
如果我們的成員是const,或者是引用,這種必須要初始化的類型,我們如果使用函數體內賦值,則是不可行的,而需要用到真正的初始化——列表初始化。
這里用了一個引用和一個const,如果采用函數體賦值的形式則會出錯,只有使用列表初始化才行
class Date { public:Date(int& year, const int month, int day):_year(year),_day(day),_month(month){}private:int &_year;const int _month;int _day; };同時,這里還有一個出錯的地方,可以看到,我這里初始化列表的順序是year,day,month。但是初始化的順序和這個順序毫無關聯,初始化的順序是按照參數在類中聲明的順序的,也就是下面的year,month,day。
當類中包含以下成員的時候,必須要通過初始化列表初始化
初始化列表的效率比在構造函數內初始化的效率高
因為如果在函數體內構造,首先會用默認構造函數來構造對象,再通過重載后的賦值運算符進行賦值,而如果用列表初始化就會直接調用拷貝構造函數,減少了一次默認構造函數的時間。
explicit關鍵字
class Date { public:Date(int year = 0, int month = 4, int day = 24):_year(year),_month(month),_day(day){}int _year;int _month;int _day; };int main() {Date d1(2020, 4, 24);Date d2 = 2020;//C++98Date d3 = { 2020, 4 }; //C++11Date d4 = { 2020, 4, 24 }; //C+11 }對于這里的d2,我們用2020給它賦值,而d3和d4分別用了列表來給它賦值,并且這四個對象它們最后的值是一模一樣的,那是為什么呢?這里的2020明明是一個整型,d3和d4是一個列表,為什么能夠給對象賦值呢?
這里就牽扯到了隱式的類型轉換
這里其實是先用這個整型值來調用了全缺省的構造函數來創建了一個臨時對象,再使用這個對象來為d2,d3,d4賦值。
這是一種很容易引起誤會的寫法,所以c++提供了關鍵字explicit,用這個關鍵字修飾的函數就會禁止隱式類型的轉化
這時這種隱式類型轉換就不會發生了
C++11 成員變量聲明缺省值
class Date { public:Date(int year){_year = year;} private: int _year = 2020;int _month = 4;int _day = 24; };對于初學C++的人,在這里很容易會將會將這里的成員變量的操作當成初始化操作,但其實這里的是在聲明階段給非靜態成員變量一個缺省值
還有一道我在csdn上看到的一個很有趣的題,給大家分享一下
以下代碼共調用多少次拷貝構造函數: ( )
Widget f(Widget u) { Widget v(u);Widget w=v;return w; }int main(){Widget x;Widget y=f(f(x));}這道題我第一眼看,在調用f的時候,會用實參來構造對象u,在用對象u構建v,然后用v構建w,最后因為是傳值返回,會用w再構造一個臨時量,所以調用一次f會使用4次拷貝構造函數,最后再用這兩次f的返回值來構造y,應該是9次。
但是答案卻是7次,讓我十分不解,所以我去論壇搜索了一下,發現這里涉及到了編譯器的優化。
我們可以看到,在第一次調用f的結束的時候,會返回一個由w構造的臨時量,在將這個臨時量作為實參來初始化形參,編譯器覺得這一步有點多余,會將其優化為一步,所以每次調用f的時候其實只經過了3次的拷貝構造,最后再加上y的拷貝構造,一共是7次。
匿名對象
當我們想要調用對象中的一個方法,或者只是想在這一句使用這個對象,其他地方不再使用對象的時候,如果我們直接構造一個對象使用,這無疑是一種很大的浪費,因為這個對象我們用了一次就扔了,不再需要了,而一個對象的生命周期是整個棧幀。
這時就需要用到匿名對象,匿名對象是一種臨時的對象,它的生命周期只有使用它的那一行語句,執行完則立即銷毀
class Date { public:Date(int year = 2020, int month = 4, int day = 24){_year = year;_month = month;_day = day;}void Print(int year){cout << "this year is " << year << endl;} private:int _year;int _month;int _day; };int main() {Date d1;d1.Print(2020);//創建一個對象,生命周期為整個函數棧幀Date().Print(2020);//創建一個匿名對象,生命周期只有這一行語句,實行完則立即調用析構函數 }所以當我們只想在這一行使用對象時,就可以考慮使用匿名對象。
友元
友元函數
其實上一章我重載的運算符還存在一個問題,就是訪問權限的問題
Date類的所有成員變量都是private的,這里是無法訪問的,只有將函數聲明為友元函數,才能使用
友元函數可以訪問類中的所有成員,包括private,和protect,所以只需要將這個函數聲明為友元函數即可。在這里插入代碼片
友元函數需要在類內進行聲明,聲明時用friend關鍵字來修飾。
友元函數可訪問類的私有和保護成員,但不是類的成員函數
友元函數不能用const修飾
友元函數可以在類定義的任何地方聲明,不受類訪問限定符限制
一個函數可以是多個類的友元函數
友元函數的調用與普通函數的調用和原理相同
友元類
如果我們想在一個類中,訪問另一個類的私有成員,可以做到嗎?
答案是可以的,friend也可以用來修飾類,也就是友元類。
只需要在要被訪問的類中,使用friend class來聲明對應的類為友元類,那個類就可以訪問了。
注意
1.友元關系是單向的,不具有交換性。
Date為A的友元,可以訪問A的私有成員,但是A并不能訪問Date的
2.友元關系不能傳遞
如果B是A的友元,C是B的友元,則不能說明C時A的友元。
內部類
有沒有想過,既然類的成員變量可以是自定義類型,那能不能在類中再構建一個類呢?
class Date { public:void Print(){cout << _year << endl;}private:class A{public:void Print(){cout << _data << endl;}private:int _data;};int _year;int _month;int _day;};可以看到,這是可以的,但是這兩個類有什么關系嗎?
這個內部類其實是一個獨立的類,它不屬于外部類,同時外部類對它也沒有任何特權,但是它同時還是外部類的友元類,可以通過外部類的對象參數來訪問外部類的所有成員。
特性:
1. 內部類可以定義在外部類的public、protected、private都是可以的。
2. 注意內部類可以直接訪問外部類中的static、枚舉成員,不需要外部類的對象/類名。
3. sizeof(外部類)=外部類,和內部類沒有任何關系
類的static成員
對于用static修飾的成員函數,稱為靜態成員函數,成員變量稱為靜態成員變量。如果對于一個成員,對其以static修飾,此時這個成員就不再屬于對象,而是屬于這一整個類的所有對象。因為靜態的成員的生命域不在類中,在靜態區,所以靜態的成員只能在類外初始化。
6.靜態成員和全局變量雖然都存儲在靜態區,但是靜態成員的生命周期只在本文件中,而全局變量不是
因為靜態成員函數不屬于某個對象,所以它沒有this指針,無法訪問任何非靜態的成員,但是非靜態的成員函數具有this指針,可以訪問靜態的成員。
類的const成員
因為對于類和對象,封裝性是一個很重要的東西,但是訪問限定符只對外部有影響,對自身的成員函數沒有影響,如果我們不想讓一個成員函數對類的成員進行修改,這時,就需要用const來修飾成員函數。
class Date { public://等價于 void print(const Date* this)void Print() const{cout << _year << '-' << _month << '-' << _day << endl;} private:int _year;int _month;int _day;};對于用const修飾的成員函數,需要將const放在最后面,來區分開const參數和const返回值。這里的const其實修飾的是該成員函數的this指針,所以該成員函數就無法對類的成員進行修改。
const對象可以調用非const成員函數嗎?
答案:不行,因為const對象的只能讀不能寫,而非const的成員函數則可讀可寫,使權限放大了,不行。
非const對象可以調用const成員函數嗎?
答案:可以,因為非const對象的可讀可寫,const成員函數只可讀,使權限縮小,可行。
const成員函數內可以調用其它的非const成員函數嗎?
答案:不行,因為const成員函數的this指針是常量,只可讀,而非const的成員函數則可讀可寫,使權限放大了,不行。
非const成員函數內可以調用其它的const成員函數嗎?
答案:可以,因為非const成員函數的this指針可讀可寫,const成員函數只可讀,使權限縮小,可行。
總結
以上是生活随笔為你收集整理的C++ 类和对象(三):构造函数补充、匿名对象、友元、内部类、类的static与const的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言实现miniShell
- 下一篇: C++ 动态内存管理:c/c++的动态内