目录
15.1 OOP(object-oriented programming):概述
15.1 OOP(object-oriented programming):概述
本节要点
- 数据抽象:将类的接口和实现相分离。
- 继承:可以定义类似的类型并对其相似关系建模。
- 动态绑定:可以在一定程度上忽略相似类型的区别,从而以统一的方式使用它们的对象。
继承
基类(base class)→ 派生类(derived class)
基类将两种不同的函数区别对待:
-
类型相关的函数:基类希望他的派生类各自定义适合自己的版本,于是将这些函数声明为虚函数(virtual function)
-
不做改变直接继承的函数
class Quote
{
public:
std::string isbn() const;
//一个基类声明虚函数的实例
virtual double net_price(std::size_t n) const;
};
派生类必须在其内部对所有需要重新定义的虚函数进行声明(虚函数在派生类中不必再添加virtual关键字,允许派生类显式地注明将使用哪个成员函数改写基类的虚函数,即添加override关键字)
class Bulk_quote :public Quote
{
public:
double net_price(std::size_t n) const override //显式注明改写的虚函数;
};
动态绑定
当我们使用基类的引用&或指针调用一个虚函数时,将发生动态绑定,以允许我们可以通过同一段代码来同时实现处理不同类(基类和派生类)的对象。
double print(ostream& o, const Quote& item, size) //基类的引用
{
double ret = item.net_price(n);
//其他相关代码
}
- print函数的调用:print函数的形参是基类Quote的引用,但我们既可以使用基类Quote的对象来调用print函数,也可以使用派生类Bulk_quote的对象来调用该函数。
- net_price函数的调用:另外,由于net_price函数为虚函数,因此将执行动态绑定,通过传入基类引用&item的对象类型来判断item.net_price(n)函数调用的是基类的函数还是派生类的函数,即在运行时选择函数的版本。
15.2 定义基类和派生类
定义基类
- 基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此。
- 任何构造函数之外的非静态函数都可以声明为虚函数。
- virtual不能用于类外部函数的定义。
- 派生类虽从基类继承而来,但不能访问基类继承来的private成员,只能访问public成员和protected成员。
定义派生类
重申:派生类必须在其内部对所有需要重新定义的虚函数进行声明(虚函数在派生类中不必再添加virtual关键字,允许派生类显式地注明将使用哪个成员函数改写基类的虚函数,即添加override关键字)
如果派生类没有覆盖基类中的某个虚函数,那么派生类会直接继承它在基类中的版本。
由于派生类中含有与基类相同的组成部分,因此我们既能把派生类的对象当成基类对象来使用,又能把基类的引用(指针)绑定到派生类对象的基类部分上,这种转换称为派生类到基类的类型转换。
Quote item;
Bulk_quote bulk;
Quote* p = &item; //p指向Quote的对象item
p = &bulk; //p指向Bulk_quote的对象bulk的Quote部分
Quote& r = bulk; //r绑定到Bulk_quote的对象bulk的Quote部分
派生类构造函数
派生类不能直接初始化继承来的成员,也必须使用基类的构造函数来初始化他的基类部分。
因此,程序中任何能够生成派生类对象的语句,都要说明其包含的基类对象是如何初始化的,如果不做说明,则编译器认为基类对象要用无参构造函数初始化。
在执行一个派生类的构造函数之前,总是先执行基类的构造函数。
在初始化列表中,要指明调用基类构造函数的形式,具体写法:
#include <iostream>
#include <string>
using namespace std;
class CBug
{
int legNum, color;
public:
CBug(int ln, int c1) : legNum(ln), color(c1)
{
cout << "CBug Constructor called." << endl;
};
~CBug()
{
cout << "CBug Destructor called." << endl;
}
void Printlnfo()
{
cout << legNum << "," << color << endl;
}
};
class CFlyingBug : public CBug
{
int wingNum;
public:
/*重点在这里*/
CFlyingBug(int ln, int c1, int wn) : CBug(ln, c1), wingNum(wn) /*重点在这里*/
{
cout << "CFlyingBug Constructor called." << endl;
}
~CFlyingBug()
{
cout << "CFlyingBug Destructor called." << endl;
}
};
int main() {
CFlyingBug fb(2, 3, 4);
fb.Printlnfo();
return 0;
}
程序输出结果:
CBug Constructor called.
CFlyingBug Constructor called.
2,3(直接继承了void Printlnfo()函数,不是重新定义的虚函数,因此仍然是只打印2,3两个值,与4无关)
CFlyingBug Destructor called.
CBug Destructor called.
15.3 虚函数
所有的虚函数都必须有定义。
15.4 抽象基类
纯虚函数
将一个函数声明为纯虚函数(pure virtual function)意在表明当前这个函数是没有意义的,与虚函数不同的是,纯虚函数无需被定义。
只需要在函数体的位置(声明语句的分号之前)书写【=0】就可以将一个虚函数说明为纯虚函数。
注意:
- 【=0】只能出现在类内部的虚函数声明语句处。
- 允许为纯虚函数提供定义,但必须定义在类的外部。
抽象基类
含有纯虚函数的类称为抽象基类。
通过抽象基类定义接口,用其后续的派生类来覆盖这些接口。因此不能创建抽象基类的对象。