C++多态公有继承

C++多态公有继承

一、说明

在派生类对象使用基类的方法时,可能不会做任何修改。但也会遇到这样一种情况,希望同一个方法在派生类和基类中的行为是不同的。换句话说,方法的行为应取决于调用该方法的对象。这种较复杂的行为称为多态—具有多种形态,即同一个方法的行为随上下文而异。有两种重要的机制可以用于实现多态公有继承:

(1)在派生类中重新定义基类的方法

(2)使用虚方法

具体的实现方式,容我慢慢道来

二、设计基类与派生类

注意: is-a关系是不可逆的,即水果不是香蕉。同时本节设计的Brass基类对象不具备BrassPlus派生类对象的所有功能。

// Brass Account Base Class
class Brass
{
private:
    // 全称
    std::string fullname;
    // 账户数量
    long acctnum;
    // 定金
    double balance;

public:
    Brass(const std::string & s = "Nullbody", long an = 1, double bal = 0.0);
    virtual ~Brass();

    void Deposit(double amt);
    double Balance() const;
    virtual void Withdraw(double amt);
    virtual void ViewAcct() const;
};

// Brass Plus Account Derived Class
class BrassPlus : public Brass
{
private:
    // 最高贷款额
    double maxloan;
    // 贷款利率
    double rate;
    // 欠银行的钱,即具体欠银行多少钱
    double owesbank;

public:
    BrassPlus(const std::string & s = "Nullbody", long an = 1, double bal = 0.0, double ml = 500, double r = 0.11125);
    BrassPlus(const Brass & ba, double ml = 500, double r = 0.11125);
    ~BrassPlus();

    virtual void ViewAcct() const;
    virtual void Withdraw(double amt);
    void ResetMax(double m) { maxloan = m; }
    void ResetRate(double r) { rate = r; }
    void ResetOwes() { owesbank = 0; }
};

三、基类与派生类说明

3.1 第二节基类与派生类说明

(1)BrassPlus类在Brass类的基础上添加了3个私有数据和3个公有函数

(2)Brass类和BrassPlus类都声明了ViewAcct()和Withdraw()方法,但BrassPlus对象和Brass对象的这些方法的行为是不同的。

详细说明: 此说明介绍了声明如何指出方法在派生类的行为的不同。两个ViewAcct()原型表明将2个独立的方法定义。基类版本的限定名为Brass::ViewAcct(),派生类版本的限定名为BrassPlus::ViewAcct(),程序将使用对象类型来确定使用哪个版本,Withdraw()方法与ViewAcct()使用类同。对于在两个类中行为相同的方法则只在基类中声明

举例说明如下:

Brass dom("Test", 1234, 1234.56);
BrassPlus dot("Test1", 5678, 5678.91);

// 使用Brass::ViewAcct()
dom.ViewAcct();
// 使用BrassPlus::ViewAcct()
dot.ViewAcct();

(3)Brass类在声明ViewAcct()和Withdraw()时使用了关键字virtual。这些方法被称为虚方法

详细说明: 使用virtual则要比前两点复杂。如果方法是通过指针或引用而不是对象调用的,它将确定使用哪一种方法。

  • 如果没有关键字virtual,程序见根据引用或指针的类型选择调用的方法
  • 如果使用virtual关键字,程序将根据引用或指针指向的对象的类型选择调用的方法

举例说明如下:

// 此示例是使用的引用做说明,指针使用与引用类似
Brass dom("Test", 1234, 1234.56);
BrassPlus dot("Test1", 5678, 5678.91);
Brass &b1_ref = dom;
Brass &b2_ref = dot;

// 如果ViewAcct()函数使用virtual关键字声明
// 使用Brass::ViewAcct()
b1_ref.ViewAcct();
// 使用BrassPlus::ViewAcct()
b2_ref.ViewAcct();

