C++ 第十三章 類繼承

第十三章 類繼承

本章內容包括:

  • is-a關係的繼承。
  • 如何以公有方式從一個類派生出另一個類。
  • 保護訪問。
  • 構造函數成員初始化列表。
  • 向上和向下強制轉換。
  • 虛成員函數。
  • 早期(靜態)聯編和晚期(動態)聯編
  • 抽象基類。
  • 純虛函數
  • 何時及如何使用公有繼承。

一個簡單的基類:

從一個類派生出另一個類時,原始類稱爲基類,繼承類稱爲派生類。例如

tabtenn0.h

#ifndef D1_TEBTENN0_H
#define D1_TEBTENN0_H

#include <string>
using std::string;
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 //D1_TEBTENN0_H

tabtenn0.cpp

#include "tebtenn0.h"
#include <iostream>

TableTennisPlayer::TableTennisPlayer(const string &fn, const string &ln, bool ht):firstname(fn),lastname(ln),hasTable(ht){}
void TableTennisPlayer::Name() const {
    std::cout << lastname << "," << firstname;
}

TableTennisPlayer類只記錄了會員的名字以及是否有桌球。有兩點說明,首先,這個類使用標準類string類來存儲姓名,相比於使用字符數組,這樣更加靈活、安全、方便。其次,構造函數使用了成員初始化列表語法。減去了使用string類默認構造函數生成firstname 的過程

派生一個類:
class RatedPlayer :public TableTennisPlayer{
    ..........
};

冒號指出 RatedPlayer 類的基類是 TableTennisPlayer 類。 上述特殊的聲明頭表明 TableTennisPlayer 是一個公有基類,這被稱爲公有派生。派生類對象包含基類對象。使用公有派生,基類的公有成員將成爲派生類的公有成員;基類的私有部分也將成爲派生類的一部分,但只能通過基類的公有和保護方法訪問。

上面的代碼完成了哪些工作呢?RatedPlayer 對象有一下特徵:

  • 派生類對象存儲了基類的數據成員(派生類繼承了基類的實現);
  • 派生類對象可以使用基類的方法(派生類繼承類基類的接口)。

需要在繼承特性中添加什麼呢?

  • 派生類需要自己的構造函數。
  • 派生類可以根據需要添加額外的數據成員和成員函數

RatedPlayer.h

#ifndef D1_RATEDPLAYER_H
#define D1_RATEDPLAYER_H

#include "tebtenn0.h"

class RatedPlayer :public TableTennisPlayer{
private:
    unsigned int rating;
public:
    RatedPlayer(unsigned int rat=0, const string & fn = "none", const string & ln = "none", bool ht = false);
    RatedPlayer(unsigned int rat, const TableTennisPlayer & ttp);
    unsigned  int Rating() const { return rating;};
    void RestRating (unsigned int r) {rating = r;};
};

#endif //D1_RATEDPLAYER_H
構造函數:訪問權限的考慮:

派生類不能直接訪問基類成員的私有成員,而必須通過基類方法進行訪問。例如RatedPlayer 構造函數不能直接設置繼承的成員(firstname,lastname,hasTable),而必須通過使用基類的公有方法來訪問私有基類的成員:

RatedPlayer.cpp

#include "RatedPlayer.h"

RatedPlayer::RatedPlayer(unsigned int rat, const string &fn, const string &ln, bool ht)
                                 : rating(rat),TableTennisPlayer(fn,ln,ht){}

RatedPlayer::RatedPlayer(unsigned int rat, const TableTennisPlayer &ttp) :rating(rat),TableTennisPlayer(ttp){}

有關派生類構造函數的特點如下:

  • 首先創建基類對象;
  • 派生類構造函數應通過成員初始化列表將基類信息傳遞給基類構造函數;
  • 派生類構造函數應初始化派生類新增的數據成員。

main.cpp

#include "RatedPlayer.h"
#include <iostream>

