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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章