1. 程式人生 > >c++入門之類繼承初步

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 }

 

從程式碼中,可以看到:繼承類可以呼叫基類的公有函式。