int main(){
    using std::cout;
    using std::endl;
    TableTennisPlayer player1("Tara","Boomda", false);
    RatedPlayer ratedPlayer1(1140,"Mallory","Duck",true);
    ratedPlayer1.Name();
    if (ratedPlayer1.HasTable()){
        cout << ": has a table.\n";
    } else {
        cout << "; hasn't a table.\n";
    }
    player1.Name();
    if (player1.HasTable()){
        cout << ": has a table.\n";
    } else {
        cout << "; hasn't a table.\n";
    }
    cout << "Name: ";
    ratedPlayer1.Name();
    cout << "; Rating: " << ratedPlayer1.Rating() << endl;
    RatedPlayer ratedPlayer2(1212,player1);
    cout << "Name: ";
    ratedPlayer2.Name();
    cout << "; Rating: " << ratedPlayer2.Rating() << endl;
    return 0;
}
結果
Duck,Mallory: has a table.
Boomda,Tara; hasn't a table.
Name: Duck,Mallory; Rating: 1140
Name: Boomda,Tara; Rating: 1212
派生類和基類之間的特殊關係:
  • 派生類對象可以使用基類的方法,條件是方法不是私有的。

  • 基類指針可以在不進行顯式類型轉換的情況下指向派生類對象;基類引用可以在不進行顯式類型轉換的情況下引用派生類對象:

    RatedPlayer ratedPlayer1(1140,"Mallory","Duck",true);
    TableTennisPlayer * player1 = &ratedPlayer1;
    player1->Name();
    cout << endl;
    TableTennisPlayer &player2 = ratedPlayer1;
    player2.Name();
    

    然而,基類指針或引用只能調用基類的方法。因此,不能使用player1調用Rating()方法。

由於基類指針或引用可以指向派生類,將出現一些有趣結果

void show(const TableTennisPlayer &ttp) {
    using std::cout;
    cout << "Name: ";
    ttp.Name();
    cout << "\nTable: ";
    if (ttp.HasTable()){
        cout << "yes\n";
    } else {
        cout << "no\n";
    }
}

RatedPlayer ratedPlayer1(1140,"Mallory","Duck",true);
show(ratedPlayer1);	

繼承:is-a 關係:

派生類和基類之間的特殊關係是基於C++繼承的底層模型的。實際上C++有3中繼承方式:公有繼承、保護繼承和私有繼承。公有繼承是最常用的方式,它建立一種is-a關係,即派生類對象也是一個基類對象,可以對基類執行的任何操作,也可以對派生類執行難。因爲派生類可以添加特性,所以,將這種關係稱爲is-a-kind-of(是一種)關係可能更準確,但通常使用術語is-a。

多態公有繼承:

RatedPlayer 繼承示例很簡單。派生類對象使用基類的方法,而未做任何修改。然而,可能會遇到這樣的情況,即希望同一個方法在派生類和基類中的行爲是不同的。換句話說,方法的行爲應取決於2調用該方法的對象。這種複雜的行爲稱爲多態—具有多種形態,即同一個方法的行爲隨上下文而異。有兩種重要的機制可用於實現多態公有繼承:

  • 在派生類中重新定義基類的方法
  • 使用虛函數。

Brass.h

#ifndef D1_BRASS_H
#define D1_BRASS_H
#include <string>

class Brass {		// 銀行存款
private:
    std::string fullname;
    long acctNum;  // 賬號
    double balance; // 結餘
public:
    Brass(const std::string &fn = "Nullbody",long an = -1, double bal = 0.0);
    void Deposit(double amt);
    virtual void Withdraw(double amt);
    double Balance() const ;
    virtual void ViewAcct() const ;
    virtual ~Brass(){};
};

class BrassPlus:public Brass{ // 增加透支
private:
    double maxLoan;
    double rate;
    double owesBank;
public:
    BrassPlus(const std::string &fn = "Nullbody",long an = -1, double bal = 0.0, double ml = 500, double r = 0.11125);
    BrassPlus(const Brass & brass, double ml = 500, double r = 0.11125);
    virtual void Withdraw(double amt) ;
    virtual void ViewAcct() const ;
    void ResetMax(double m) { maxLoan = m ;};
    void ResetRate(double r) { rate = r ;};
    void ResetOwes() { owesBank = 0 ;};
};

