大厂题第1辑——虚函数七题精讲之3:纯虚函数

  1. 什么是纯虚函数?
  2. 什么是抽象类?
  3. 纯虚函数可以提供实现吗?有什么意义?

题6-什么是纯虚函数?

C++中有两种继承:接口继承和实现继承。二者的第一个区别是:基类都干了些什么?

  • 如果基类啥实事不干,只是提出了:目标、要求,即提出了 “希望有”的东西,而不真正实现,那么继承这个“不干实事”的基类的,就叫接口继承。
  • 反过来,如果基类好歹干了点事,提供了某些功能的实现(哪怕是个半成品),那么继承它,就叫实现继承。

放在现实生活中,“接口继承”大概类似于“继承遗志”,而“实现继承”相当于“继承遗产”。

  • 前者:一个父亲,自己没有读大学,但希望儿女们实现读清华北大的目标;一个父亲,自己一分存款没有,但希望自己女儿非富即贵,这样的父亲(基类)的儿女(派生类),自然只能“继承遗志”。
  • 后者:一个拥有两家上市企业,五十套一线城市房产,银行存款15个亿……他的儿女将来可以继承遗产。

如果某个基类,在某个功能上,一点都没有实现,但又希望派生类能实现,这个功能就可以使用一个只声明规格(只提要求),不提供任何实现的成员函数来表达,这个成员函数就叫 “纯虚函数 / pure virtual function”。

比如,有个“父亲”类,在发财这件事上的实现度完全是零(幸好不是负数),而在学识这件事上,实现了部分高中文化,那么这个类的设计就是:

class Father
{
public:
    // 财富
    virtual void Fortune() = 0; // 纯虚函数

    // 学识
    virtual void Learning() 
    {
         cout << "清华池高中肄业";
    }
};

成员函数 “Fortune()” 被标记上“virtual”,同时还标记为 “ = 0 ”,在C++当中,这就是表明它是一个“纯虚函数”。语义是:本基类完全不知道如何提供 “财富()” 的实现,派生类们,你们好好努力,一定要好好实现啊!

请大家对比一下 Fortune 和 Learning。

题7-什么是抽象类?

一个类(结构也一样,下同),只要拥有一个纯虚函数,这个类就是 “抽象类 (Abstract class)”。抽象类没办法创建出真实的对象,比如:

Father f; // 失败,因为 Father::Fortune() 是纯虚函数

如果一个类所有成员函数都是纯虚函数,那么这个类我们通常也称为“接口 / interface”。比如:

class Target
{
public:
     virtual void Fly() = 0; // 会飞
     virtual void Cry() = 0; // 会哭
     virtual void Say() = 0; // 会说
};

因此,所谓的“接口”,其实就是自己一点实事不干,却给未来的后代(派生类)们提了一堆要求的家伙……

以抽象类为基类的派生类怎么办呢?可以一个一个虚函数都实现,也可以挑选某些虚函数加以实现,另外的一些暂不实现,留给派生类的派生类(一代一代继承遗志啊!)。实现在不爽,也可以一个都不实现,然后还再添加几个新的纯虚函数……

因此,关于抽象类的更准确的定义是:一个类,不管是自己加的,还是是继承而来,只要拥有一个纯虚函数,就是抽象类。

题8-纯虚函数可以提供实现吗?有什么意义?

会问这个问题的考官,八成有些 “刁”……

没错,一个虚函数确实可以在被声明为 “ = 0 ” 的同时,又提供了具体实现,比如:

class Father
{
public:
    virtual void Fortune() = 0 ;
};

void Father::Fortune() // 正确,可行
{
   cout << "孩子,其实咱们家有前清留下的两亿银票";
}

注意,实现必须在类外,不能在声明纯虚成员函数时直接加上定义——我也不知道为什么,大概是那样写实现有点过份“造假”——刚说 “ = 0”,马上就有了实现?

class Father
{
public:
    virtual void Fortune() = 0 // 错误,不可行
    {
       cout << "孩子,其实咱们家有前清留下的两亿银票" ;
    }
};

为一个纯虚类提供实现,有什么意义和作用吗?

答:几乎没有意义,为纯虚函数提供定义,改不了当前类被视为抽象类的局面。当前类仍然无法实例化。不过,一个有定义(实现)的纯虚函数确实可以被调用。比如:

class Father
{
public:
    virtual void Fortune() = 0 ;
};

void Father::Fortune() // 正确,可行
{
   cout << "孩子,其实咱们家有前清留下的两亿银票";
}

class Son : public Father
{
    void Fortune() override
    {
         Father::Fortune(); // 调用基类的纯虚函数!!!
         cout << "\n真的吗?\n" << endl;
    }
};

如果没有提供纯虚函数 “Father::Fortune()” 的实际定义,以上在代码要么在编译时出错(本例),要么在运行时出错(更复杂的情况下)。出错位置就在注释为 “调用基类的纯虚函数!!!”那行。

在一个复杂的系统里,编译器有时候无法检测出代码调用了一个没有实现的纯虚函数,这就会造成运行期错误,以至程序意外退出。所以,为纯虚函数提供实现(哪怕是空实现,或者输出错误信息),可以有效躲开这种运行期错误——但是,这种做法事实上也隐藏了程序可能的设计错误或实现错误,所以它被视为是一种实用的,但不一定是好的做法。

为纯虚的析构函数提供默认的实现(通常就是空实现),倒是一个被写上书的好做法。具体我们在后续 “虚析构函数”时再做讲解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南郁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值