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