#endif //D1_BRASS_H

Brass.cpp

#include "Brass.h"
#include <iostream>
using std::cout;
using std::endl;
using std::string;
typedef std::ios_base::fmtflags format;
format setFormat();
typedef std::streamsize precis;
void restore(format f, precis p);

Brass::Brass(const std::string &fn, long an, double bal):acctNum(an),balance(bal){
    fullname = fn;
}


void Brass::Deposit(double amt) { // 存款
    if (amt < 0)
        cout << "Negative deposit not allowed; deposit is canceled.\n";
    else
        balance += amt;
}

void Brass::Withdraw(double amt) {
    format initialState = setFormat();
    precis prec = cout.precision(2);
    if (amt < 0)
        cout << "Withdrawal amount must be positive; withdrawl canceled.\n";
    else if (amt <= balance)
        balance -= amt;
    else
        cout << "Withdrawal amount of $ exceeds your balance.\n"
             << "withdrawal canceled.\n";
    restore(initialState,prec);
}

double Brass::Balance() const {
    return balance;
}

void Brass::ViewAcct() const {
    format initialState = setFormat();
    precis prec = cout.precision(2);
    cout << "Client: " << fullname << endl;
    cout << "Account Number: " << acctNum << endl;
    cout << "Balance: $" << balance << endl;
    restore(initialState, prec);

}


BrassPlus::BrassPlus(const std::string &fn, long an, double bal, double ml, double r)
                                : Brass(fn,an,bal),maxLoan(ml),rate(r),owesBank(0.0){}

BrassPlus::BrassPlus(const Brass &brass, double ml, double r)
                                : Brass(brass),maxLoan(ml),rate(r),owesBank(0.0){}

void BrassPlus::Withdraw(double amt) {
    format initialState = setFormat();
    precis prec = cout.precision(2);

    double bal = Balance();
    if (amt < bal)
        Brass::Withdraw(amt);
    else if ( amt < bal + maxLoan - owesBank) {
        double  advance = amt - bal;
        owesBank  += advance * (1.0 + rate);
        cout << "Bank advance: $" << advance << endl;
        cout << "Finance charge: $" << advance * rate << endl;
        Deposit(advance);
        Brass::Withdraw(amt);
    } else
        cout << "Credit limit exceeded. Transaction cancelled.\n";
    restore(initialState,prec);
}

void BrassPlus::ViewAcct() const {
    format initialState = setFormat();
    precis prec = cout.precision(2);

    Brass::ViewAcct();
    cout << "Maximum loan: $" << maxLoan << endl;
    cout << "owed to bank: $" << owesBank << endl;
    cout.precision(3);
    cout << "Loan Rate: " << 100 * rate << "%\n";
    restore(initialState,prec);
}

format setFormat(){
    return cout.setf(std::ios_base::fixed,std::ios_base::floatfield);
}

void restore(format f, precis p){
    cout.setf(f,std::ios_base::floatfield);
    cout.precision(p);
}

main1.cpp

#include <iostream>
#include "Brass.h"


int main(){
    using std::cout;
    using std::endl;
    Brass Piggy("Porcelot Pigg",381299,4000.00);
    BrassPlus Hoggy("Horation Hogg",382288,3000.00);
    Piggy.ViewAcct();
    cout << endl;
    Hoggy.ViewAcct();
    cout << endl;
    cout << "Depositing $1000 into the Hoggy Account:\n";
    Hoggy.Deposit(1000.00);
    cout << "New balance: $" << Hoggy.Balance() << endl;
    cout << "Withdrawing $4200 from Pigg Account:\n";
    Piggy.Withdraw(4200.00);
    cout << "Pigg account balance: $" << Piggy.Balance() << endl;
    cout << "Withdrawing $4200 from Hogg Account:\n";
    Hoggy.Withdraw(4200.00);
    Hoggy.ViewAcct();
    return 0;
}
結果:
Client: Porcelot Pigg
Account Number: 381299
Balance: $4000.00

