第七章:類
類的宣告
類的類名定義了唯一的類名。
類可以宣告與定義分離,僅宣告時稱為前向宣告,這種宣告之後定義之前產生的是不完全型別,這可以用來幫助定義指向這種型別的引用或指標。
直到類被定義後資料成員才能被宣告為這種型別,在建立類的物件之前必須完成類的定義,否則編譯器不知道該分配多少儲存空間。
類的作用域
一個類就是一個作用域,在類的外部尤其需要注意使用作用域運算子來訪問。在外部使用函式時,如果返回型別也是定義在類內部,我們需要同時對返回型別和函式名利用作用域運算子確定類。
名字查詢
通常規則為
1、首先在名字所在的塊中尋找宣告語句,只考慮在名字的使用之前出現的宣告
2、如果沒找到則繼續查詢外層作用域
3、報錯
-
用於類的成員函式
1、首先,編譯成員的宣告
2、類全部可見後才編譯函式體
編譯器處理完類中的全部宣告後才會處理成員函式的定義,好處是成員函式體中可以使用使用類中定義的任何名字。
-
用於類成員宣告
包括返回型別或者引數列表中的名字,都必須在使用前確保可見。其規則遵循通常規則
-
用於型別名
一般來說,內層作用域允許重新定義外層作用域的名字,即使該名字已經在內層作用域中使用過,然而在類中,如果成員使用了外層作用域中的某個名字,則類不能重新定義該名字。
隱式的類型別轉換
如果建構函式只接受一個實參,實際上定義了轉換為此型別的隱式轉換機制,又稱為轉換建構函式。
儘量不要使用,可以透過將建構函式宣告為explicit來阻止隱式型別轉換,但explicit建構函式只能用於直接初始化,
成員函式
特性:宣告必須在類的內部,定義則不必,允許過載。
在外部定義時需要使用作用域運算子來確定該函式位於哪一個類的作用域。
double Sales_data::avg_price() const {
...
}
常量成員函式
double avg_price() const://在引數列表後新增關鍵字 const
常量成員函式不能改變呼叫它的物件的內容。
常量物件及其引用或指標都只能呼叫常量成員函式。
this相關
成員函式透過一個 this 的額外引數來訪問呼叫它的那個物件,當我們呼叫成員函式時,用請求該函式的物件地址初始化 this。this 的目的總是指向該物件,實際上它是一個常量指標。
返回**this*的成員函式
class person {
public:
int age = 1;
person add1(int a) {
age += a;
return *this;
}
person& add2(int a) {
age += a;
return *this;
}
};
person.add1(10).add1(10);//age被修改為21
person.add2(10).add2(10);//age被修改為11
this將指向呼叫該成員函式所屬的物件,透過解引用符操作後即得到該物件。又由於返回值型別是引用,這些函式返回值返回的是物件本身。而如果返回型別不是引用,則返回的是臨時副本。
一個常量成員函式如果以引用的形式返回**this*,那麼它的返回型別顯示是常量引用。
總之,這種情況下尤其需要注意函式的返回型別。
基於const的過載
關鍵在於連續呼叫函式時返回的是常量物件還是非常量物件
資料成員
可變資料成員
透過關鍵字mutable宣告變數,這樣的變數能夠被常量成員函式進行修改。
初始值
類內初始值必須以符號=,或者花括號表示。
靜態成員
這一類成員直接與類相關聯,而不是與類的各個物件相關聯,所有物件共享。使用關鍵字static, 可以是常量、引用、指標、類型別等。類的靜態成員存在於任何物件之外,物件中不包含任何與靜態資料成員有關的資料。
宣告與定義
不能被宣告成const,不能在函式體內使用this指標(無論是顯式還是隱式)。
當在類的外部定義靜態成員時,不能重複static關鍵字,該關鍵字只出現在類內部的宣告語句。
一般來說,類的靜態成員不能在類內部初始化,但是可以為靜態成員提供const整數型別的類內初始值,不過要求靜態成員必須是字面值常量型別的constexpr。
使用
-
使用作用域運算子直接訪問靜態成員
-
使用類的物件、引用或指標來訪問
-
成員函式可以不透過作用域運算子
靜態成員可以是不完全型別,可以做實參。
建構函式
類透過建構函式來初始化類物件的資料成員,當類的物件被建立,建構函式被自動執行。
建構函式名與類名相同,無返回型別,可以不顯式定義,允許過載,不允許被宣告為const。
預設的建構函式
如果我們未顯式的定義一個建構函式,編譯器會定義一個預設建構函式,它會按照如下規則初始化類的資料成員
- 首先使用類內初始值來初始化成員;
- 如果不存在類內初始值,預設初始化該成員
但需要注意,某些類不能依賴於預設的建構函式
- 如果我們定義了一些建構函式,除非定義一個預設建構函式
建構函式名() = default;
否則類將沒有預設建構函式。這會導致如果我們使用沒有全部初始化類成員的建構函式時,會出現未定義的行為。
- 預設建構函式可能執行錯誤的操作。比如定義在塊中的內建型別或複合型別(陣列、指標等等)的物件被預設初始化時,它們的值將會是未定義的
- 有時編譯器不能為某些類合成預設建構函式。比如,如果類中包含一個其他類型別的成員且這個成員的的型別沒有預設建構函式,編譯器將無法初始化該成員。
建構函式初始值列表
class person {
public:
int age = 0;
int tall = 0;
int c = 0;
public:
person(int a, int b): age(a), tall(b) { }
};
使用建構函式的前兩個引數來初始化成員age和tall。如果將變數c僅宣告並嘗試訪問
int c ;
person p1 = person(1,2);
cout<<p1.c;//c++11成功,c++14開始會報錯
cout<<p1.age<<p1.tall;//不報錯
需要注意的是,初始化的順序與成員宣告的順序保持一致,而與初始值列表中的順序無關
總之確保所有變數在構造結束時被顯式初始化,以防止出現未定義的行為。
最好令建構函式初始值的順序和成員宣告的順序保持一致,儘量避免使用某些成員初始化其他成員
委託建構函式
為了避免有多個參數列不同但是邏輯相近的建構函式造成程式碼重複,引入委託建構函式。只需要在委託建構函式的初始化列表中呼叫目標建構函式即可,會先執行目標建構函式(先執行初始值列表再執行函式體),再執行委託建構函式。
注意不要構造委託環。
類的複製、賦值與析構
第13章進一步學習
訪問控制與封裝
public:在整個程式內可以被訪問
private:可以被類的成員函式訪問,但是不能被直接使用類的程式碼訪問
class的預設訪問許可權為private,struct為public
友元
友元可以允許其他類或者函式訪問它的非公有成員,友元宣告只能出現在類定義的內部。
友元關係不具有傳遞性。
定義友元
friend 函式宣告;//非成員函式作友元
friend 類宣告;//類作友元
friend 返回型別 類名::成員函式名;//類的成員函式作友元
friend void class1::func();
在成員函式作友元時,假定我們想要的情況為令類c1中的成員函式func作為c2的友元函式
class c2{
friend void c1::func();
}
需要滿足:
1、首先定義c1類,其中宣告瞭func函式,但是不能定義它。這是因為在func使用c2的成員前必須宣告c2。
2、接下來定義c2,其中包括對func的友元宣告。
3、最後定義func
聚合類
聚合類允許使用者直接訪問其成員,並且具有特殊的初始化語法。
- 所有成員都是public
- 沒有定義任何建構函式
- 沒有類內初始值
- 沒有基類,沒有virtual函
可以使用花括號來進行成員初始值列表,但是初始值的順序必須與宣告的順序保持一致,如果列表中的元素數量不夠,則靠後的成員被值初始化。
缺點:
- 需要類的成員變數的訪問屬性都是public。
- 增加了類的使用者的負擔,類的使用者要明確清晰如何初始化一個聚合類。
- 任何對成員變數的改動,都需要改動初始化語句
字面值常量類
面值常量類是一種資料成員都是字面值型別的聚合類。如果一個類符合以下要求,則它也是個字面值常量類:
- 資料成員都必須是字面值型別。
- 類必須至少含有一個constexpr建構函式。
- 如果一個資料成員含有類內初始值,則內建型別成員的初始值必須是一條常量表示式;或者如果成員屬於某種型別,則初始值必須使用成員自己的constexpr建構函式