C++Primer Plus(第六版)第十三章学习笔记:

本章内容:

  • is-a关系的继承
  • 保护以公有方式从一个类派生出另一个类
  • 保护访问
  • 构造函数初始化列表
  • 向上和向下的强制转换
  • 虚成员函数
  • 早期联编与晚期联编
  • 抽象基类
  • 纯虚函数
  • 何时及如何使用公有继承

13.1 一个简单基类
head

#include <string>
#ifndef GOLF_H_INCLUDED
#define GOLF_H_INCLUDED
using std::string;
class TableTennisPlayer
{
    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;}
};

class RatePlayer :public TableTennisPlayer
{
private:
    unsigned int rating;
public:
    RatePlayer(unsigned int r = 0, const string & fn = "none", const string & ln = "none", bool ht = false): TableTennisPlayer(fn, ln, ht), rating(r){}
    RatePlayer(unsigned int r, const TableTennisPlayer & tp);
    unsigned int Rating() const {return rating;}
    void RessetRating (unsigned int r) {rating = r;}
};
#endif // GOLF_H_INCLUDED

definition

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

TableTennisPlayer::TableTennisPlayer(const string & fn, const string& ln, bool ht): firstname(fn), lastname(ln), hasTable(ht){}

void TableTennisPlayer::Name()const
{
    std::cout << lastname << ", " << firstname << std::endl;
}

RatePlayer::RatePlayer(unsigned int r, const TableTennisPlayer & tp):TableTennisPlayer(tp), rating(r){}

13.3 多态公有继承
两种方法:

  • 派生类中重新定义基类方法
  • 使用虚方法
    definiton
#include <string>
#ifndef GOLF_H_INCLUDED
#define GOLF_H_INCLUDED
using std::string;

class Brass
{
    string m_fullname;
    long m_acctNum;
    double m_balance;
public:
    Brass(const string & fullname = "none", long acctNum = -1, double balance = 0.0);
    void deposit(double amt);
    virtual void Withdraw(double amt);
    double Balance() const;
    virtual void Vieacct() const;
    virtual ~Brass(){};
};

class BrassPlus : public Brass
{
    double m_maxloan;
    double m_rate;
    double m_owesBank;
public:
    BrassPlus(const string& fullname = "none", long acctNum = -1, double balance = 0.0, double ml = 500, double r = 0.11125);
    BrassPlus(const Brass & ba, double ml = 500, double r = 0.11125);
    virtual void ViewAcct() const;
    virtual void Withdraw(double amt);
    void ResetMax(double m) {m_maxloan = m;}
    void ResetRate(double r) {m_rate = r;}
    void ResetOwes() {m_owesBank = 0;} 
};

#endif //

上面的定义中使用了virtual关键字,该关键字决定该方法所使用的实现方法是哪一种,如果没有virtual,当使用引用或指针调用方法时,调用的是指针类型的类方法,有virtual时调用的是指针所指类型的类方法。

另外,基类中声明为虚方法的函数在派生类中会自动生成虚方法,但是可以自己打出来标识出虚方法,而且在定义中可以省略。

测试

// contain function called in file1
#include <iostream>
#include "golf.h"
using std::cout;
using std::endl;
using std::string;

/*typename std::ios_base::fmtflags format;
typename std::streamsize precis;
format setFormat();
void restore(format f, preces p);*/


Brass::Brass(const string & fn ,long an, double bal)
{
    m_fullname = fn;
    m_acctNum = an;
    m_balance = bal;
}

void Brass::deposit(double amt)
{
    if(amt > 0)
    m_balance += amt;
    else
        cout << "Negative deposit not allowed\n";
}