Client: Horation Hogg
Account Number: 382288
Balance: $3000.00
Maximum loan: $500.00
owed to bank: $0.00
Loan Rate: 11.125%

Depositing $1000 into the Hoggy Account:
New balance: $4000
Withdrawing $4200 from Pigg Account:
Withdrawal amount of $ exceeds your balance.
withdrawal canceled.
Pigg account balance: $4000
Withdrawing $4200 from Hogg Account:
Bank advance: $200.00
Finance charge: $22.25
Client: Horation Hogg
Account Number: 382288
Balance: $0.00
Maximum loan: $500.00
owed to bank: $222.25
Loan Rate: 11.125%

程序說明:

  • BrassPlush類在Brass類的基礎上添加了3個私有數據成員和3個公有成員函數。

  • Brass類和BrassPlush類都聲明瞭ViewAcct()和Withdraw()方法,但兩個兩個對象的方法行爲是不同的。

  • Brass類在聲明ViewAcct()和Withdraw()時使用了新關鍵字virtual。這些方法被稱爲虛方法;

    作用:如果方法是通過引用或指針而不是對象調用的,它將確定使用哪一種方法。如果沒有關鍵字virtual(),程序將根據引用類型或指針類型選擇方法。如果使用了關鍵字virtual(),程序將根據引用類型或指針類型指向的對象選擇方法。

  • Brass類還聲明瞭一個虛析構函數,雖然該虛構函數不起任何作用。

爲何要使用虛構函數:

在程序中如果成員使用new創建了空間,需要在析構函數中使用delete釋放掉。雖然在本程序中好像並不需要析構函數。如果析構函數不是虛的。在使用指針或引用就很容易出現問題。因爲在不適用virtual關鍵字情況下,如果使用Brass類的指針指向BrassPlush類,指針釋放時,調用的析構函數將是Brass類的析構函數,而不是BrassPlush類的虛構函數。因此,使用虛函數可以保證正確的析構函數調用。

main2.cpp 演示虛方法的行爲:

在main1.cpp中,方法是通過對象調用的,沒有使用到虛方法的特性。下面來看一個使用虛方法的例子,創建一個Brass指針數組,儲存Brass類和BrassPlush類的指針。

main2.cpp

#include <iostream>
#include "Brass.h"

int main(){
    using std::cin;
    using std::cout;
    using std::endl;

    Brass * p_clients[4];
    std::string temp;
    long tempnum;
    double tempbal;
    char kind ;

    for (int i =0;i < 4;i++) {
        cout << "Enter client's name: ";
        getline(cin,temp);
        cout << "Enter client's account number: ";
        cin >> tempnum;
        cout << "Enter opening balance: $";
        cin >> tempbal;
        cout << "Enter 1 for Brass Account or 2 for BrassPlush Account: ";
        while (cin >> kind &&(kind != '1' && kind != '2'))
            cout << "Enter either 1 or 2";
        if (kind == '1')
            p_clients[i] = new Brass(temp,tempnum,tempbal);
        else {
            double tmax,trate;
            cout << "Enter the overdraft limit: $";
            cin >> tmax;
            cout << "Enter ther interest rate as a decimal fraction: ";
            cin >> trate;
            p_clients[i] = new BrassPlus(temp,tempnum,tempbal,tmax,trate);
        }
        while (cin.get() != '\n') continue;
    }
    cout << endl;
    for (auto & p_client : p_clients){
        p_client->ViewAcct();
        cout << endl;
    }
    for (auto & p_client : p_clients){
        delete  p_client;
    }
    cout << "Done.\n";
    return 0;
}

靜態聯編和動態聯編:

程序調用函數時,將使用哪個可執行代碼塊呢?編譯器負責回答這個問題。將源代碼中的函數調用解釋爲執行特定的函數代碼塊被稱爲函數聯編。在C語言中,這非常簡單,因爲每個函數名都對應一個不同的函數。在C++中,由於函數重載的緣故,這項任務更加複雜。編譯器必須查看函數參數以及函數名才能確定使用哪個函數。然而C/C++編譯器可以在編譯過程中完成這種聯編。在編譯過程中進行聯編稱爲靜態聯編,又稱早期聯編。然而,虛函數使這項工作變得更爲困難。使用哪個函數是不能在編譯時確定的,因爲編譯器不知道用戶將選擇哪種類型的對象。所以,編譯器必須能夠在程序運行時選擇正確的虛方法的代碼,這種被稱爲動態聯編,又稱晚期聯編。

