Enssential C++ 学习(五)面向对象编程风格

5.1 面向对象编程概念700

5.2 漫游:面向对象编程思维

  • 什么是虚函数

默认情形下,成员函数的解析是静态确定的,即在编译时就被静态地选择了。若要令其在运行时动态地进行,需要在声明前加关键字virtual,构成虚函数。

非虚成员函数是静态确定的,换句话说,该成员函数在编译时就会被静态地选择。然而,虚成员函数是动态确定的,换句话说,成员函数在运行时才被动态地选择,该选择基于对象的类型,而不是指向该对象的指针或引用的类型。这被称作“动态绑定”。对于虚函数这一特性有一个专用术语----晚绑定,运用虚函数这种方法叫做函数覆盖。

  • 虚函数有什么用

虚函数允许派生类取代基类所提供的实现。编译器确保当对象为派生类时,派生类的实现总是被调用,即使对象是使用基类指针访问而不是派生类的指针

  • 基类和派生类的构造函数和析构函数

当程序定义出一个派生对象,基类和派生类的构造函数都会被执行,当派生对象被销毁时,先执行派生类的析构函数,再执行基类的析构函数。

凡基类定义有一个(或多个)虚函数,应该将其析构函数声明为虚函数。

如果派生类有一个特殊的析构函数,并且我们也需要动态的删除基类的指针,那么这个基类的析构函数就应该是虚拟的。

虚拟调用是一种能够在给定信息不完全的情况下工作的机制。特别地,虚拟允许我们调用某个函数,对于这个函数,仅仅知道它的接口,而不知道具体的对象类型。但是要建立一个对象,你必须拥有完全的信息。特别地,你需要知道要建立的对象的具体类型。因此,对构造函数的调用不可能是虚拟的

  • 如何实现派生类

class 派生类名称 : 关键字public/protected/private 基类名称

class Book : public LibMat{}
  • 派生类的使用

使用派生类时,从基类继承而来的成员和自身定义的成员都可以直接使用。

5.2 不带继承的多态

我没技巧,我认输嗷

5.4 定义一个抽象基类

  1. 找出所有子类共通的操作行为(公有接口)
  2. 找出哪些操作行为必须根据不同的派生类而有不同的实现方式,这些操作行为应成为整个类继承体系中的虚函数(静态成员函数无法被声明为虚函数)
  3. 找出每个操作行为的访问层级

public:一般程序都能访问

private:只能在基类内使用

protected:可让派生类访问,一般程序不能访问

  • 纯虚函数

如果一个虚函数没有实质意义的话可以将其赋值为0,成为一个纯虚函数。任何类如果声明有一个(或多个)纯虚函数,程序无法为它产生任何对象,这种类只能作为派生类的子对象使用,而且派生类必须为所有虚函数提供确切的定义。

纯虚函数其实就是声明一个虚函数,在派生类中在定义它。也就是说纯虚函数只有函数的名字而不具备函数的功能,不能被调用。而在派生类中只有对此函数提供定义后,才能具备函数的功能,才能被调用。它仅仅是在基类中为其派生类保留一个函数的名字,以方便派生类根据需要对它定义。如果基类中没有保留函数名字,就无法实现多态。

一般情况下,纯虚函数是用来定义抽象基类的时候来使用的。所谓的抽象基类就是一种不用来定义对象,而只作为一种基本类型用作继承的抽象类,而常常用它来作为基类,所以叫抽象基类。凡是包含纯虚函数的类都是抽象类。因为纯虚函数时不能被调用的,包含纯虚函数的类是无法建立对象的。抽象类的作用是作为一个类族的共同基类,或者为一个类族提供一个公共接口。

5.5 定义一个派生类

  • 派生类的组成

  1. 基类构成的子对象:基类的non-static data member
  2. 派生类的non-static data member

要包含基类的头文件,进行继承声明前,其基类的定义必须已经存在。

必须为从基类继承而来的每个纯虚函数提供对应的实现。

必须声明派生类专属的member。

  • 基类与派生类的萃取

基类的protected member在派生类中同样也是protected,只能给后续的派生类使用,当前的派生类自己都不能用。

class scope运算符可以指明调用对象,使被调用的函数实例在编译时就被解析,跳过虚函数机制。

