15-2 定义基类和派生类
目录
15.2.1 定义基类
类的定义
定义Quote类
class Quote{
public :
Quote() = default;
Quote(const string &book, double sales_price):
bookNo(book), price(sales_price) {}
string isbn() const {return bookNo;}
//返回给定数量的书籍的销售总额
//派生类负责编写并使用不同的折扣算法
virtual double net_price(int n) const{
return n*price;
}
virtual ~Quote() = default; //对析构函数进行动态绑定
private :
string bookNo; //书籍的ISBN号
protected :
double price = 0.0; //代表普通状态下不打折的价格
}
基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此。
成员函数与继承
从基类的角度说
- 类型无关的成员,直接定义
- 类型相关的成员,定义为虚函数
从派生类角度说
- 派生类会继承基类的成员
- 可以覆盖基类的虚函数
从使用者角度说
在用基类的指针或引用调用虚函数时会发生动态绑定
关于虚函数virtual
- 可以把构造函数之外的任何非静态函数声明为虚函数
- virtual只能出现在类内部的声明语句中
- 派生类如果没有覆盖虚函数,则会直接继承基类的版本
更多关于虚函数的讨论详见15.3
访问控制与继承
访问说明符 | 对类成员 | 对类用户 | 对派生类 |
---|---|---|---|
public | 可见 | 可见 | 可见 |
private | 可见 | 不可见 | 不可见 |
protected | 可见 | 不可见 | 可见 |
15.2.2 定义派生类
类的定义
class Bulk_quote : public Quote{ //Bulk_quote继承自Quote
public :
Bulk_quote() = default;
Bulk_quote(const stirng&, double, int, double);
//覆盖基类的函数版本来实现大量购买的折扣政策
double net_price(int ) const override;
private :
int min_qty = 0; //使用折扣政策的最低购买量
double discount; = 0.0; //以小数表示的折扣额
}
- Bulk_quote是Quote的派生类
- Bulk_quote继承了Quote中的成员如isbn\bookNo\price等
- Bulk_quote重定义(覆盖)虚函数net_price
- Bulk_quote新增了两个数据成员
派生类向基类的类型转换
一个派生类对象包含多个组成部分:
-
一个含有派生类自己定义的(非静态)成员的子对象
-
一个与该派生类继承的基类对应的子对象
如果有多个基类,那么这样的子对象也有多个
因为在派生类对象中含有与其基类对应的组成部分
- 能把派生类的对象当成基类对象来使用
- 能将基类的指针或引用绑定到派生类对象中的基类部分上。
Quote item; //基类对象
Bulk_quote bulk; //派生类对象
Quote *p = &item; //p指向基类对象
p = &bulk; //p指向bulk的Quote部分
Quote &r = bulk; //r绑定到bulk的Quote部分
这种转换称之为派生类向基类的类型转换
在派生类对象中含有与其积累对应的组成部分,这是继承的关键所在。
派生类构造函数
每个类控制自己的成员初始化过程(接口与类耦合)
派生类无法直接初始化从基类继承的成员,必须使用基类的构造函数来初始化它的基类部分
//实现Bulk_quote的构造函数
Bulk_quote(const string &book, double p,
int qty, double disc) :
Quote(book,p), min_qty(qty), discount(disc){}
派生类的初始化过程:
-
初始化基类部分:委托基类的构造函数
没有显式调用基类的构造函数则基类部分进行默认初始化,此时如果基类没有默认构造函数就会报错
-
初始化派生类自己成员
如果继承有好几个层级:派生类只初始化它的直接基类,详见15.4
继承与静态成员
如果基类定义了一个静态成员
- 整个继承体系中只会由static成员的唯一定义和唯一实例
- static成员遵循通用的访问控制规则
防止继承的发生
final
防止继承
class Z final {/**/}; //Z不能作为基类
class A : public Z {/**/}; //错误:Z是final的
15.2.3 类型转换与继承
静态类型与动态类型
- 静态类型(static type):编译时已知
- 动态类型(dynamic type):运行时才可知
当基类的指针或引用调用虚函数时,会发生动态绑定,此时基类对象的静态类型和动态类型不一致【引用对象的静态类型是基类,动态类型可能是派生类】。
其他情况下,静态类型与动态类型一定一致
派生类给基类赋值
当我们用一个派生类对象为一个基类对象初始化或赋值时,只有该派生类对象中的基类部分会被拷贝、移动或赋值,它的派生类部分将被忽略掉。