要探討這一過程,首先介紹C++中如何處理指針和引用類型的兼容性。

指針和引用類型的兼容性:

在C++中,動態聯編與通過指針和引用調用方法相關,從某種程度上說,這是由繼承控制的。公有繼承建立is-a關係的一種方法是如何處理指向對象的指針和引用。通常,C++不允許將一種類型的地址賦值給另一種類型的指針,也不允許將一種類型的地址賦給另一種類型的指針,也不允許將一種類型的引用指向另一種類型。

double x = 2.5;
int pi = &x; // not allowed
long & r1 = x; // not allowed

然而,指向基類的引用或指針可以引用派生類對象,而不必進行顯式轉換。

將派生類引用或指針轉換爲基類引用或指針被稱爲向上強制轉換,這使公有繼承不需要進行顯式類型轉換。該規則是is-a關係的一部分。BussPlush對象都是Brass對象,因爲它繼承了Brass對象的全部成員與函數。

相反,將基類指針或引用轉換類派生類指針或引用被稱爲向下強制轉換

void fr(Brass &rb); // use rb.ViewAcct()
void fp(Brass * pb); // use pb->ViewAcct()
void fv(Brass b); // use b.ViewAcct()
int main(){
	Brass b1("Billy",1234123.10000.12);
	BrassPlush bp1("Bretty", 2341234.12345.0)
	fr(b1);  // user Brass::ViewAcct()
	fr(bp1); // user BrassPlush::ViewAcct()
	fp(b1);  // user Brass::ViewAcct()
	fp(bp1); // user Brass::ViewAcct()
	fv(b1);		// user Brass::ViewAcct()
	fv(bp1);	// user Brass::ViewAcct()
}

按值傳遞導致只將BrassPlush 對象的Brass部分傳遞給函數fv。但隨引用和指針發生的隱式向上轉換導致函數fr和fp分別調用了 Brass::ViewAcct() 和 BrassPlush::ViewAcct()。

虛成員函數和動態聯編:

1 爲什麼有兩種類型的聯編以及爲什麼默認靜態聯編?

動態聯編能夠重新定義類方法,而靜態聯編在這方面很差,爲何不摒棄靜態聯編呢?原因有兩個—效率和概念模型。

首先來看效率。爲使程序能夠在運行階段進行決策,必須採取一些方法來跟蹤基類指針或引用指向的對象類型,這增加了額外的處理開銷(稍後將介紹一種動態聯編方法)。如果類不作爲基類,這不需要動態聯編。如果派生類不重新定義方法,也不需要動態聯編。這種情況下,使用靜態聯更合理,效率更高,由於靜態聯編的效率更高,因此被設計成C++的默認選擇。

接下來看概念模型。在設計類時,可能包含一些不在派生類重新定義的成員函數。如Brass::Banalce()。這有兩方面好處:首先,效率更高;其次,指出不需要重新定義該函數。這表明,僅將那些預期被重新定義的方法聲明爲虛的。

虛函數的工作原理:

通常,編譯器處理虛函數的方法是:給每個對象添加一個隱藏成員。隱藏成員中保存了一個指向函數地址數組的指針。這種數組稱爲虛函數表。虛函數表中存儲了爲類對象進行聲明的虛函數地址。

總之,使用虛函數時,在內存和執行速度方面都有一定的成本,包括:

  • 每個對象都將增大,增大量爲存儲地址的空間;
  • 對於每個類,編譯器都創建一個虛函數地址表
  • 對於每個函數調用,都需要執行一項額外操作,及到表中查找地址。