派生类内与基类同名的member会遮掩基类的member,如果要在派生类内使用继承来的member,要用class scope运算符限定。(在基类和派生类中使用同名的non-virtual函数不好,最好都声明为virtual,但还是根据自己的经验和技巧决定)。

5.6 运用继承体系

面向对象设计大大简化了修改和扩展的负担,继承机制妙!

5.7 基类应该多么抽象

这标题真的,笑笑,有被谢到。

  1. 抽象基类提供的是接口,并未提供任何实现
  2. 将所有派生类共有的实现内容剥离出来,移至基类内

Data member如果是个reference,必须在构造函数的成员初始化列表中加以初始化。一旦初始化,就再也无法指向另一个对象。(嗯?那你还有什么用??)如果data member是个pointer,就无此限制:可以在构造函数内加以初始化,也可以先将它初始化为null,稍后再令它指向某个有效的内存地址。

5.8 初始化、析构、复制

  • 初始化

基类的构造函数用来处理基类所声明的所有data member的初始化操作。如果是抽象基类,没有data member,将构造函数声明为protected。

派生类的构造函数不仅要初始化派生类的data member,还需要为其基类的data member提供适当的值。

派生类的初始化操作先调用基类的构造函数,再调用派生类的构造函数。如果未明确指定调用基类的哪个构造函数,编译器便会自动调用基类的默认构造函数。

  • 复制

定义派生类Fibonacci的copy constructor

Fibonacci::Fibonacci(const Fibonacci &rhs):num_sequence(rhs){}

rhs代表等号右边的派生类对象,它在成员初始化列表中被传给基类的复制构造函数。如果基类未自行定义复制构造函数,默认逐一成员初始化程序会执行。

  • 析构

基类的析构函数会在派生类的析构函数结束之后被自动调用,无须在派生类中做明确调用。

5.9 在派生类中定义一个虚函数

如果派生类继承了纯虚函数,那么这个派生类也会被视为抽象类,也就无法为它定义任何对象。

如果派生类要覆盖基类提供的虚函数,那么派生类为这个虚函数要提供新定义,函数原型必须完全符合基类所声明的函数原型,包括:参数列表、返回类型、常量性。

Exception:当基类的虚函数返回某个基类形式(通常是pointer或reference)时:

class num_sequence{
public:
    //派生类的clone()函数可以返回一个指针,指向num_sequence的任何一个派生类
    virtual num_sequence *clone()=0;
};

派生类中的同名函数可以返回该基类所派生出来的类型:

class Fibonacci : public num_sequence{
public:
    //在派生类中,关键字virtual并非必要
    Fibonacci *clone(){return new Fibonacci(*this);}
};

在派生类中为了覆盖基类的某个虚函数而进行声明操作时,不一定要加关键字virtual,编译器会根据两个函数的原型声明自行决定是否覆盖。

  • 虚函数的静态解析

两种情况下不会调用虚函数:

1.基类的构造函数和析构函数内

因为在构造派生类对象时要先调用基类的构造函数,如果在构造函数内调用派生类的那一份虚函数,有可能访问未经初始化的data member。

2.使用的是基类的对象,而非基类对象的pointer或reference时。

为基类声明一个实际对象的同时也就分配了对应的内存空间,如果稍后传入的确是一个派生类对象,那就没有足够的内存放置派生类的各个data member。

5.10 运行时的类型鉴定机制(Run-Time Type Identification RTTI)

  • typeid运算符

1. 支持查询多态化的class pointer或class reference,获得其所指对象的实际类型。

#include<typeinfo>
inline const char* num_sequence::what_am_i() const {return typeid(*this).name();}
返回至一个type_info对象,该对象的name()函数会返回一个const char*,用以表示类名,该对象关联至"what_am_i()函数之中由this指针所指对象"的实际类型

typeid运算符会返回一个存储了与类型相关的种种信息的type_info对象。

2. 支持==和!=比较操作,可以用来确定指针是否指向某个对象

num_sequence *ps = &fib;
if(typeid(*ps) == typeid(Fibonacci))
  • static_cast类型转换运算符

提供无条件转换

if(typeid(*ps) == typeid(Fibonacci))
{
    Fibonacci *pf = static_cast<Fibonacci*>(ps);
    pf->gen_elem(64);
}
  • dynamic_cast类型转换运算符

提供有条件转换,在运行时进行检验操作。

if(Fibonacci *pf = dynamic_cast<Fibonacci*>(ps))
    pf->gen_elems(64);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值