C++學習筆記之繼承
一、基類和派生類
很多時候,一個類的對象也“是”另一個類的對象,如矩形是四邊形,在C++中,矩形類Rectangle可以由四邊形類Quad繼承而來,於是,四邊形類Quad是基類,矩形類Rectangle是派生類。但是如果說四邊形一定是矩形顯然不對。幾個簡單的基類和派生類的例子:
基類 派生類
食物 米飯、面條、水餃
交通工具 汽車、火車、飛機
國家 中國、美國、西班牙
可以看出,每個派生類的對象都是基類的一個對象,並且一個基類可以有很多派生類。繼承關系構成一種樹狀層次結構。基類和派生類存在這種層次關系,如下圖:
下面用程序實例來說明:
建立一個乒乓球會員的類TableTennisPlayer類:
復制代碼
1 #ifndef TABTEN_H_
2 #define TABTEN_H_
3 #include <string>
4 using std::string;
5 //一個簡單的基類
6 class TableTennisPlayer
7 {
8 private:
9 string firstname;
10 string lastname;
11 bool hasTable;
12 public:
13 TableTennisPlayer (const string & fn = "none",
14 const string & ln = "none", bool ht = false);
15 void Name() const;
16 bool HasTable() const {return hasTable;}
17 void ResetTable(bool v) {hasTable = v;}
18 };
19
20 #endif
復制代碼
復制代碼
1 #include <iostream>
2 #include "tabten.h"
3
4 TableTennisPlayer::TableTennisPlayer (const string & fn,
5 const string & ln, bool ht) : firstname(fn), lastname(ln),hasTable(ht) {}
6
7 void TableTennisPlayer::Name() const
8 {
9 std::cout << lastname << ", " << firstname;
10 }
復制代碼
TableTennisPlayer類只記錄會員的姓名以及是否有桌球。假設一些會員參加過錦標賽,則需要這樣一個類,它能包括會員在比賽中的比分。我們要重新新建一個類嗎?顯然不用,這時候只需從TableTennisPlayer類派生出一個類,假設為RatedPlayer
class RatedPlayer : public TableTennisPlayer
{
...
};
冒號表示RatedPlayer類的基類是TableTennisPlayer類,public 表明TableTennisPlayer是一個公有基類,這被稱為公有派生。
使用公有派生,基類的公有成員將成為派生類的公有成員;基類的私有部分也將成為派生類的一部分,但是只能通過基類的公有(public)和保護(protected)方法訪問。
RatedPlayer對象將具有以下特征:
派生類對象存儲了基類的數據成員(派生類繼承了基類的實現)
派生類可以使用基類的方法(派生類繼承了基類的接口)
派生類還需要做:
添加自己的構造函數
根據需要添加額外的數據成員和成員函數
添加派生類的頭文件如下:
復制代碼
1 #include <iostream>
2 #include "tabten.h"
3
4 TableTennisPlayer::TableTennisPlayer (const string & fn,
5 const string & ln, bool ht) : firstname(fn), lastname(ln),hasTable(ht) {}
6
7 void TableTennisPlayer::Name() const
8 {
9 std::cout << lastname << ", " << firstname;
10 }
11
12 class RatedPlayer : public TableTennisPlayer
13 {
14 private:
15 unsigned int rating; //添加數據成員
16 public:
17 //派生類的構造函數必須給新成員(如果添加了的話)和基類的成員提供數據
18 RatedPlayer (unsigned int r = 0, const string & fn = "none",
19 const string & ln = "none", bool ht = false);
20 RatedPlayer (unsigned int r, const TableTennisPlayer & tp);
21 unsigned int Rating() const {return rating;}//添加方法
22 void ResetRating (unsigned int r) {rating = r;}//添加方法
23 };
復制代碼
復制代碼
1 #include <iostream>
2 #include "tabten.h"
3
4 TableTennisPlayer::TableTennisPlayer (const string & fn,
5 const string & ln, bool ht) : firstname(fn), lastname(ln),hasTable(ht) {}
6
7 void TableTennisPlayer::Name() const
8 {
9 std::cout << lastname << ", " << firstname;
10 }
11
12 RatedPlayer::RatedPlayer (unsigned int r, const string & fn,
13 const string & ln, bool ht) : TableTennisPlayer(fn, ln, ht)
14 {
15 rating = r;
16 }
17 RatedPlayer::RatedPlayer (unsigned int r, const TableTennisPlayer & tp)
18 : TableTennisPlayer(tp), rating(r){}
復制代碼
派生類的構造函數必須給新成員(如果添加了的話)和基類的成員提供數據。派生類不能直接訪問基類的私有成員,而必須通過基類方法訪問,例如RatedPlayer構造函數不能直接設置繼承的成員firstname, lastname和hasTable, 而必須使用基類的公有方法來訪問私有的基類成員。
有關派生類構造函數的要點如下:
首先創建基類對象
派生類構造函數通過成員初始化列表將基類的信息傳遞給基類構造函數
派生類構造函數應初始化派生類新增的數據成員。
二、使用派生類
復制代碼
1 #include <iostream>
2 #include "tabten.h"
3
4 int main ()
5 {
6 using std::cout;
7 using std::endl;
8 TableTennisPlayer player1("Tara", "Boomdea", false);
9 RatedPlayer rplayer1(1300, "Mallory", "Duck", true);
10 rplayer1.Name();//派生類調用基類的方法
11 cout << ( rplayer1.HasTable() ? (": has a table\n") : ("hasn't a table\n") );
12 player1.Name();
13 cout << ( rplayer1.HasTable() ? (": has a table\n") : ("hasn't a table\n") );
14 cout << "Name: ";
15 rplayer1.Name();
16 cout << "; Rating: " << rplayer1.Rating() << endl;
17
18 //用基類對象初始化派生類
19 RatedPlayer rplayer2(1212, player1);
20 cout << "Name: ";
21 rplayer2.Name();
22 cout << "; Rating: " << rplayer2.Rating() << endl;
23
24 return 0;
25 }
復制代碼
運行結果:
三、protected數據的繼承
當基類中的成員數據為protected時,派生類就可以直接訪問,而不用通過基類的公共方法去訪問這些protected數據,簡單來說,派生類可以直接繼承protected數據成員,可以免去調用成員函數的開銷,
使程序的性能稍稍有所提高。
在一個類的聲明中,一個良好的類聲明順序最好是先聲明public,然後是protected成員,最後是private成員。
使用protected數據注意的事項
派生類對象不必使用成員函數設置基類的protected數據成員值,派生類很容易將無效的值賦給基類的protected數據,導致對象處於不可靠的狀態中
使用protected數據成員,導致派生類成員函數實現可能太依賴基類的實現,實際上,派生類應該只依賴基類提供的服務(即非private成員函數),而不應該依賴基類的實現
多數情況下,使用private數據成員是更好的軟件工程的方法,雖然protected數據的繼承使程序的性能稍稍有所提高,但是代碼優化就交給編譯器去做好了,這樣的話代碼更易於維護、修改和調試,一句話,除非萬不得已,盡量不使用protected數據的繼承。
“程序員應該致力於編寫符合軟件工程原則的代碼,而將優化的問題留給編譯器去做”。一條好的准則是:“不要懷疑編譯器”。
四、補充
派生類不會繼承基類的構造函數、析構函數和重載的賦值運算符,但是派生類可以調用基類的構造函數、析構函數和重載的賦值運算符函數。
當由基類派生出一個類時,繼承基類的方式三種,即public繼承、protected繼承和private繼承。但實際情況是,一般很難采用private繼承和protected繼承,而且使用時需十分小心。
當從public基類派生一個類時,基類的public成員成為派生類中的public成員,基類的protected成員成為派生類中的protected成員。派生類永遠不能直接訪問基類的private成員,但是可以通過調用基類的public和protected成員函數進行訪問
當從protected基類派生一個類時,基類的public和protected成員都變成派生類中的protected成員
當從private基類派生一個類時,基類的public和protected成員都變成派生類中的private成員
private和protected繼承不是“is-a”關系