基类派生类及动态绑定

使用基类引用或者指针调用一个虚成员函数时才会执行动态绑定

定义基类和派生类

定义基类

class Quote{
private:
    string bookNo;
protected:
    double price=0;
public:
    Quote()=default;//需要默认的构造函数
//    带参数的构造函数
    Quote(const string & book,double sales_price):bookNo(book),price(sales_price){};
    // 通过isbn函数访问私有的bookNo
    string isbn()const{return bookNo;}
//    计算基类的总价格:普通状态下不打折
    virtual double net_price(size_t n) const{return n*price;}
    // 通过virtual关键字,对析构函数进行动态绑定
    virtual ~Quote()=default; 
};
  • 基类有两种成员函数:基类希望派生类直接继承而不要改变的函数;基类希望派生类进行覆盖(override)的函数
    • 成员函数如果没有被声明为virtual function,则其解析过程发生在编译时而非运行时。(如isbn()函数,基类和派生类用的都是一样的版本)
    • 基类将希望派生类进行覆盖的函数定义为虚函数virtual (如:派生类的书要打折,所以它的价格计算要重新定义)。在函数前面加上virtual使得该函数执行动态绑定
      • 动态绑定(参考下文_调用_部分)
        • 使用基类引用或者指针调用一个虚成员函数时才会执行动态绑定
        • 通过动态绑定,可以用同一段代码分别处理基类和派生类对象
        • 函数的运行版本由实参决定,即在运行时选择函数的版本,所以动态绑定也称运行时绑定

          在使用存在继承关系的类型时,必须区分静态类型和动态类型。表达式的静态类型在编译时总是已知的;动态类型直到运行时才知道

定义派生类

