c++入門之類繼承初步
繼承是面向物件的一種很重要的特性,先來複習基類的基本知識:
先上一段程式碼:
1 # ifndef TABLE00_H 2 # define TABLE00_H 3 # include "string"; 4 using std::string; 5 class Player 6 { 7 private: 8 string first_name; 9 string last_name; 10 bool SEAT; 11 public: //注意,這裡只是標頭檔案,進行函式宣告的地方 12 //Player(const string & fn = "none", const string & ln = "none", bool symbol = false);13 Player(const string & fn , const string & ln , bool symbol); 14 //注意,在最開始設計類的時候,可能我們並沒有注意到 要使用 &引用 和const,使用& 和const的契機是: 15 //1 使用& 是因為,初始化型別為 string型別或者c型別字串,進行這種非基類資料型別複製的時候,都會耗費大量記憶體和時間,所以採用引用& 16 // 2 使用const 的契機: 因為這裡採用了引用,這種做法是為了不改變被引用引用的物件。思考將引用符號去掉,是否還有加const的必要性?17 void NameShow()const; 18 bool SeatVerify()const; 19 //思考:在函式名後面加const,是因為在寫函式體之前就想好了函式的功能是否改變成員變數,如果函式的功能不改變成員變數,就新增const, 20 //說白了這是一種從頂層到底層的設計,我們明白了函式的功能不改變成員變數,所以為了防止寫函式體的過程改變成員變數,我們加了一個const。 21 // 一般的 const加在誰的前面。就是用來修飾誰的,加在返回型別前面,就是修飾返回值,加在形參前面,即修飾形參,則加在函式名後面,是修飾函式體的,具體的也就是 22//不改變類物件的成員值。即這種函式稱之為常成員函式。 23 //思考:當函式程式碼比較短的時候,可否在標頭檔案直接使用行內函數將函式體鍵入? 24 void SetSeat(bool); 25 26 }; 27 //以下為共有繼承類宣告部分 28 class RePlayer :public Player 29 { 30 private: 31 unsigned int ratio; 32 public: 33 RePlayer(unsigned int, const string & fn, const string & ln, bool symbol); 34 RePlayer(unsigned int, const Player & np); 35 int Ratio() const; 36 void InitialRatio(unsigned int); 37 }; 38 39 # endif
先複習基本知識:
1 # ifndef TABLE00_H...# endif 表明:如果之前沒有定義TABLE00_H段,則編譯# ifndef TABLE00_H...# endif之間程式段,否則不編譯,這 能夠避免一個檔案被多個重疊檔案連續包含時報錯,比如B標頭檔案包含了A檔案,C標頭檔案包含了B檔案和A檔案,那麼如果沒加# ifndef TABLE00_H...# endif ,則會因為重複定義報錯,因此在寫標頭檔案時,一律寫上 # ifndef TABLE00_H...# endif可以避免程式的報錯問題。
2 對一個類而言,建構函式是十分重要的一個環節,建構函式存在的意義是:讓私有成員變數被初始化,我們應當始終注意這一初衷,只有這樣,我們才能設計正確的形參。
3 我們應該注意引用&變數的使用契機,當傳遞的引數是複雜資料型別(比如類和c型別的字串),由於巨大的記憶體開銷和排程,採用引用的方式無疑是一種高效的方式
4 上述程式碼段17,18,35行的函式成為:常成員函式,在此,先宣告函式宣告結尾const的作用,使得程式體不能改變私有成員變數的值(否則報錯),比如成員顯示函式,可以使用常成員函式
上述程式碼28-37行為繼承類的生命,從這個宣告我們可以得到這樣一些基本資訊與結論:
1 繼承類首先也是類,具有類的一般特性:包括私有成員、公有成員,以及建構函式。
2 觀察繼承類的建構函式。發現其建構函式同樣服從:讓私有成員變數被初始化.但繼承類繼承了基類,因此也要對基類的成員進行初始化,說白了,要對所有的成員進行初始化。
易錯:
也許有人看了13,33,34行的程式碼,會發出這樣的疑問:為何這裡使用了引用變數卻沒有初始化,引用變數在定義變數時不是要進行初始化嗎?
回答:我們在宣告類,甚至在定義類的時候,本質工作是什麼???本質工工作是:構造,構造一個數據型別,並不是在定義變數,只有我們在使用類(構造的資料型別)去定義物件的時候,我們才是真正的定義了一個變數,所以 定義類的過程,並不是定義變數的過程,所以並不必要對&進行初始化,說白了,此時的引用&只是一個空殼子,並不實際的分配記憶體,進行初始化這些功能。
進行了類宣告之後,但成員函式還未得到定義,為此,給出類定義:
1 # include "table00.h" 2 # include "iostream" 3 using std::string; 4 using std::cout; 5 using std::endl; 6 /*class Player //如果在函式體檔案再宣告class Player則會出現重定義的情況!!!,所以採用這種做法是錯誤的。 7 { 8 private: 9 string first_name; 10 string last_name; 11 bool SEAT; 12 public: 13 Player(const string & fn = "none", const string & ln = "none", bool symbol = false) 14 { 15 first_name = fn; 16 last_name = ln; 17 SEAT = symbol; 18 } 19 void NameShow()const //注意在函式體中,這個const也不能丟舎. 20 { 21 cout << first_name << "," << last_name << endl; 22 } 23 bool SeatVerify()const 24 { 25 return SEAT; 26 } 27 void SetSeat(bool change_seat) 28 { 29 SEAT = change_seat; 30 } 31 };*/ 32 //驗證上述寫法和下述寫法哪個更好。以及對於作用域有沒有更好的表示方法。 33 //Player::Player(const string & fn = "none", const string & ln = "none", bool symbol = false) 34 Player::Player(const string & fn , const string & ln, bool symbol ) 35 36 { 37 first_name = fn; 38 last_name = ln; 39 SEAT = symbol; 40 } 41 void Player:: NameShow()const //注意在函式體中,這個const也不能丟舎. 42 { 43 cout << first_name << "," << last_name << endl; 44 } 45 bool Player:: SeatVerify()const 46 { 47 return SEAT; 48 } 49 void Player:: SetSeat(bool change_seat) 50 { 51 SEAT = change_seat; 52 } 53 //要認識到面向物件這個詞的含義:函式的作用盡管也是為了完成一個功能,但更多的是完成對資料的操作,即我們更關注資料本身 54 // 成員函式的本質在於:服務於成員變數(通常情況是這樣),所以在進行成員函式設計的時候,我們所關注的重點是:對成員變數進行何種操作,完成何種功能 55 //一定要注意主體物件是成員變數。 56 57 RePlayer::RePlayer(unsigned int v, const string & fn, const string & ln, bool symbol) : Player(fn, ln, symbol) 58 { 59 ratio = v; 60 // first_name = fn; 注意,如果我們試圖直接訪問基類私有變數,是有問題的 61 // last_name = ln; 但我們需要在呼叫繼承類建構函式之前,呼叫基類建構函式。 62 // SEAT = symbol; 63 } 64 //這兩條都是繼承類建構函式,需要在呼叫之前呼叫基類建構函式,因此需要先初始化基類建構函式。 65 RePlayer::RePlayer(unsigned int v, const Player & np) : Player(np) 66 { //需要注意的是:如果 前面定義 unsigned int v =0;則後面的np也要賦初值 67 //注意,這裡的寫法發生了重定義。 68 ratio = v; 69 // first_name = fn; 注意,如果我們試圖直接訪問基類私有變數,是有問題的 70 // last_name = ln; 但我們需要在呼叫繼承類建構函式之前,呼叫基類建構函式。 71 // SEAT = symbol; 72 } 73 int RePlayer:: Ratio()const 74 { 75 return ratio; 76 } 77 78 void RePlayer::InitialRatio(unsigned int initial) 79 { 80 ratio = initial; 81 }
關於成員函式(也被稱為介面,其實很形象!!!)有以下內容需要說明:
1 無論是基類的成員函式,還是繼承類的成員函式,發現:成員函式都更側重於:對成員變數(也稱為實現,也很形象)進行了何種操作。雖然成員函式也描述了:完成了一個怎樣的功能,但我們更側重於:對成員變數完成了一種怎樣的功能,也就是最終落腳點在於:成員變數發生了什麼?因此,我們在寫成員函式的時候,一定不能漫無目的,思考要完成一個什麼功能但脫離了成員變數,一定要認識到我們的成員函式是緊緊的圍繞成員變數展開的。
2 關注繼承類的建構函式的實現:也就是上述,57和65行的程式碼。在初始化一個繼承類成員(實際上包含了基類成員在內的所有成員)的時候,必然先初始化基類的成員變數,要呼叫繼承類的建構函式,一定要首先呼叫其基類的建構函式,完成對基類成員變數先進行初始化。因此在進入繼承類建構函式函式體之前,必然先要呼叫基類建構函式完成基類成員變數的初始化。
這也是為什麼57行Player(fn, ln, symbol)與65行的 Player(np)會寫在函式體{}的前面
3 我們注意:60行和69行的程式碼,當我們試圖去直接訪問基類私有成員變數時,程式是禁止的,也就是說,我們只能通過基類的公有函式才能訪問基類的私有成員。這一點保證了父類和子類的獨立性關係。
最終,我們給出函式的呼叫:
1 # include "table00.h" 2 # include "iostream" 3 using namespace std; 4 int main() 5 { 6 Player player1("jack", "cracy", true); 7 player1.NameShow(); 8 Player player2(player1); 9 player2.NameShow(); 10 RePlayer player3(0, "robert", "lin", true); 11 player3.NameShow(); 12 RePlayer player4(12,player2); 13 player4.NameShow(); 14 system("pause"); 15 return 0; 16 17 }
從程式碼中,可以看到:繼承類可以呼叫基類的公有函式。