// 如果ViewAcct()函数没有使用virtual关键字声明
// 使用Brass::ViewAcct()
b1_ref.ViewAcct();
// 使用Brass::ViewAcct()
b2_ref.ViewAcct();

注意: 在基类中将派生类会重新定义的方法声明为虚方法。方法在基类中被声明为虚方法后,它在派生类中将自动称为虚方法。然而,在派生类声明中使用关键字virtual来指出哪些函数是虚函数也不失为一个好办法。 关键字virtual只用于类声明的方法原型中,而不使用于方法定义

(4)Brass类还声明了一个虚析构函数。

详细说明: 基类声明了一个虚析构函数,这样做是为了确保释放派生类对象时,按正确的顺序调用析构函数。

3.2 基类与派生类实现及使用说明
// 全局函数声明
std::ios_base::fmtflags setformat();
void restore(std::ios_base::fmtflags fmt, std::streamsize pc);

// 基类实现
Brass::Brass(const std::string &s, long an, double bal)
{
    fullname = s;
    acctnum = an;
    balance = bal;
}

Brass::~Brass()
{

}

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

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

void Brass::Withdraw(double amt)
{
    // 格式设置
    std::ios_base::fmtflags initialstate = setformat();
    std::streamsize precis = std::cout.precision(2);

    if (amt < 0)
        std::cout << "Withdrawal amount must be positive; " << "Withdrawal canceled.\n";
    else if (amt < balance)
        balance -= amt;
    else
        std::cout << "Withdrawal amount of $" << amt << " exceeds your balance.\n" << "Withdrawal canceled.\n";

    restore(initialstate, precis);
}

void Brass::ViewAcct() const
{
    // 设置格式
    std::ios_base::fmtflags initialstate = setformat();
    std::streamsize precis = std::cout.precision(2);

    std::cout << "Client: " << fullname << std::endl;
    std::cout << "Account Number: " << acctnum << std::endl;
    std::cout << "Balance: $" << balance << std::endl;

    restore(initialstate, precis);
}

// 派生类实现
BrassPlus::BrassPlus(const std::string &s, long an, double bal, double ml, double r)
    : Brass(s, an, bal)
    , maxloan(ml)
    , rate(r)
    , owesbank(0.0)
{

}

BrassPlus::BrassPlus(const Brass &ba, double ml, double r)
    : Brass(ba)
    , maxloan(ml)
    , rate(r)
    , owesbank(0.0)
{

}

BrassPlus::~BrassPlus()
{

}

// 派生类重新定义了ViewAcct()工作方式
void BrassPlus::ViewAcct() const
{
    // 格式设置
    std::ios_base::fmtflags initialstate = setformat();
    std::streamsize precis = std::cout.precision(2);

    Brass::ViewAcct();
    std::cout << "Maximum loan: $" << maxloan << std::endl;
    std::cout << "Owed to bank: $" << owesbank << std::endl;

    std::cout.precision(3);

    std::cout << "Loan Rate: " << 100 * rate << "%\n";

    restore(initialstate, precis);
}

// 派生类重新定义了Withdraw()工作方式
void BrassPlus::Withdraw(double amt)
{
    // 格式设置
    std::ios_base::fmtflags initialstate = setformat();
    std::streamsize precis = std::cout.precision(2);

    double bal = Balance();
    if (bal < 0)
        Brass::Withdraw(amt);
    else if (amt <= bal + maxloan - owesbank) {
        double advance = amt - bal;
        owesbank += advance * (1.0 + rate);
        // 银行预付款
        std::cout << "Band advance: $" << advance << std::endl;
        // 财务费用
        std::cout << "Financd charge: $" << advance * rate << std::endl;

        Deposit(advance);
        Brass::Withdraw(amt);
    }
    else
        std::cout << "Credit limit exceeded. Transaction cancelled.\n";

    restore(initialstate, precis);
}

// 全局函数实现
std::ios_base::fmtflags setformat()
{
    return std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield);
}

