一个简单的基类
基類
基類:從一個類派生出另一個類時,原始類稱為基類。
派生類:繼承類稱為派生類。
設計一個乒乓球類
Webtown俱樂部決定跟蹤乒乓球會會員。
TableTennisPlayer類只是記錄會員的姓名以及是否有球桌。string類來存儲姓名,相比使用字符數組,更方便、更靈活、更安全。
tabtenn0.h
//tabtenn0.h
#ifndef TABTENN0_H
#define TABTENN0_H
#include <string>
using namespace std;class TableTennisPlayer
{
private:string firstname; //姓名string lastname;bool hasTable; //是否有球桌
public:TableTennisPlayer(const string &fn = "none", const string &ln = "none", bool ht = false);void Name() const;bool HasTable() const { return hasTable;};void ResetTable(bool v) { hasTable = v;};
};
#endif
tabenn0.cpp
//tabenn0.cpp
#include "tablenn0.h"
#include <iostream>
TableTennisPlayer::TableTennisPlayer(const string &fn, const string &ln, bool ht) : firstname(fn), lastname(ln), hasTable(ht){}void TableTennisPlayer::Name() const
{cout << lastname << ", " << firstname;
}
首先為firstname調用string的默認構造函數,再調用string的賦值運算符將firstname設置為fn,但初始化列表可以減少這個步驟,它直接使用string的復制構造函數將firstname初始化為fn。
TableTennisPlayer::TableTennisPlayer(const string &fn, const string &ln, bool ht)
{firstname = fn;lastname = ln;hasTable = ht;
}
main.cpp
//usett0.cpp
int main()
{TableTennisPlayer player1("Chuck", "Blizzard", true);TableTennisPlayer player2("Tara", "Boomdea", false);player1.Name();if(player1.HasTable())cout << ": has a table.\n";elsecout << ": hasn't a table.\n";player2.Name();if(player2.HasTable())cout << ": has a table";elsecout << ": hasn't a table.\n";return 0;
}
Blizzard, Chuck: has a table.
Boomdea, Tara: hasn't a table.
注意到該程序實例化對象時將C風格字符串作為參數:
TableTennisPlayer player1("Chuck", "Blizzard", true);
TableTennisPlayer player2("Tara", "Boomdea", false);
但構造函數的形參類型被聲明為const string&。這導致類型不匹配。string類有一個將const char*作為參數的構造函數,使用C風格字符串初始化string對象時,將自動調用這個構造函數。總之,可將string對象或C風格字符串作為構造函數TableTennisPlayer的參數;將前者作為參數時,將調用接受const string&作為參數的string構造函數,而將后者作為參數時,將調用接受const char*作為參數的string構造函數。
派生類
派生類權限:
- 派生類對象包含基類對象。
- 基類的公有成員將成為派生類的公有成員。
- 基類的私有部分也將成為派生類的一部分,但只能通過基類的公有和保護方法訪問。
派生類對象:
- 派生類對象存儲了基類的數據成員(派生類繼承了基類的實現)
- 派生類對象可以使用基類的方法(派生類繼承了基類的接口)
在繼承特性的添加:
- 派生類需要自己的構造函數
- 派生類可以根據需要添加額外的數據成員和成員函數
派生一個類
Webtown俱樂部的一些成員曾經參加過當地的乒乓球錦標賽,需要這樣一個類,能包括成員在比賽中的比分。從TableTennisClass派生一個類。首先將RatedPlayer類聲明為從TableTennisClass類派生而來:
//將RatedPlayer類聲明為從TableTennisClass類派生而來
class RatedPlayer:public TableTennisPlayer
{
...
};
冒號指出RatePlayer類的基類是TableTennisplayer類。上述特殊的聲明頭表明TableTennisPlayer是一個公有基類,被稱為公有派生。
Ratedplayer對象將具有以下特征:
1)派生類對象存儲了基類的數據成員(派生類繼承了基類的實現)。
2)派生類對象可以使用基類的方法(派生類繼承了基類的接口)。
因此,RatedPlayer對象可以存儲運動員的姓名及其是否有球桌。另外,RatedPlayer對象還可以使用TableTennisPlayer類的Name()、hasTable()、ResetTable()方法。
需要在繼承特性中添加什么
1)派生類需要自己的構造函數。
2)派生類可以根據需要添加額外的數據成員和成員函數。
派生類需要另一個數據成員來存儲比分,還應包含檢索比分的方法和重置比分的方法。
//一個簡單派生類
class RatedPlayer:public TableTennisPlayer
{
private:unsigned int rating; //添加數據成員
public:RatedPlayer(unsigned int r = 0, const string &fn = "none", const string &ln = "none", bool ht = false);RatedPlayer(unsigned int r, const TableTennisPlayer &tp);unsigned int Rating() const { return rating; } //添加成員方法void ResetRating (unsigned int r) { rating = r; } //添加成員方法
};
構造函數必須給新成員和繼承的成員提供數據。在第一個RatedPlayer構造函數中,每個成員對應一個形參;而第二個Ratedplayer構造函數使用一個TableTennisPlayer參數,該參數包括firstname、lastname、hasTable。
公有派生
使用公有派生,基類的公有成員將成為派生類的公有成員;基類的私有部分也將成為派生類的一部分,但只能通過基類的公有和保護方法訪問。
構造函數:訪問權限的考慮
派生類不能直接訪問基類的私有成員,而必須通過基類方法進行訪問。RatedPlayer構造函數不能直接設置繼承的成員(firstname、lastname、hasTable),而必須使用基類的公有方法來訪問私有的基類成員。派生類構造函數必須使用基類構造函數。
創建派生類對象時,程序首先創建基類對象?;悓ο髴斣诔绦蜻M入派生類構造函數之前被創建。C++使用成員初始化列表語法來完成。
第一個RatedPlayer構造函數的代碼:
RatedPlayer::RatedPlayer(unsigned int r, const string &fn, const string &ln, bool ht):TableTennisPlayer(fn, ln, ht)
{rating = r;
}
:TableTennisPlayer(fn, ln, ht)是成員初始列表。它是可執行的代碼,調用TableTennisPlayer構造函數。
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
則RealPlayer構造函數將把實參"Mallory"、“Duck"和true賦給形參fn、ln和ht,然后將這些參數作為實參傳遞給TableTennisPlayer構造函數,后者將創建一個嵌套TableTennisPlayer對象,并將數據"Mallory”、"Duck"和true存儲在該對象中。然后,程序進入RealPlayer構造函數體,完成RealPlayer對象的創建,并將參數r的值賦給rating成員。
如果省略成員初始列表,情況將如何:
RatedPlayer::RatedPlayer(unsigned int r, const string &fn, const string &ln, bool ht) //如果沒有成員初始化列表
{rating = r;
}
必須首先創建基類對象,如果不調用基類構造函數,程序將使用默認的基類構造函數,因此上述代碼等效于:
RatedPlayer::RatedPlayer(unsigned int r, const string &fn, const string &ln, bool ht) //:TableTennisPlayer()
{rating = r;
}
除非要使用默認構造函數,否則應顯示調用正確的基類構造函數。
下面來看第二個構造函數的代碼:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer &tp):TableTennisPlayer(tp)
{rating = r;
}
這里也將TableTennisPlayer的信息傳遞給了TableTennisPlayer構造函數:
TableTennisPlayer(tp)
由于tp的類型為TableTennisPlayer&,因此將調用基類的復制構造函數?;悰]有定義復制構造函數,如果需要使用復制構造函數但又沒有定義,編譯器將自動生成一個。在這種情況下,執行成員復制的隱式復制構造函數是合適的,因為這個類沒有使用動態內存分配(string成員確實使用了動態內存分配,成員復制將使用string類的復制構造函數來復制string成員)。
如果愿意,也可以對派生類成員使用成員初始化列表語法。這種情況下,應在列表中使用成員名,而不是類名。所以,第二個構造函數可以如下:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer &tp):TableTennisPlayer(tp), rating(r)
{
}
派生類構造函數
派生類構造函數:
1)首先創建基類對象。
2)派生類構造函數應通過成員初始化列表將基類傳遞給基類構造函數。
3)派生類構造函數應初始化派生類新增的數據成員。
創建派生類對象時,程序調用構造函數順序:
1)首先調用基類構造函數。
2)再調用派生類構造函數。
基類構造函數負責初始化繼承的數據成員;派生類構造函數主要用于初始化新增的數據成員。
派生類的構造函數總是調用一個基類構造函數。可以使用初始化器列表語法指明要使用的基類構造函數,否則將使用默認的基類構造函數。
派生類過期時,程序調用析構函數順序:
1)首先調用派生類析構函數。
2)再調用基類析構函數。
成員初始化列表
派生類構造函數可以使用初始化器列表機制將值傳遞給基類構造函數。
derived::derived(type1 x, type2 y):base(x, y) //成員初始化列表
{...
}
其中derived是派生類,base是基類,x和y是基類構造函數使用變量。
如果派生類構造函數接收到參數10和12,則這種機制將10和12傳遞給被定義為接受這些類型的參數的基類構造函數。
除虛基類外,類只能將值傳遞回相鄰的基類,但后者可以使用相同的機制將信息傳遞給相鄰的基類,依次類推。
如果沒有在成員初始化列表中提供基類構造函數,程序將使用默認的基類構造函數。成員初始化列表只能用于構造函數。
使用派生類
要使用派生類,程序必須要能夠訪問基類聲明。將類的聲明置于同一個頭文件中。也可以將每個類放在獨立的頭文件中,但由于這兩個類是相關的,所以把其類聲明放在一起更合適。
tabtenn1.h
//tabtenn1.h
#ifndef TABTENN0_H
#define TABTENN0_H
#include <string>
using namespace std;//基類
class TableTennisPlayer
{
private:string firstname;string lastname;bool hasTable;
public:TableTennisPlayer(const string &fn = "none", const string &ln = "none", bool ht = false);void Name() const;bool HasTable() const { return hasTable;};void ResetTable(bool v) { hasTable = v;};
};//派生類
class RatedPlayer:public TableTennisPlayer
{
private:unsigned int rating;
public:RatedPlayer(unsigned int r = 0, const string &fn = "none", const string &ln = "none", bool ht = false);RatedPlayer(unsigned int r, const TableTennisPlayer &tp);unsigned int Rating() const { return rating; }void ResetRating (unsigned int r) { rating = r; }
};
#endif
tabtenn1.cpp
//tabtenn1.cpp
#include "tablenn1.h"
#include <iostream>
TableTennisPlayer::TableTennisPlayer(const string &fn, const string &ln, bool ht):firstname(fn), lastname(ln), hasTable(ht){}void TableTennisPlayer::Name() const
{cout << lastname << ", " << firstname;
}RatedPlayer::RatedPlayer(unsigned int r, const string &fn, const string &ln, bool ht):TableTennisPlayer(fn, ln, ht)
{rating = r;
}RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer &tp):TableTennisPlayer(tp), rating(r)
{
}
main.cpp
//usett1.cpp
#include <iostream>
#include "tablenn1.h"int main()
{TableTennisPlayer player1("Tara", "Boomdea", false);RatedPlayer rplayer1(1140, "Mallory", "Duck", true); rplayer1.Name(); //使用基類方法if(rplayer1.HasTable())cout << ": has a table.\n";elsecout << ": hasn't a table.\n";player1.Name();if(player1.HasTable())cout << ": has a table";elsecout << ": hasn't a table.\n";cout << "Name: ";rplayer1.Name();cout << "; Rating: " << rplayer1.Rating() << endl;RatedPlayer rplayer2(1212, player1);cout << "Name: ";rplayer2.Name();cout << "; Rating: " << rplayer2.Rating() << endl;return 0;
}
Duck, Mallory: has a table.
Boomdea, Tara: hasn't a table.
Name: Duck, Mallory; Rating: 1140
Name: Boomdea, Tara; Rating: 1212
派生類和基類之間的特殊關系
1)派生類對象可以使用基類的方法,但不能是私有的。
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
rplayer1.Name(); //派生類對象使用基類方法
2) 基類指針可以在不進行顯式類型轉換的情況下指向派生類對象;基類引用可以在不進行顯示類型轉換的情況下引用派生類對象。
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
TableTennisPlayer & rt = rplayer;
TableTennisPlayer * pt = &rplayer;
rt.Name(); //引用
pt->Name(); //指針
然而,基類指針或引用只能用于調用基類方法,不能使用rt或pt來調用派生類的ResetRanking方法。
3) C++要求引用和指針類型與賦值的類型匹配,但這一規則對繼承來說是例外。這種例外是單向的,不可以將基類對象和地址賦給派生類引用和指針:
TableTennisPlayer player("Betsy", "Bloop", true);
RatedPlayer & rr = player; //錯誤的
RatedPlayer * pr = player; //錯誤的
如果允許基類引用隱式地引用派生類對象,則可以使用基類引用為派生類對象調用基類的方法。因為派生類繼承了基類的方法,所以這樣做不會出問題。
如果將基類對象賦給派生類引用,派生類引用能夠為基對象調用派生類方法,這樣就會出問題。
將RatedPlayer::Rating()方法用于TableTennisPlayer對象是沒有意義的,因為TableTennisPlayer對象沒有rating成員。
4) 如果基類引用和指針可以指向派生類對象,將出現一些有趣的結果。其中之一是基類引用定義的函數或指針參數可用于基類對象或派生類對象。
void Show(const TableTennisPlayer &rt)
{std::cout << "Name: ";rt.Name();std::cout << "\nTable: ";if (rt.HasTable())std::cout << "yes\n";elsestd::cout << "no\n";
}
形參rt是一個基類引用,它可以指向基類對象或派生類對象,所以可以在Show()中使用TableTennis參數或Ratedplayer參數:
TableTennisPlayer player1("Tara", "Boomdea", false);
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
Show(player1);
Show(rplayer1);
5) 對于形參為指向基類的指針的函數,也存在相似的關系。它可以使用基類對象的地址或派生類對象地址作為實參:
void Wohs(const TableTennisPlayer * pt);
...
TableTennisPlayer player1("Tara", "Boomdea", false);
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
Wohs(&player1);
Wohs(&rplayer1);
引用兼容性屬性也能夠將基類對象初始化為派生類對象,盡管不那么直接。
RatedPlayer olaf1(1840, "Olaf", "Loaf", true);
TableTennisPlayer olaf2(olaf1);
要初始化olaf2,匹配的構造函數的原型:
TableTennisPlayer(const RatedPlayer &);
類定義中沒有這樣的構造函數,但存在隱式復制構造函數:
TableTennisPlayer(const TableTennisPlayer &);
形參是基類引用,因此它可以引用派生類。將olaf2初始化為olaf1時,將要使用該構造函數,它復制firstname、lastname和hasTable成員。它將olaf2初始化為嵌套在RatedPlayer對象olaf1中的TableTennisPlayer對象。
同樣,也可以將派生類對象賦給基類對象:
RatedPlayer olaf1(1840, "Olaf", "Loaf", true);
TableTennisPlayer winner;
winner = olaf1;
在這種情況下,程序將使用隱式重載賦值運算符:
TableTennisPlayer & operator=(const TableTennisPlayer &)const;
基類引用指向的也是派生類對象,因此olaf1的基類部分被復制給winner。
總結
- 上一篇: P2617 Dynamic Rankin
- 下一篇: 使用itextpdf编辑PDF