第五章 面向对象编程风格
5.1 面向对象编程
继承:将一群相关的类组织起来,并让我们得以分享期间的共通数据和操作行为; 父类:基类; 子类:派生类。
多态:可以使我们操纵不同类时,如同操纵单一个体。 让基类的pointer 和 reference 得以十分透明的指向任何一个派生类的对象。
动态绑定:解析操作会延迟到运行时才进行。
静态绑定:程序执行之前就已经解析出应该调用哪一个函数。
虚函数:virtual 实现了多态的机制。基类定义虚函数,子类可以重写该函数;在派生类中对基类定义的虚函数进行重写时,需要在派生类中声明该方法为虚方法。
当子类重新定义了父类的虚函数后,当父类的指针指向子类对象的地址时,[即B b; A a = &b;] 父类指针根据赋给它的不同子类指针,动态的调用子类的该函数,而不是父类的函数,
且这样的函数调用发生在运行阶段,而不是发生在编译阶段,称为动态联编。而函数的重载可以认为是多态,只不过是静态的。
同时在函数构造时,会依次调用父类、子类、子子类的调用函数;析构的顺序则相反。 使用虚函数后,子类实现父类的虚函数后,调用该函数就会调用子类的函数而不是父类的函数
5.3 不带继承的多态
所有数列类型名称被放在一个名为ns_type的枚举类型中
class num_sequence{
public:
enum ns_type{
ns_unset,ns_fi,ns_pell,ns_lucas,ns_tri,ns_squ,ns_pen
};
}
nstype() 函数会检测整数参数是否返回某一有效数列。
class num_sequence{
public:
static ns_type nstype(int num)
{
return num <= 0 || num >= num_seq ? ns_unset : static_cat< ns_type > (num);
}
};
static_cat是个特殊转换记号,可将num转换为对应的ns_type
不带继承的多态的缺点:费工夫,且事后维护工程浩大
5.4 定义一个抽象基类
定义抽象类的第一个步骤:找出所有子类共通操作行为。就是定义基类的公有接口。
class num_sequence{
public:
int elem(int pos);
void gen_elems(int pos);
const char* what_am_i() const;
ostream& print(ostream &os = cout) count;
bool check_integrity(int pos);
static int max_elems();
}
第二步:找出哪些操作行为与类型相关,也就是说有哪些行为必须根据不同的派生类而有不同的实现方式。这些操作行为应该成为类继承体系中的虚函数。
class num_sequence{
public:
int elem(int pos);
void gen_elems(int pos); //产生数列元素
const char* what_am_i() const; //不确定是否有关 与类型
ostream& print(ostream &os = cout) count;
bool check_integrity(int pos); //检测是否有效
static int max_elems();
}
第三步:找出每个操作行为的访问层次。皆可访问:public 除基类外不需要用到:private 可以被派生类用到,但不允许一般程序使用
class num_sequence{
public:
virtual ~num_sequence(){};
virtual int elem(int pos)const = 0;
virtual const char* what_am_i() const = 0; //不确定是否有关 与类型
virtual ostream& print(ostream &os = cout) count =0;
static int max_elems() {retun _max_elems;};
protected:
virtual void gen_elems(int pos) const =0;
bool check_integrity(int pos); //检测是否有效
const static int _max_elems = 1024;
}
每个虚函数,要么得有定义,要么设置为纯虚函数。
任何类如果声明一个或多个纯虚函数,那么由于其接口的不完整性(纯虚函数没有函数定义),程序无法为其产生任何对象。这种类只能作为派生类的子对象使用,而且前提是这些派生类必须
为所有虚函数提供确切的定义。
为什么要定义纯虚函数?
纯虚函数是为你的程序制定一种标准,即只要你继承了我,就必须按照我的标准来,实现我所有的方法,否则你也是虚拟的。
根据规则,凡基类定义有一个或读多个虚函数,应该要将其destructor声明为virtual。
class num_sequence{
public:
virtual ~num_sequence();
};
5.5 定义一个派生类
派生类由两部分组成:一是基类构成的子对象,由基类的non-static data member组成;二是派生类的部分。
派生类的名称之后紧跟着冒号,关键字public,及基类的名称。
#include "num_sequence.h"
class Fibonacci : public num_sequence{
public:
// ....
}
Fibonacci必须为从基类继承而来的每个纯虚函数提供对应的实现,还必须声明专属的member。
派生类的虚函数必须精确吻合基类中的函数原型,在类之外对虚函数进行定义时,不必指明关键字virtual。
每当派生类有某个member与其基类的member同名,便会遮掩注基类的那份member。也就是说,派生类内对该名称的任何使用,都会被解析为该派生类自身的那份member,而非继承来的那份member。
该问题的产生是由于基类中该函数 被视为虚函数。
5.7 基类应该多么抽象
Data member如果是个reference,必须在constructor的member initialize list中加以初始化。一旦初始化,就再也无法指向另一个对象。如果data member是pointer,就可以在construction内加以初始化,
也可以将它初始化为null,稍后再令它指向某个有效的内存地址。
5.8 初始化、析构、复制
初始化操作可以留给每个派生类,但这么做会有潜在的危机。
较好的设计方式是,为基类提供constructor,并利用这个constructor处理基类所声明的所有data member的初始化操作。
5.9 在派生类中定义一个虚函数
如果我们继承了纯虚函数,那么这个派生类也会被视为抽象类,也就无法为它定义任何对象。
为什么要定义纯虚函数?
纯虚函数是为你的程序制定一种标准,即只要你继承了我,就必须按照我的标准来,实现我所有的方法,否则你也是虚拟的。
如果我们决定覆盖基类所提供的虚函数,那么派生类提供的新定义,其函数原型必须完全符合基类所声明的函数原型,包括:参数列表、返回类型、常量性(const-ness)。
“返回类型必须完全吻合”规则也有意外 – 当基类的虚函数返回某个基类形式时,
class num_sequence{
public:
//派生类的clone()可返回一个指针,
//指向num_sequence的任何一个派生类
virtual num_sequence *clone() =0;
//....
}
派生类中的同名函数便可以返回该基类所派生出来的类型
class Fibonacci : public num_sequence{
public:
//Fibonacci派生自num_sequence
//在派生类中,关键字virtual并非必要
Fibonacci *clone(){return new Fibonacci(*this);}
}
5.10 运行时的类型鉴定机制
怎么合理设计一份 what_am_i()函数,使其实现其功能?
1、在每个子类都拥有一份what_am_i()函数,都返回一个足以代表该类的字符串;
2、只在父类提供一份what_am_i()函数,令各派生类通过继承机制加以复用。
3、为num_sequence增加一个string member,并令每一个派生类的constructor都将自己的类名作为参数,传给num_sequence的constructor。
4、利用typeid运算符。运行时类型鉴定机制的一部分,由程序语言支持。
#include <typeinfo>
inline const char* num_sequence::what_am_i() const {return typeid(*this).name();}
ps -> gen_elems(64);
预期调用的是Fibonacci的gen_elems。但编译时却会发生错误。
为了调用Fibonacci所定义的gen_elems(),必须指示编译器,将ps的类型转换为Fibonacci指针。static_cast 运算符可以担起这项任务。
if(typeid (*ps) == typeid(Fibonacci))
{
Fibonacci *pf = static_cast<Fibonacci*> (ps); //无条件转换
pf->gen_elems(64);
}
static_cast 也有潜在危险,因为编译器无法确认我们所进行的转换操作是否完全正确。
dynamic_cast 运算符就可以提供有条件的转换:
if(Fibonacci *pf = dynamic_cast<Fibonacci*> (ps))
pf->gen_e;ems(64);