void Brass::Withdraw(double amt)
{
    //setup ###.##format
    cout << std::fixed;
    cout.precision(2);
    cout.setf(std::ios_base::showpoint);
    if(amt < 0)
    {
        cout << "Withdrawal amount must be positive;" << "withdrawal canceled.\n";
    }
    else if(m_balance >= amt)
        m_balance -= amt;
    else
        cout << "You are poor ,how much money did you get you have no conscious.\n";
}
double Brass::Balance() const
{
    return m_balance;
}

void Brass::ViewAcct() const
{
    // setup ###.## format
    cout << std::fixed;
    cout.precision(2);
    cout.setf(std::ios_base::showpoint);
    cout << "Client: " << m_fullname << endl;
    cout << "Account Number: " << m_acctNum << endl;
    cout << "Balance: $" << m_balance << endl;
}

//BrassPlus methods
BrassPlus::BrassPlus(const string &fn, long an, double bal, double ml, double r) : Brass(fn, an, bal)
{
    m_maxloan = ml;
    m_owesBank = 0;
    m_rate = r;
}
BrassPlus::BrassPlus(const Brass & b, double ml, double r) : Brass(b)
{
    m_maxloan = ml;
    m_owesBank = 0;
    m_rate = r;
}

//redefine Viewacct()works

void BrassPlus::ViewAcct()const
{
    //setup format
    cout << std::fixed;
    cout.precision(2);
    cout.setf(std::ios_base::showpoint);
    Brass::ViewAcct();
    cout << "Maximum loan: $" << m_maxloan <<endl;
    cout << "Owed to bank: $" << m_owesBank <<endl;
    cout.precision(3);
    cout << "Loan Rate: " << 100 * m_rate <<endl;

}
void BrassPlus::Withdraw(double amt)
{
    cout << std::fixed;
    cout.precision(2);
    cout.setf(std::ios_base::showpoint);
    double bal = Balance();
    if(bal >= amt)
        Brass::Withdraw(amt);
    else if(amt <= bal + m_maxloan - m_owesBank)
    {
        m_owesBank += (amt - bal) * m_rate;
        cout << "Bank advance: $" << amt - bal << endl;
        cout << "Finance charge: $" << (amt - bal) * m_rate << endl;
        deposit(amt - bal);
        Brass::Withdraw(amt);

    }
    else
        cout << "You shall not take my money away.\n";

}

但是上方代码没有使用虚方法的特性,因为使用对象来调用的方法,不是用指针

接下来用一个基类的指针数组来存储不同的类型,从而使用虚方法的特性

#include <iostream>
#include <string>
#include "golf.h"



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

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

4.为何要使用虚析构函数
如果不用,那在上述的基类指针数组中,就只有基类对象会被释放,其他的不会调用析构函数
virtual类方法类似与一个家庭里的家族文化,传承下来的,大家都用,但如果不加,就是一直家中实体的东西,就只有一个,不能大家都用

13.4.2 虚成员函数与动态联编
动态联编:在程序运行的时候决定可执行代码块
静态联编:在编译的时候决定执行代码块

2.虚函数的工作原理
简单的说一下:对于提供虚函数声明的基类以及其派生类对象,都会生成一个虚函数表,虚函数表中存储的是各个虚函数的地址,而每个对象都会有个指针用于指向虚函数表的表头地址
此时如果派生类中的虚函数有提供新的定义,就在派生类对象的虚函数表中添加新定义的函数地址,如果没有就存储其基类的虚函数地址。
调用的时候就从这个虚函数表来查找调用什么位置的函数执行

13.4.3 有关虚函数注意事项

  • 关键字:virtual
  • 调用的是所指向的对象类方法而不是指针类型的方法
  • 如果定义的类作为基类,则派生类中重新定义的函数都应用virtual来声明

1.构造函数
构造函数不能是虚函数,因为创建的时候调用的是派生类的构造函数,而在派生类的构造函数中使用基类的构造函数,所以虚的也没有意义
2.析构函数
基类需要一个虚析构函数
3.友元
不能是虚函数,因为只有成员函数才能是虚函数,友元函数不是成员函数
4.没有重新定义
使用最新的虚函数版本
5.重新定义将隐藏方法
在派生类中重新定义的成员函数,将隐藏其基类中的同名函数的方法,也就是说只有新的这个能用,这 和重载是不同的

