• C++ Primer Plus学习:第十三章


    第十三章 类继承

    继承的基本概念

    类继承是指从已有的类派生出新的类。例:

    0-1 player.h

    class player

    {

    private:

    string firstname;

    string lastname;

    bool hasTable;

    public:

    player(const string & fn="NULL", const string & ln= "none", bool ht = false);

    void Name() const;

    bool HasTable() const{return hasTable;};

    void ResetTable(bool v) {hasTable = v;};

    };

    0-2 TableTennisClass.h

    class TableTennisClass: public Player

    {

    private:

    unsigned int rating;

    public:

    TableTennisPlayer(unsigned int r=0, const string &fn ="none",

    const string &ln ="none",bool ht=false);

    unsigned int Rating() const {return rating};

    }

    以上的方法称为公有继承,派生类对象存储了基类的数据成员和基类的方法。

    派生类不能直接访问基类的私有成员,必须通过基类方法访问。创建派生类对象的时候,程序首先创建基类对象。基类对象应在程序进入派生类构造函数前被创建。C++使用成员初始化列表来完成这种工作。

    0-3 C++初始化列表

    TableTennisPlayer::TableTennisPlayer(unsigned int r=0, const string &fn ="none",

    const string &ln ="none",bool ht=false):player(fn,ln,ht);

    使用派生类构造函数的参数创建基类。

    如果不指定调用基类的构造函数,程序将调用默认的基类的构造函数。

    派生类对象过期时,程序首先调用派生类的析构函数,再调用基类的析构函数。

    is-a继承关系:派生类对象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类执行。

    多态公有继承

    多态:希望同一个方法在基类和派生类中的行为是不同的,我们可以使用虚方法来实现这种功能。

    我们使用brass类和brassplus类说明

    0-4 brass.h

    #include<string>

    class brass

    {

    private:

    std::string fullName;

    long acctNum;

    double balance;

    public:

    brass(const std::string & s= "NullBody", long an=-1,

    double bal =0.0,);

    void Despoit(double amt);

    virtual void Withdraw(double amt);

    double Balance() const;

    virtual void ViewAcct() const;

     

    }

    0-5 brassplus.h

    class brassplus:public brass

    {

    private:

    double maxLoan;

    double rate;

    double owesBank;

    public:

    brass(const std::string & s= "NullBody", long an=-1,

    double bal =0.0, double ml=500,

    double r=0.1125);

    brassplus(const brass & ba, double ml=500,double r=0.1125);

    virtual void ViewAcct() const;

    virtual void Withdraw(double amt);

    void ResetMax(double m) {maxLoan=m};

     

    }

    当定义基类方法为虚方法后,可以在继承类中重写此方法。通过对象调用虚方法,不能显示出虚方法的特性,当使用应用或者指针调用时,会显示出虚方法的多态性。

    0-6 虚方法特性

    cons tint CLIENTS=2;

    brass* clients[CLIENTS];

    clients[0]=new brass("Tom",123456,1000);

    clients[1]=new brassplus(*clients[0],1000,0.0125);

    //虚方法的多态性

    clients[0]->ViewAcct(); //调用brass类的ViewAcct()函数

    clients[1]-> ViewAcct();; //调用brassplus类的ViewAcct()函数

    虚析构函数

    若基类的析构函数不是虚的,delete clients[1];将执行~brass();这是不对的,若~brass声明为virtual ~brass();将执行brassplus的析构函数。

    动态联编与静态联编

    函数名联编:将源代码中的函数调用解释为执行特定的函数代码快。

    在在编译阶段进行联编。但是,使用虚函数后,不能在编译阶段决定使用哪个函数,所以,编译器必须生成能够在程序运行阶段时选择正确虚方法的代码,这被称为动态联编。

    虚函数的工作原理

    编译器处理虚函数的方式是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址的指针。这种数组称为虚函数表(virtual function table,vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址。基类和派生类对象均有一个独立的虚函数表的指针,如果派生类没有重定义虚函数,保存函数原始版本的地址。

    虚函数使用注意事项

    基类中声明的虚函数在基类和所有派生类中都是虚的。

    如果定义的类被用作基类,则应将那些在派生类中需要重定义的类方法声明为虚的。

    构造函数不能是虚的。派生类不继承基类的构造函数。

    基类的析构函数应该是虚的。

    如果派生类没有重新定义函数,将使用该函数的基类版本,如果派生类位于派生链中,则将使用最新的虚函数版本。

    如果基类声明重载了,那么派生类中应该重定义所有的基类版本。如果之定义部分版本,其余版本将隐藏。

    抽象基类

    含有纯虚函数的类,此类不允许实例化,只能用来作为基类。

    纯虚函数:声明的结尾加上=0;例:virtual double Area() const=0;

    纯虚函数可以在源文件中定义。

    继承和动态内存分配

    如果基类动态内存分配,并重新定义赋值和复制构造函数。如果派生类不使用动态内存分配,不需要为派生类定义显式析构函数、复制构造函数和赋值运算符。

    如果派生类使用动态内存分配,则应为派生类定义显式析构函数、复制构造函数和赋值运算符。如下表所示:

    0-7 baseDMA.h

    //Base Class of Dynamic Memory Allocation

    #ifndef BDMA_H_

    #define BDMA_H_

    #include<iostream>

    class baseDMA

    {

    private:

    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);

    };

    #endif

    0-8 base.cpp

    #include "baseDMA.h"

    #include<cstring>

     

    baseDMA::baseDMA(const char *l,int r)

    {

    label=new char[std::strlen(l)+1];

    std::strcpy(label,l);

    rating=r;

    }

     

    //复制构造函数

    baseDMA::baseDMA(const baseDMA & rs)

    {

    label=new char[std::strlen(rs.label)+1];

    std::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[std::strlen(rs.label)+1];

    std::strcpy(label,rs.label);

    rating=rs.rating;

    return *this;

    }

     

    //<<操作符重载(友元函数)

    std::ostream & operator<<(std::ostream & os, const baseDMA &rs)

    {

    os<<"Label"<<rs.label<<std::endl;

    os<<"Rating"<<rs.rating<<std::endl;

    return os;

    }

     

    0-9 deriveDMA.h

    #ifndef DDMA_H_

    #define DDMA_H_

    class deriveDMA

    {

    private:

    char *style;

    public:

    deriveDMA(const char * s="none", const char *l="null",int r=0);

    deriveDMA(const char *s, const baseDMA & rs);

    deriveDMA(const deriveDMA & ds);

    ~deriveDMA();

    deriveDMA & operator=(const deriveDMA &rs);

    friend std::ostream & operator<<(std::ostream);

    };

    #endif

    0-10 deriveDMA.cpp

    #include "deriveDMA.h"

    #include <cstring>

     

    deriveDMA::deriveDMA(const char *s, const char *l, int r):baseDMA(l,r)

    {

    style=new char[std::strlen(s)+1];

    std::strcpy(style,s);

    }

     

    deriveDMA::deriveDMA(const char * s,const baseDMA & rs):baseDMA(rs)

    {

    style=new char[std::strlen(s)+1];

    std::strcpy(style,s);

    }

     

    //重定义复制构造函数

    deriveDMA::deriveDMA(const deriveDMA & ds)

    {

    style=new char[std::strlen(ds.style)+1];

    std::strcpy(style,ds.style);

    }

     

    //析构函数

    deriveDMA::~deriveDMA()

    {

    delete [] style;

    }

     

    //重定义赋值操作符

    //利用派生类对象调用基类赋值操作符初始化派生类中的基类成员

    deriveDMA & operator=(const deriveDMA & ds)

    {

    //如果是自我赋值

    if(this==&ds)

    return *this;

    delete [] style;

    //使用派生类对象初始化基类对象

    //等价于*this=ds;

    baseDMA::operator(ds);

    style=new char[std::strlen(ds.style)+1];

    std::strcpy(style,ds.style);

    return *this

    }

     

    //重载<<运算符

    std::ostream & operator<<(std::ostream & os, const deriveDMA & ds)

    {

    //由于<<重载通过友元函数定义,而派生类不继承基类的友元函数

    //所以使用强制类型转化将派生类对象转化为基类对象的引用

    //以调用基类的<<运算符(友元函数)

    os<<(const baseDMA &)ds;

    os<<"Style"<<ds.style<<std::endl;

    return os;

    }

    总结

    派生类不继承基类的构造函数、析构函数和赋值运算符。

    如果派生类使用了new运算符,则必须提供显式的赋值运算符,必须为类的每个成员提供赋值运算符。

    友元函数并非成员函数,因此不能被继承。

  • 相关阅读:
    CSS尺寸单位 % px em rem 详解
    【MySQL】mysql在Windows下使用mysqldump命令备份数据库
    CSS教程:vlink,alink,link和a:link
    正则表达式入门教程
    【MySQL】MySQL支持的数据类型
    iOS应用程序状态图
    Java开发
    Java开发
    iOS开发点滴
    Android开发点滴
  • 原文地址:https://www.cnblogs.com/xyb930826/p/5267037.html
Copyright © 2020-2023  润新知