類
類(class)是結構體的拓展,不僅能夠擁有成員元素,還擁有成員函數。
在面向對象編程(OOP)中,對象就是類的實例,也就是變量。
C++ 中 struct 關鍵字定義的也是類,上文中的 結構體 的定義來自 C。因為某些歷史原因,C++ 保留並拓展了 struct。
定義類
類使用關鍵字 class 或者 struct 定義,下文以 class 舉例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
與使用 struct 大同小異。該例定義了一個名為 Object 的類。該類擁有兩個成員元素,分別為 weight,value;並在 } 後使用該類型定義了一個數組 e。
定義類的指針形同 struct。
訪問説明符
不同於 struct 中的舉例,本例中出現了 public,這屬於訪問説明符。
public:該訪問説明符之後的各個成員都可以被公開訪問,簡單來説就是無論 類內 還是 類外 都可以訪問。protected:該訪問説明符之後的各個成員可以被 類內、派生類或者友元的成員訪問,但類外 不能訪問。private:該訪問説明符之後的各個成員 只能 被 類內 成員或者友元的成員訪問,不能 被從類外或者派生類中訪問。
對於 struct,它的所有成員都是默認 public。對於 class,它的所有成員都是默認 private。
關於 "友元" 和 "派生類",可以參考下方摺疊框,或者查詢網絡資料進行詳細瞭解。
對於算法競賽來説,友元和派生類並不是必須要掌握的知識點。
關於友元以及派生類的基本概念
友元(friend):使用 friend 關鍵字修飾某個函數或者類。可以使得在 被修飾者 在不成為成員函數或者成員類的情況下,訪問該類的私有(private)或者受保護(protected)成員。簡單來説就是隻要帶有這個類的 friend 標記,就可以訪問私有或受保護的成員元素。
派生類(derived class):C++ 允許使用一個類作為 基類,並通過基類 派生 出 派生類。其中派生類(根據特定規則)繼承基類中的成員變量和成員函數。可以提高代碼的複用率。
派生類似 "is" 的關係。如貓(派生類)"is" 哺乳動物(基類)。
對於上面 private 和 protected 的區別,可以看做派生類可以訪問基類的 protected 的元素(public 同),但不能訪問 private 元素。
訪問與修改成員元素的值
方法形同 struct
- 對於變量,使用
.符號。 - 對於指針,使用
->符號。
成員函數
成員函數,顧名思義。就是類中所包含的函數。
常見成員函數舉例
1 2 3 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
該類有一個打印 Object 成員元素的函數,以及更改成員元素 weight 的函數。
和函數類似,對於成員函數,也可以先聲明,在定義,如第十四行(聲明處)以及十七行後(定義處)。
如果想要調用 var 的 print 成員函數,可以使用 var.print() 進行調用。
重載運算符
何為重載
C++ 允許編寫者為名稱相同的函數或者運算符指定不同的定義。這稱為 重載(overload)。
如果同名函數的參數種類、數量中的一者或多者兩兩不相同,則這些同名函數被看做是不同的。
需要注意的是:如果兩個同名函數的區別僅僅是返回值的類型不同則無法進行重載,此時編譯器會拒絕編譯!
如果在調用時不會出現混淆(指調用某些同名函數時,無法根據所填參數種類和數量唯一地判斷出被調用函數。常發生在具有默認參數的函數中),則編譯器會根據調用時所填參數判斷應調用函數。
而上述過程被稱作重載解析。
重載運算符,可以部分程度上代替函數,簡化代碼。
下面給出重載運算符的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
該例定義了一個向量類,並重載了 * + - 運算符,並分別代表向量內積,向量加,向量減。
重載運算符的模板大致可分為下面幾部分。
1 2 3 | |
對於自定義的類,如果重載了某些運算符(一般來説只需要重載 < 這個比較運算符),便可以使用相應的 STL 容器或算法,如 sort。
如要了解更多,可參見「參考資料」第四條。
可以被重載的運算符
1 2 3 4 5 6 | |
在實例化變量時設定初始值
為完成這種操作,需要定義 默認構造函數(Default constructor)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
該例定義了 Object 的默認構造函數,該函數能夠在我們實例化 Object 類型變量時,將所有的成員元素初始化為 0。
若無顯式的構造函數,則編譯器認為該類有隱式的默認構造函數。換言之,若無定義任何構造函數,則編譯器會自動生成一個默認構造函數,並會根據成員元素的類型進行初始化(與定義 內置類型 變量相同)。
在這種情況下,成員元素都是未初始化的,訪問未初始化的變量的結果是未定義的(也就是説並不知道會返回和值)。
如果需要自定義初始化的值,可以再定義(或重載)構造函數。
關於定義(或重載)構造函數
一般來説,默認構造函數是不帶參數的,這區別於構造函數。構造函數和默認構造函數的定義大同小異,只是參數數量上的不同。
構造函數可以被重載(當然首次被叫做定義)。需要注意的是,如果已經定義了構造函數,那麼編譯器便不會再生成無參數的默認構造函數。這會可能會使試圖以默認方法構造變量的行為編譯失敗(指不填入初始化參數)。
使用 C++11 或以上時,可以使用 {} 進行變量的初始化。
關於 {}
使用 {} 進行初始化,會用到 std::initializer_list 這一個輕量代理對象進行初始化。
初始化步驟大概如下
- 嘗試尋找參數中有
std::initializer_list的默認構造函數,如果有則調用(調用完後不再進行下面的查找,下同)。 - 嘗試將
{}中的元素填入其他構造參數,如果能將參數按照順序填滿(默認參數也算在內),則調用該默認構造函數。 - 若無
private成員元素,則嘗試在 類外 按照元素定義順序或者下標順序依次賦值。
上述過程只是完整過程的簡化版本,詳細內容參見 "參考資料九"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
關於隱式類型轉換
有時候會寫出如下的代碼
1 2 3 4 5 6 7 8 | |
看上去十分不符合邏輯,一個 int 類型不可能轉化為 node 類型。但是編譯器不會進行 error 提示。
原因是在進行賦值時,首先會將 1 作為參數調用 node::node(int),然後調用默認的複製函數進行賦值。
但大多數情況下,編寫者會希望編譯器進行報錯。這時便可以在構造函數前追加 explicit 關鍵字。這會告訴編譯器必須顯式進行調用。
1 2 3 4 5 6 | |
也就是説 node a=1 將會報錯,但 node a=node(1) 不會。因為後者顯式調用了構造函數。當然大多數人不會寫出後者的代碼,但此例足以説明 explicit 的作用。
不過在算法競賽中,為了避免此類情況常用的是 "加強對代碼的規範程度",從源頭上避免
銷燬
這是不可避免的問題。每一個變量都將在作用範圍結束走向銷燬。
但對於已經指向了動態申請的內存的指針來説,該指針在銷燬時不會自動釋放所指向的內存,需要手動釋放動態內存。
如果結構體的成員元素包含指針,同樣會遇到這種問題。需要用到析構函數來手動釋放動態內存。
析構 函數(Destructor)將會在該變量被銷燬時被調用。重載的方法形同構造函數,但需要在前加 ~
默認定義的析構函數通常對於算法競賽已經足夠使用,通常我們只有在成員元素包含指針時才會重載析構函數。
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
為類變量賦值
默認情況下,賦值時會按照對應成員元素賦值的規則進行。也可以使用 類名稱() 或 類名稱{} 作為臨時變量來進行賦值。
前者只是調用了複製構造函數(copy constructor),而後者在調用複製構造函數前會調用默認構造函數。
另外默認情況下,進行的賦值都是對應元素間進行 淺拷貝,如果成員元素中有指針,則在賦值完成後,兩個變量的成員指針具有相同的地址。
1 2 3 4 | |
如需解決指針問題或更多操作,需要重載相應的構造函數。
更多 構造函數(constructor)內容,參見「參考資料」第六條。
參考資料
- cppreference class
- cppreference access
- cppreference default_constructor
- cppreference operator
- cplusplus Data structures
- cplusplus Special members
- C++11 FAQ
- cppreference Friendship and inheritance
- cppreference value initialization
本页面最近更新:,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:Ir1d, cjsoft, Lans1ot, JasonkayZK
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用