13.5 访问控制protected
关键字:protected
特点:与private在基类中没有区别,而在派生类中有区别,就是派生类可以直接访问基类的protected成员,不能访问基类的private成员,就像子女可以打开父母的遗产的保险柜,但是不能知道他们自己私人的很多事情

13.6 抽象基类(abstract base class)
对于圆是一种特殊的椭圆这件事,如果将圆从椭圆类中派生出来则非常麻烦,不如重新定义,但重新定义又忽略了他们的共同点,这时候使用一个ABC将他们两者的共同点创建为一个类,再从这个类中派生出两者
中间不能实现的函数就作为纯虚函数,后面=0就行了

包含纯虚函数的类只能作为基类,不能被实例化

抽象基类的实现就是在之前的那边添加一个基础抽象类AcctABC,把两种账号都从此基类派生出来,就可以用一个AcctABC的指针数组管理两种派生类了。

13.7 继承和动态内存分配
13.7.1 派生类不用new
基类用new,派生类不用,则在派生类中不用显式析构函数等。

13.7.2 派生类使用new
必须为派生类定义显式的析构函数、复制构造函数和赋值运算符重载

13.7.3 动态分配继承实例
其中有使用friend函数来重载<<运算符。当派生类想要重载时,在输出中先强制转换为基类,用一次基类的<< 运算符,再输出派生类的新数据成员
动态分配的派生类要单独编写析构函数
head

#include <iostream>
#ifndef GOLF_H_INCLUDED
#define GOLF_H_INCLUDED

class baseDMA
{
    char * label;
    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
{
    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
{
    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 & rs);
    ~hasDMA();
    hasDMA & operator=(const hasDMA & rs);
    friend std::ostream & operator<<(std::ostream & os, const hasDMA & rs);
};
#endif // GOLF_H_INCLUDED

definition

// contain function called in file1
#include <iostream>
#include <cstring>
#include "golf.h"

using namespace std;

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

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

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

ostream & operator<<(ostream & os, const baseDMA & rs)
{
    os << "Label: " << rs.label << endl;
    os << "rating: " << rs.rating << endl;
    return os;
}

//lackDMA
lacksDMA::lacksDMA(const char * c, const char * l, int r): baseDMA(l, r)
{
    strncpy(color, c, 39);
    color[39] = '\0';
}
lacksDMA::lacksDMA(const char * c, const baseDMA & rs) : baseDMA(rs)
{
    strncpy(color, c, 39);
    color[39] = '\0';
}

ostream & operator<<(ostream & os, const lacksDMA & rs)
{
    os << (const baseDMA &) rs;
    os << "Color: " <<rs.color << endl;
    return os;
}

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

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

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

hasDMA & hasDMA::operator=(const hasDMA & hs)
{
    if(this == &hs)
        return *this;
    baseDMA::operator=(hs);
    delete [] style;
    style = new char[strlen(hs.style) + 1];
    strcpy(style, hs.style);
    return *this;
}
ostream & operator<<(ostream & os, const hasDMA & hs)
{
    os << (const baseDMA &)hs;
    os << "Style: " << hs.style << endl;
    return os;
}

13.8 类设计回顾
13.8.1 编译器生成的成员函数

  • 默认构造函数
  • 复制构造函数
  • 赋值运算符重载

13.8.2 其他类方法

  • 构造函数
  • 析构函数
  • 转换
  • 按值传递对象和传递引用
  • 返回对象和返回引用
  • 使用const

13.8.3 公有继承的考虑因素

  • is-a关系
  • 什么不能被继承
  • 赋值运算符
  • 私有成员和保护成员
  • 虚方法
  • 析构函数
  • 友元函数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值