有關虛函數的注意事項:
  • 在基類方法的聲明中使用關鍵字virtual可使該方法在基類以及所有的派生類中是虛的。

  • 如果使用指向對象的引用或指針來調用虛方法,程序將使用對象類型定義的方法,而不使用爲引用 對指針類型定義的方法這稱爲動態聯編或晚期聯編。

  • 如果定義的類將被用作基類,則應將那些要在派生類中重新定義的類方法聲明爲虛的。

  • 重新定義將隱藏方法:

    假設創建瞭如下代碼:

    class Dwelling {
    public:
    	virtual void showperks(int a) const;
    }
    class Hovel:public Dwelling {
    public:
    	virtual void showperks() const;	
    }
    

    新函數將showperks()定義爲一個不接受任何參數的函數。重新定義不會生成個函數的兩個重載版本,而是隱藏了基類中的版本。

    • 如果重新定義繼承的方法,應確保與原型完全相同,但如果返回類型是基類引用或指針,則可修改爲指向派生類的引用或指針,這種特性被稱爲返回類型協變,因爲允許返回類型隨類型的變化而變化。

      class Dwelling {
      public:
      	virtual Delling & build(int n) const;
      }
      class Hovel:public Dwelling {
      public:
      	virtual Hovel & build(int n) const;	
      }
      
    • 如果基類聲明被重載了,則應在派生類中重新定義所有的基類版本:

      class Dwelling {
      public:
      	virtual void showperks(int a) const;
      	virtual void showperks(double x) const;
      	virtual void showperks() const;
      }
      class Hovel:public Dwelling {
      public:
      	virtual void showperks(int a) const;
      	virtual void showperks(double x) const;
      	virtual void showperks() const;
      }
      

      如果只重新定義一個版本,如Hovel::showperks(),則另外兩個版本將被隱藏,派生類對象無法使用它們

訪問控制protected:

與private相似,但protected 成員在派生類中可以訪問

class Brass {
protected:
    double balance;
}

這種情況下,BrassPlush類就可以直接訪問balance對象,而不需要Balance()方法了。

抽象基類:

C++ 通過使用純虛函數提供未實現的函數。純虛函數聲名處有=0,參見Area()

class BaseEllipse
{
private:
	double x;
	double y;
	...
public:
	BaseEllipase(double x0=0,double y0=0) :x(x0),y(y0){}
	virtual ~BaseEllipase(){}
	void Move(int nx, ny) {x = nx,y=ny};
	virtual double Area() const = 0;
}

當類的聲明中包含純虛函數時,則不能創建該類的對象。這裏的理念是純虛函數的類只能用作基類。

要成爲真正的ABC(abstract base class),必須至少包含一個純虛函數。這裏的方法Area()沒有定義,但C++甚至允許虛函數有定義。

應用ABC概念:

AccABC.h

#ifndef D1_ACCABC_H
#define D1_ACCABC_H

#include <iostream>
#include <string>

// Abstract Base Class
class AccABC {
private:
    std::string fullname;
    long acctNum;
    double balance;
protected:
    struct Formatting {
        std::ios_base::fmtflags flag;
        std::streamsize pr;
    };
    const std::string & FullName() const { return fullname;};
    long AcctNum() const { return acctNum;};
    Formatting SetFormat() const ;
    void Restore(Formatting &f) const ;
public:
    AccABC(const std::string & fn = "NullBody", long an = -1, double bal =0.0):fullname(fn),acctNum(an),balance(bal){};
    void Deposit(double amt);
    virtual void Withdraw(double amt) = 0;
    double  Balance() const { return balance;};
    virtual void ViewAcct() const = 0;
    virtual ~AccABC(){};
};

class Brass: public AccABC {
public:
    Brass(const std::string & fn = "NullBody", long an = -1, double bal =0.0):AccABC(fn,an,bal){};
    virtual void Withdraw(double amt);
    virtual void ViewAcct() const;
    virtual ~Brass(){};
};


