使用基类的引用或者指针调用一个虚成员函数时才会执行动态绑定
定义基类和派生类
定义基类
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;
};
- 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) {}
)- 派生类的拷贝、移动、赋值运算符也必须显式地为基类赋值
#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