void restore(std::ios_base::fmtflags fmt, std::streamsize pc)
{
    std::cout.setf(fmt, std::ios_base::floatfield);
    std::cout.precision(pc);
}

详细说明:

(1)派生类不能直接访问基类的私有数据,而必须使用基类的公有方法才能访问这些数据。访问的方式取决于方法。

  • 派生类构造函数在初始化基类私有数据时,采用成员初始化列表语法,将基类信息传递给基类构造函数。
  • 派生类非构造函数不能使用成员初始化列表语法,但派生类方法可以调用公有的基类方法。

(2)在派生类方法中,标准技术是使用作用域解析运算符来调用基类方法。代码举例如下:

// 代码如果没有使用作用域解析运算符,编译器将认为ViewAcct()是BrassPlus::ViewAcct(),这将创建一个不会终止的递归
// 函数
void BrassPlus::ViewAcct() const
{
    Brass::ViewAcct();
}

(3)方法ViewAcct()与Withdraw()使用格式化方法setf()和precision()将浮点值的输出模式设置为定点,即包含两位小数。

3.3 虚方法

设计的Brass基类指针既可以指向Brass对象,也可以指向BrassPlus对象,因此可以使用一个数组来表示多种类型的对象。这就是多态性。多态性示例代码如下:

const int CLIENTS = 2;
Brass *clients[CLIENTS];
clients[0] = new Brass("Test0", 1234, 1234.56);
clients[1] = new BrassPlus("Test1", 5678, 5678.91);

for (int i = 0;i < CLIENTS;++i)
{
    clients[i]->ViewAcct();
}

多态性说明:

(1)假如ViewAcct()是使用关键字virtual声明

  • 如果数组成员指向的是Brass对象,则调用的是Brass::ViewAcct()。
  • 如果数组成员指向的是BrassPlus对象,则调用的是BrassPlus::ViewAcct()。

(2)假如ViewAcct()不是虚方法

  • 则在任何情况下都将调用Brass::ViewAcct()。
3.4 为何使用虚析构函数
  • 如果析构函数不是虚方法,则将只调用对应于指针类型的析构函数。对于Brass * 指针将只调用Brass基类的析构函数,即使Brass * 指针指向的是BrassPlus对象。
  • 如果析构函数是虚方法,将调用相应对象类型的析构函数。因此,如果指针指向的是BrassPlus对象,将调用BrassPlus的析构函数,然后自动调用基类的析构函数。因此,使用虚析构函数可以确保正确的析构函数序列被调用。
  • 如果BrassPlus包含一个执行某些操作的析构函数,则Brass必须有一个虚析构函数,即使Brass的析构函数不执行任何操作。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的继承多态和虚函数是面向对象编程的重要概念。 继承是指一个类可以从另一个类继承属性和方法。子类可以继承父类的公有成员和保护成员,但不能继承私有成员。通过继承,子类可以重用父类的代码,并且可以添加自己的特定功能。继承可以实现代码的重用和层次化的设计。 多态是指同一个函数可以根据不同的对象调用不同的实现。多态可以通过虚函数来实现。虚函数是在基类中声明为虚拟的函数,它可以在派生类中被重写。当通过基类指针或引用调用虚函数时,实际调用的是派生类中的实现。这样可以实现动态绑定,即在运行时确定调用的函数。 虚函数的原理是通过虚函数表来实现的。每个包含虚函数的类都有一个虚函数表,其中存储了虚函数的地址。当调用虚函数时,编译器会根据对象的类型在虚函数表中查找对应的函数地址并调用。 综上所述,C++中的继承多态和虚函数是实现面向对象编程的重要机制,它们可以提高代码的灵活性和可扩展性。 #### 引用[.reference_title] - *1* *3* [C++多态之 虚函数和虚函数表](https://blog.csdn.net/weixin_46053588/article/details/121231465)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [c++多态及虚函数表内部原理实战详解](https://blog.csdn.net/bitcarmanlee/article/details/124830241)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值