class Bulk_quote:public Quote{
private:
//    继承了Quote的bookNo和price,并新增了两个新的数据成员min_qty和discount
    size_t min_qty=0; // 享受折扣的最低购买量
    double discount=0.0;//折扣
public:
    Bulk_quote()=default;//需要默认的构造函数
    //    带参数的构造函数
    Bulk_quote(string& book,double p,size_t qty,double disc);
    //子类覆盖基类的net_price函数,定义适用于自己总价的新版本
    double net_price(size_t cnt) const override;
};
//实现派生类的构造函数。并且,派生类必须用基类的构造函数初始化派生类的基类部分
Bulk_quote::Bulk_quote(string& book,double p,size_t qty,double disc): Quote(book,p),min_qty(qty),discount(disc) {}
//实现自己的net_price函数,并override基类的net_price函数
double Bulk_quote::net_price(size_t cnt) const {
    if (cnt>=min_qty)
        return cnt*(1-discount)*price; //    量很大,享受折扣价格
    else
        return cnt* price; //量不大,原价购买
}
  • 派生类向基类的类型转换
    • 因为在派生类对象中含有与其基类对应的组成部分,所以能把派生类的对象当做基类对象使用,也能将基类的指针或引用绑定到派生类对象的基类部分
  • 一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型返回类型必须与被它覆盖的基类函数完全一致(如 double net_price(size_t cnt) const
  • 只有虚函数才能被覆盖
// f1不是虚函数,这种是不可行的
class A{
    void f1();
};
class B:A{
    void f1() override;
};

派生类向基类转换
图片来自侯捷老师讲义_高级编程下_P49

  • c++新标准允许派生类显式地注明(标注关键字override)它使用某个成员函数覆盖了它继承的虚函数,如double net_price(size_t) const override;
  • 派生类必须用基类的构造函数初始化派生类的基类部分:Bulk_quote::Bulk_quote(string& book,double p,size_t qty,double disc): Quote(book,p),min_qty(qty),discount(disc) {}

调用

  • 通过(是否属于派生类向基类的类型转换),利用动态绑定,既能使用基类Quote的对象调用该函数(即:print_total函数),也能使用派生类对象调用调用它。因此,实际传入print_total的对象类型决定到底执行net_price的哪个版本
// 计算总价
void print_total(const Quote & item,size_t n){
    double ret=item.net_price(n);
    cout<<"ISBN: "<<item.isbn()<<", total due: "<<ret<<endl;
}

int main(){
//    实例化基类和派生类对象
    string bookNo("123456");
    Quote item(bookNo, 9.9); // 构造基类的对象。这本book的ISBN号为123456,单价为9.9
    Bulk_quote bulk(bookNo, 9.9,100,0.1); // 构造派生类的对象
    Quote &r=bulk; //r绑定到bulk的Quote部分

    // 如果调用基类,则计算基类的价格
    print_total(item,100); //9.9*100=990
    //如果调用派生类,则计算派生类的价格
    print_total(r,100);  // 9.9*(1-0.1)*100=891
}
//output:
//ISBN: 123456, total due: 990
//ISBN: 123456, total due: 891

完整代码实现:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class Quote{
private:
    string bookNo;
protected:
    double price=0;
public:
    Quote()=default;//需要默认的构造函数
//    带参数的构造函数
    Quote(const string & book,double sales_price):bookNo(book),price(sales_price){};
    // 通过isbn函数访问私有的bookNo
    string isbn()const{return bookNo;}
//    计算基类的总价格:普通状态下不打折
    virtual double net_price(size_t n) const{return n*price;}
    // 通过virtual关键字,对析构函数进行动态绑定
    virtual ~Quote()=default;
};

class Bulk_quote:public Quote{
private:
//    继承了Quote的bookNo和price,并新增了两个新的数据成员min_qty和discount
    size_t min_qty=0; // 享受折扣的最低购买量
    double discount=0.0;//折扣
public:
    Bulk_quote()=default;//需要默认的构造函数
    //    带参数的构造函数
    Bulk_quote(string& book,double p,size_t qty,double disc);
    //子类覆盖基类的net_price函数,定义适用于自己总价的新版本
    double net_price(size_t cnt) const override;
};
//实现派生类的构造函数。并且,派生类必须用基类的构造函数初始化派生类的基类部分
Bulk_quote::Bulk_quote(string& book,double p,size_t qty,double disc): Quote(book,p),min_qty(qty),discount(disc) {}
//实现自己的net_price函数,并override基类的net_price函数
double Bulk_quote::net_price(size_t cnt) const {
    if (cnt>=min_qty)
        return cnt*(1-discount)*price; //    量很大,享受折扣价格
    else
        return cnt* price; //量不大,原价购买
}

// 计算总价
void print_total(const Quote & item,size_t n){
    double ret=item.net_price(n);
    cout<<"ISBN: "<<item.isbn()<<", total due: "<<ret<<endl;
}

int main(){
//    实例化基类和派生类对象
    string bookNo("123456");
    Quote item(bookNo, 9.9); // 构造基类的对象。这本book的ISBN号为123456,单价为9.9
    Bulk_quote bulk(bookNo, 9.9,100,0.1); // 构造派生类的对象
    Quote &r=bulk; //r绑定到bulk的Quote部分

    // 如果调用基类,则计算基类的价格
    print_total(item,100); //9.9*100=990
    //如果调用派生类,则计算派生类的价格
    print_total(r,100);  // 9.9*(1-0.1)*100=891
}
//output:
//ISBN: 123456, total due: 990
//ISBN: 123456, total due: 891

派生类的构造函数,拷贝构造函数和拷贝赋值函数

复制对象时勿忘记每一个成分,实现完整拷贝 (effective C++, 条款12)

  • 派生类构造函数在初始化阶段不但要初始化派生类自己的成员,还负责初始化派生类对象的基类部分(如Bulk_quote::Bulk_quote(string& book,double p,size_t qty,double disc): Quote(book,p),min_qty(qty),discount(disc) {}
  • 派生类的拷贝、移动、赋值运算符也必须显式地为基类赋值

c++ primer P555

代码示例:

#include<iostream>
#include<string>

using namespace std;


class Traveler {
    string str;


public:
    Traveler(string s) : str(s) {}

    Traveler(const Traveler &t) {
        str = t.str;
    }

    Traveler &operator=(const Traveler &t) {
        str = t.str;
        return *this;
    }

    string showString() {
        return str;
    }


};// class Traveler


class Pager {
    string str;


public:
    Pager(string s) : str(s) {}

    Pager(const Pager &t) {
        str = t.str;
    }

    Pager &operator=(const Pager &t) {
        str = t.str;
        return *this;
    }

    string showString() {
        return str;
    }

};// class Pager




class BusinessTraveler : public Traveler {
    Pager p;
public:
    BusinessTraveler() : Traveler(""), p("") {}   //default constructor
    BusinessTraveler(string s, string r) : Traveler(s), p(r) {} // constructor
    BusinessTraveler(const BusinessTraveler &b) : Traveler(b), p(b.p) {} // copy-constructor
    BusinessTraveler operator=(const BusinessTraveler &b)         // operator assignment
    {
        Traveler::operator=(b);
        p = b.p;
        return *this;
    }

    friend ostream &operator<<(ostream &out, BusinessTraveler &b); //输出流重载


};


ostream &operator<<(ostream &out, BusinessTraveler &b) {
    cout << b.showString() << "  " << b.p.showString();
    return out;
}

int main() {
    BusinessTraveler b1;
    BusinessTraveler b2("Mike", "Pager1");
    BusinessTraveler b3("Jane", "Page2");
    BusinessTraveler b4(b2);
    BusinessTraveler b5 = b3;


    cout <<"b1: " <<b1 << endl;
    cout <<"b2: " << b2 << endl;
    cout <<"b3: " << b3 << endl;
    cout <<"b4: " << b4 << endl;
    cout <<"b5: " << b5 << endl;
}
//结果
//b1:
//b2: Mike  Pager1
//b3: Jane  Page2
//b4: Mike  Pager1
//b5: Jane  Page2

   转载规则


《基类派生类及动态绑定》 M 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
第四章:如何运用贪心思想玩跳跃游戏 第四章:如何运用贪心思想玩跳跃游戏
labuladong如何运用贪心思想玩跳跃游戏 有关动态规划的问题,大多是让你求最值的,比如最长子序列,最小编辑距离,最长公共子串等等等。 那么贪心算法作为特殊的动态规划也是一样,也一定是让你求个最值。 贪心问题往往可以通过动态规划来解决
2020-07-12
下一篇 
AI Model AI Model
Tools Linux 服务器上配置 Samba 以供 macOS 访问共享文件夹,附:如何在 Mac 上映射网络驱动器 当配置 Samba 服务器以供 macOS 访问共享文件夹时,以下是详细的步骤: 安装 Samba 服务器:在
2020-06-18
  目录