class BrassPlus:public AccABC{
private:
private:
    double maxLoan;
    double rate;
    double owesBank;
public:
    BrassPlus(const std::string &fn = "Nullbody",long an = -1, double bal = 0.0, double ml = 500, double r = 0.11125)
                                    :AccABC(fn,an,bal),maxLoan(ml),rate(r),owesBank(0.0){};
    BrassPlus(const Brass & brass, double ml = 500, double r = 0.11125):AccABC(brass),maxLoan(ml),rate(r),owesBank(0.0){};
    virtual void Withdraw(double amt) ;
    virtual void ViewAcct() const ;
    void ResetMax(double m) { maxLoan = m ;};
    void ResetRate(double r) { rate = r ;};
    void ResetOwes() { owesBank = 0 ;};
};

#endif //D1_ACCABC_H

AccABC.cpp

#include "AccABC.h"
#include <iostream>
using std::cout;
using std::endl;
using std::string;
using std::ios_base;

void AccABC::Deposit(double amt) {
    if (amt < 0)
        cout << "Negative deposit not allowed; deposit is canceled.\n";
    else
        balance += amt;
}

void AccABC::Withdraw(double amt){
    balance -= amt;
}

AccABC::Formatting AccABC::SetFormat() const {
    Formatting f;
    f.flag = cout.setf(ios_base::fixed,ios_base::floatfield);
    f.pr = cout.precision(2);
    return f;
}

void AccABC::Restore(AccABC::Formatting &f) const {
    cout.setf(f.flag,ios_base::floatfield);
    cout.precision(f.pr);
}

void Brass::Withdraw(double amt) {
    if (amt < 0)
        cout << "Withdrawal amount must be positive; withdrawl canceled.\n";
    else if (amt <= Balance())
        AccABC::Withdraw(amt);
    else
        cout << "Withdrawal amount of $ exceeds your balance.\n"
             << "withdrawal canceled.\n";
}

void Brass::ViewAcct() const {
    Formatting f = SetFormat();
    cout << "Client: " << FullName() << endl;
    cout << "Account Number: " << AcctNum() << endl;
    cout << "Balance: $" << Balance() << endl;
    Restore(f);
}

void BrassPlus::Withdraw(double amt) {
    Formatting f = SetFormat();

    double bal = Balance();
    if (amt < bal)
        AccABC::Withdraw(amt);
    else if ( amt < bal + maxLoan - owesBank) {
        double  advance = amt - bal;
        owesBank  += advance * (1.0 + rate);
        cout << "Bank advance: $" << advance << endl;
        cout << "Finance charge: $" << advance * rate << endl;
        Deposit(advance);
        AccABC::Withdraw(amt);
    } else
        cout << "Credit limit exceeded. Transaction cancelled.\n";
    Restore(f);
}

void BrassPlus::ViewAcct() const {
    Formatting f = SetFormat();
    AccABC::ViewAcct();
    cout << "Maximum loan: $" << maxLoan << endl;
    cout << "owed to bank: $" << owesBank << endl;
    cout.precision(3);
    cout << "Loan Rate: " << 100 * rate << "%\n";
    Restore(f);
}

保護方法FullName()和AcctNum()提供了對數據成員fullname和acctNum的只讀訪問,使得可以進一步定製每個派生類的ViewAcct()。

這個版本在設置輸出格式方面做了兩項改進。前一個版本使用了兩個函數調用來設置輸出格式,並使用一個函數調用來恢復格式。舊版本問題在於SetFormat()和restore()都是獨立函數,這些函數與客戶定義的同名函數發生衝突。解決方法有多種:1、將函數聲明爲靜態的,這樣它們歸文件brass.cpp及其繼任AccABC.cpp私有。 2、將函數與Formatting結構放到獨立的命名空間中

繼承和動態內存分配:

一個例子:

dma.h

#ifndef D1_DMA_H
#define D1_DMA_H

#include <iostream>

class baseDMA
{
private:
    char * lable;
    int rating;
public:
    baseDMA(const char * l = "null", int r = 0);
    baseDMA(const baseDMA & rs);
    virtual ~baseDMA();
    baseDMA &operator=(const baseDMA & rs);
    friend std::ostream &operator<<(std::ostream &os, const baseDMA & rs);
};

