C++ 面向对象程序设计[1]
概论
派生类必须在其内部对所有重新定义的虚函数进行声明。C++11标准允许派生类显示地注明它将使用哪个成员函数改写基类的虚函数,具体做法是在该函数的形参后面增加一个override
关键字。
class Quote {
public:
std::string isbn() const;
virtual double net_price(std::size_t n) const;
};
class Bulk_quote : public Quote {
public:
double net_price(std::size_t n) const override;
};
动态绑定
函数的运行版本由实参对象的类型决定,即在运行时选择函数的版本,所以动态绑定有时又被称为运行时绑定。
在C++中,当我们使用基类的引用(或者指针)调用一个虚函数时,将会发生动态绑定。
double print_total(std::ostream &os, const Quote &item, std::size_t n) {
doubel ret = item.net_price(n);
os << "ISBN: " << item.isbn()
<< " # sold: " << n << " total due: " << ret << std::endl;
return ret;
}
print_total
中的net_price
函数由基类的引用item
调用,而net_price
函数是一个虚函数,因此到底调用哪个派生类的net_price
由实际运行时传递的实参类型决定。
定义基类和派生类
定义基类
class Quote {
public:
Quote() = default;
Quote(const std::string &book, double sales_price)
: bookNo(book), price(sales_price) {}
std::string isbn() const { return bookNo; }
virtual double net_price(std::size_t n) const { return n * price; }
virtual ~Quote() = default;
private:
std::string bookNo;
protected:
double price = 0.0;
};
作为继承关系中根节点的类通常都会定义一个虚析构函数。
成员函数与继承
基类中的两种成员函数:
- 基类希望其派生类进行覆盖的函数;
- 基类希望派生类直接继承而不要改变的函数。
通常将前者定义为虚函数。任何构造函数之外的非静态函数都可以是虚函数。
访问控制与继承
派生类只能访问基类中的public
成员,而不能访问private
成员。若基类希望其派生类有权访问某成员,同时禁止其他用户访问,那么就在基类中使用protected
关键字来修饰该成员。
定义派生类
class Bulk_quote : public Quote {
public:
Bulk_quote() = default;
Bulk_quote(const std::string &, double, std::size_t, double);
double net_price(std::size_t n) const override;
private:
std::size_t min_qty = 0;
double discount = 0.0;
};
共有派生,则基类的公有成员也是派生类的公共接口的组成部分。此外,共有派生类型的对象可以被绑定到指向基类的指针或者引用上。
派生类中的虚函数
如果派生类没有对基类中的虚函数进行override,派生类会直接继承其在基类中的版本。
派生类对象及派生类向基类的类型转换
派生类对象中会包含该派生类继承的基类的子对象。Bulk_quote
对象会包含四个数据元素:从Quote
中继承来的bookNo
以及price
,Bulk_quote
自己定义的min_qty
和discount
派生类构造函数
派生类使用基类的构造函数来构造其所具有的基类子对象。
Bulk_quote(const std::string& book, double p,
std::size_t qty, double disc) :
Quote(book, p), min_qty(qty), discount(disc){}
派生类构造函数通过构造函数初始化列表来将实参传递给基类构造函数。
除非特别说明,否则派生类对象的基类部分会像数据成员一样执行默认初始化。如果要使用基类的非默认构造函数,需要以类名加圆括号的实参列表的形式来调用。
派生类使用基类的成员
继承与静态成员
如果基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义。如果某静态成员在继承类中可以访问,那么我们既可以通过基类使用它,也可以通过派生类使用它。
派生类的声明
被用作基类的类
基类本身必须已经得到定义,而非只是声明。
防止继承的发生
在类后跟关键字final
,表明该类不能被用作基类。
类型转换与继承
可以用基类的指针或者引用指向派生类的对象。如果表达式既不是引用也不是指针,那么其动态类型与静态类型始终一致。
静态类型与动态类型
表达式的静态类型在编译时已知,它是变量声明时的类型或表达式生成的类型;动态类型则是变量或表达式表示的内存中的对象的类型。
不存在从基类向派生类的隐式类型转换
在对象之间不存在类型转换
派生类向基类的自动类型转换只对指针或引用类型有效,在派生类和基类类型之间不存在这种变换。