一、前言 在上一篇C++基礎博文中討論了C++最基本的代碼重用特性——類繼承,派生類可以在繼承基類元素的同時,添加新的成員和方法。但是沒有考慮一種情況:派生類繼承下來的方法的實現細節並不一定適合派生類的需求,此時派生類需要重載集成方法。 二、重載方法及虛函數 我們討論《C++ Primer Plus ...
一、前言
在上一篇C++基礎博文中討論了C++最基本的代碼重用特性——類繼承,派生類可以在繼承基類元素的同時,添加新的成員和方法。但是沒有考慮一種情況:派生類繼承下來的方法的實現細節並不一定適合派生類的需求,此時派生類需要重載集成方法。
二、重載方法及虛函數
我們討論《C++ Primer Plus》中的如下場景:銀行記錄客戶信息,包括客戶姓名、當前餘額。客戶這一類別當然能夠創建客戶對象、存款、取款以及顯示信息。銀行需要特殊記錄具有透支許可權的客戶,因此這一類別的客戶要額外記錄透支上限、透支貸款利率以及當前透支總額。此外,取款和顯示信息兩個操作必須考慮客戶的透支情況。綜上,具有透支許可權的客戶是客戶這一基類的派生類,派生類中不但需要添加新的成員,還要重載兩個繼承方法。
類聲明代碼:
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 #endifbrass.h
類方法定義代碼:
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 }brass.cpp
上述代碼多了一個新的語法特性:虛函數(virtual function)。當基類聲明中函數前加virtual,表示該函數為虛函數。區別在於當調用者是引用或者指針時,調用的是基類方法,還是派生類重載後的方法。具體區別我們後邊在討論。重中之重在於虛析構函數的意義。如果程式中使用delete刪除占用的動態記憶體,且用於索引記憶體地址的指針類型是基類,那麼即使該指針指向的是一個派生類對象,此時僅基類析構函數被調用。 我們著重觀察brassPlus類重載的方法WithDraw有什麼變化。這類客戶由於具有透支許可權,在取款時肯定要考慮欠款情況。若欲取出金額≤存儲金額,則直接調用基類方法WithDraw,把存儲金額減小;若欲取出金額大於存儲金額,就必須進一步分析欠款情況。已欠款+此次欠款≤透支額度時,取款操作才有效。因此:owes+(amt - balance) ≤ maxLoan,進一步變形為:amt ≤ balance+maxLoan-owes。
三、應用程式示例及結果分析
現在看看應用程式代碼和顯示結果。APP代碼:
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 }usebrass.cpp
列印結果:
Pigg和Hogg分別是基類和派生類對象。當兩種均取款額度超出存儲金額時,Hogg由於具有透支許可權,才得以成功完成操作。註意之後創建的兩個對象dom和dot,從調用ViewAcct()函數過程中再次體會虛函數的意義。若沒有使用virtual關鍵字,程式根據引用或指針的類型選擇使用基類方法還是派生類同名的重載後方法。若使用該關鍵字,則根據引用或指針所指向對象的類型來選擇。程式中,b1_ref和b2_ref均是Brass類引用,但分別是Brass類對象dom和BrassPlus類對象dot的別名,因此使用virtual關鍵字後的ViewAcct()函數,依次調用基類和派生類方法。收工。