class lacksDMA: public baseDMA
{
private:
    enum {COL_LEN = 40};
    char color[COL_LEN];
public:
    lacksDMA(const char *c ="blank", const char *l ="null" ,int r =0);
    lacksDMA(const char *c, const baseDMA & rs);
    friend std::ostream &operator<<(std::ostream & os, const lacksDMA & rs);
};

class hasDMA: public baseDMA
{
private:
    char *style;
public:
    hasDMA(const char *s ="none", const char *l ="null", int r =0);
    hasDMA(const char *s, const baseDMA &rs);
    hasDMA(const hasDMA &hs);
    ~hasDMA();
    hasDMA &operator=(const hasDMA &rs);
    friend std::ostream &operator<<(std::ostream &os, const hasDMA &rs);
};

#endif //D1_DMA_H

dma.cpp

#include "dma.h"
#include <cstring>

baseDMA::baseDMA(const char *l, int r) {
    lable = new char[std::strlen(l)+1];
    std::strcpy(lable,l);
    rating = r;
}

baseDMA::baseDMA(const baseDMA &rs) {
    lable = new char[std::strlen(rs.lable) + 1];
    std::strcpy(lable,rs.lable);
    rating = rs.rating;
}

baseDMA::~baseDMA() {
    delete [] lable;
}

baseDMA &baseDMA::operator=(const baseDMA &rs) {
    if (this == &rs){
        return *this;
    }
    delete[] lable;
    lable = new char[std::strlen(rs.lable) + 1];
    std::strcpy(lable,rs.lable);
    rating = rs.rating;
    return *this;
}

std::ostream &operator<<(std::ostream &os, const baseDMA &rs) {
    os << "lable: " << rs.lable << '\n';
    os << "rating: " << rs.rating << '\n';
    return os;
}

lacksDMA::lacksDMA(const char *c, const char *l, int r):baseDMA(l,r) {
    std::strncpy(color,c, COL_LEN-1);
    color[COL_LEN-1] = '\0';
}

lacksDMA::lacksDMA(const char *c, const baseDMA &rs) :baseDMA(rs){
    std::strncpy(color,c, COL_LEN-1);
    color[COL_LEN-1] = '\0';
}

std::ostream &operator<<(std::ostream &os, const lacksDMA &rs) {
    os << (const baseDMA &) rs;
    os << "color: " << rs.color << '\n';
    return os;
}

hasDMA::hasDMA(const char *s, const char *l, int r):baseDMA(l,r){
    style = new char [std::strlen(s) + 1];
    strcpy(style,s);
}

hasDMA::hasDMA(const char *s, const baseDMA &rs) :baseDMA(rs) {
    style = new char [std::strlen(s) + 1];
    strcpy(style,s);
}

hasDMA::hasDMA(const hasDMA &hs) :baseDMA(hs) {
    style = new char [std::strlen(hs.style) + 1];
    strcpy(style,hs.style);
}

hasDMA::~hasDMA() {
    delete[] style;
}

hasDMA &hasDMA::operator=(const hasDMA &rs) {
    if (this == &rs) return *this;
    baseDMA::operator=(rs);
    delete[] style;
    style = new char [std::strlen(rs.style) + 1];
    strcpy(style,rs.style);
    return *this;
}

std::ostream &operator<<(std::ostream &os, const hasDMA &rs) {
    os << (const baseDMA &) rs;
    os << "style: " << rs.style << '\n';
    return os;
}

main.cpp

#include <iostream>
#include "dma.h"

int main(){
    using std::cout;
    using std::endl;
    baseDMA shirt("Portabelly",8);
    lacksDMA ballon("red","Blimpo",4);
    hasDMA map("Mercator","Buffalo Keys",5);
    cout << "Displaying basDMA object:\n";
    cout << shirt << endl;
    cout << "Displaying lacksDMA object:\n";
    cout << ballon << endl;
    cout << "Displaying hasDMA object:\n";
    cout << map << endl;
    lacksDMA ballon2(ballon);
    cout << "Result of lacksDMA copy:\n";
    cout << ballon2 << endl;
    hasDMA map2;
    map2 = map;
    cout << "Result of hasDMA assignment:\n";
    cout << map2 << endl;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章