1、前言ios
在上一篇C++基礎博文中討論了C++最基本的代碼重用特性——類繼承,派生類能夠在繼承基類元素的同時,添加新的成員和方法。可是沒有考慮一種狀況:派生類繼承下來的方法的實現細節並不必定適合派生類的需求,此時派生類須要重載集成方法。dom
2、重載方法及虛函數ide
咱們討論《C++ Primer Plus》中的以下場景:銀行記錄客戶信息,包括客戶姓名、當前餘額。客戶這一類別固然可以建立客戶對象、存款、取款以及顯示信息。銀行須要特殊記錄具備透支權限的客戶,所以這一類別的客戶要額外記錄透支上限、透支貸款利率以及當前透支總額。此外,取款和顯示信息兩個操做必須考慮客戶的透支狀況。綜上,具備透支權限的客戶是客戶這一基類的派生類,派生類中不但須要添加新的成員,還要重載兩個繼承方法。函數
類聲明代碼:spa
1 #ifndef BRASS_H_ 2 #define BRASS_H_ 3 4 #include <string> 5 6 class Brass 7 { 8 private: 9 std::string fullName; 10 long acctNum; 11 double balance; 12 public: 13 Brass(const std::string& s = "Nullbody",long an = -1,double ba = 0.0);//default constructor 14 void Deposit(double amt); 15 double Balance() const; 16 virtual void Withdraw(double amt);//virtual function 17 virtual void ViewAcct() const; 18 virtual ~Brass() {}//使用虛析構函數確保先調用繼承類析構函數 19 }; 20 21 //brass plus account class 22 class BrassPlus:public Brass 23 { 24 private: 25 double maxLoan; 26 double rate; 27 double owesBank; 28 public: 29 BrassPlus(const std::string& s = "Nullbody",long an = -1, 30 double bal = 0.0,double ml = 500,double r = 0.11125); 31 BrassPlus(const Brass& ba,double ml = 500,double r = 0.11125); 32 virtual void ViewAcct() const; 33 virtual void Withdraw(double amt); 34 void ResetMax(double m) {maxLoan = m;}//inline function 35 void ResetRate(double r) {rate = r;} 36 void ResetOwes() {owesBank = 0;} 37 }; 38 39 #endif
類方法定義代碼:指針
1 #include"brass.h" 2 #include <iostream> 3 4 using std::cout; 5 using std::endl; 6 using std::string; 7 8 //brass methods 9 Brass::Brass(const string& s,long an,double bal) 10 { 11 fullName = s; 12 acctNum = an; 13 balance = bal; 14 } 15 16 void Brass::Deposit(double amt) 17 { 18 if(amt < 0) 19 cout << "Negative deposit not allowed;" 20 << "deposit is cancelled.\n"; 21 else 22 balance += amt; 23 } 24 25 void Brass::Withdraw(double amt) 26 { 27 if(amt < 0) 28 cout << "Withdrawal amount must be positive;" 29 << "withdrawal canceled.\n"; 30 else if (amt <= balance) 31 balance -= amt; 32 else 33 cout << "Withdrawal amount of $" << amt 34 << "exceeds your balance.\n" 35 << "Withdrawal canceled.\n"; 36 } 37 38 double Brass::Balance() const 39 { 40 return balance; 41 } 42 43 void Brass::ViewAcct() const 44 { 45 cout << "Client: " << fullName << endl; 46 cout << "Account Number: " << acctNum << endl; 47 cout << "Balance: $" << balance << endl; 48 } 49 50 //brassPlus methods 51 BrassPlus::BrassPlus(const string& s,long an,double bal, 52 double ml,double r):Brass(s,an,bal) 53 { 54 maxLoan = ml; 55 owesBank = 0.0; 56 rate = r; 57 } 58 59 BrassPlus::BrassPlus(const Brass& ba,double ml,double r):Brass(ba) 60 { 61 maxLoan = ml; 62 owesBank = 0.0; 63 rate = r; 64 } 65 66 //redefine viewacct() 67 void BrassPlus::ViewAcct() const 68 { 69 Brass::ViewAcct(); 70 cout << "Maximum loan: $" << maxLoan << endl; 71 cout << "Owed to bank: $" << owesBank << endl; 72 } 73 74 void BrassPlus::Withdraw(double amt) 75 { 76 double bal = Balance(); 77 if(amt <= bal) 78 Brass::Withdraw(amt); 79 else if(amt <= bal + maxLoan - owesBank)// 已欠 + 此欠 ≤ maxLoan 80 { 81 double advance = amt - bal; 82 owesBank += advance * (1.0+rate); 83 cout << "Bank advance: $" << advance << endl; 84 cout << "Finance charge: $" << advance*rate << endl; 85 Deposit(advance); 86 Brass::Withdraw(amt);// return to zero 87 } 88 else 89 cout << "Credit limit exceeded. Transcation cancelled.\n" ; 90 }
上述代碼多了一個新的語法特性:虛函數(virtual function)。當基類聲明中函數前加virtual,表示該函數爲虛函數。區別在於當調用者是引用或者指針時,調用的是基類方法,仍是派生類重載後的方法。具體區別咱們後邊在討論。重中之重在於虛析構函數的意義。若是程序中使用delete刪除佔用的動態內存,且用於索引內存地址的指針類型是基類,那麼即便該指針指向的是一個派生類對象,此時僅基類析構函數被調用。 咱們着重觀察brassPlus類重載的方法WithDraw有什麼變化。這類客戶因爲具備透支權限,在取款時確定要考慮欠款狀況。若欲取出金額≤存儲金額,則直接調用基類方法WithDraw,把存儲金額減少;若欲取出金額大於存儲金額,就必須進一步分析欠款狀況。已欠款+這次欠款≤透支額度時,取款操做纔有效。所以:owes+(amt - balance) ≤ maxLoan,進一步變形爲:amt ≤ balance+maxLoan-owes。
code
3、應用程序示例及結果分析對象
如今看看應用程序代碼和顯示結果。APP代碼:blog
1 #include <iostream> 2 #include "brass.h" 3 4 int main() 5 { 6 using std::cout; 7 using std::endl; 8 9 Brass Piggy("Porcelot Pigg",381299,4000.00); 10 BrassPlus Hoggy("Horatio Hogg",382288,3000.00); 11 12 Piggy.ViewAcct(); 13 cout << endl; 14 Hoggy.ViewAcct(); 15 cout << endl; 16 17 cout << "Depositing $1000 into the Hogg Account:\n"; 18 Hoggy.Deposit(1000.00); 19 cout << "New balance: $" <<Hoggy.Balance() <<endl; 20 cout << endl; 21 22 cout << "Withdrawing $4200 from the Pigg Account:\n"; 23 Piggy.Withdraw(4200.00); 24 cout << "Pigg account balance: $" << Piggy.Balance() << endl; 25 cout << endl; 26 27 cout << "Withdrawing $4200 from the Hogg Account:\n"; 28 Hoggy.Withdraw(4200.00); 29 Hoggy.ViewAcct(); 30 cout << endl; 31 32 Brass dom("Dominic Banker",11224,4183.45); 33 BrassPlus dot("Dorothy Banker",12118,2592.00); 34 35 Brass& b1_ref = dom; 36 Brass& b2_ref = dot;//use BrassPlus::ViewAcct() function 37 38 b1_ref.ViewAcct(); 39 cout << endl; 40 b2_ref.ViewAcct(); 41 cout << endl; 42 43 return 0; 44 }
打印結果:繼承
Pigg和Hogg分別是基類和派生類對象。當兩種均取款額度超出存儲金額時,Hogg因爲具備透支權限,才得以成功完成操做。注意以後建立的兩個對象dom和dot,從調用ViewAcct()函數過程當中再次體會虛函數的意義。若沒有使用virtual關鍵字,程序根據引用或指針的類型選擇使用基類方法仍是派生類同名的重載後方法。若使用該關鍵字,則根據引用或指針所指向對象的類型來選擇。程序中,b1_ref和b2_ref均是Brass類引用,但分別是Brass類對象dom和BrassPlus類對象dot的別名,所以使用virtual關鍵字後的ViewAcct()函數,依次調用基類和派生類方法。收工。