Menu
CoddyTech

再探虚函数

本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 58 课,共 104 课。

现在我们已经了解了编译时多态和运行时多态的区别,接下来深入了解虚函数,以及让它们使用起来更安全的 override 关键字。

在基类中将函数标记为 virtual 后,派生类可以提供自己的实现。override说明符会明确告诉编译器,你打算重写一个虚函数:

class Animal {
public:
    virtual void speak() {
        std::cout << "Some sound" << std::endl;
    }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

使用 override 很重要,因为它能在编译时捕获错误。如果你不小心拼错函数名或使用了错误的参数,编译器会提醒你,而不是悄悄创建一个新函数:

class Cat : public Animal {
public:
    void speek() override {  // 编译错误:没有可重写的函数
        std::cout << "Meow!" << std::endl;
    }
};

final说明符可以阻止进一步重写。当派生类应当是最后一个重写特定函数的类时,就使用它:

class Bulldog : public Dog {
public:
    void speak() override final {
        std::cout << "Gruff woof!" << std::endl;
    }
};

class TinyBulldog : public Bulldog {
    void speak() override {}  // 错误:无法重写 final 函数
};

重写虚函数时,始终使用 override。它能说明你的意图,并让编译器验证你是否确实重写了现有的虚函数。

challenge icon

挑战

简单

我们来构建一个通知系统,展示虚函数中 override 和 final 关键字的作用。你将创建一个通知处理器层次结构,其中一些方法可以进一步自定义,另一些方法则会被锁定,无法修改。

你将把代码分成三个文件:

  • Notifier.h:定义一个基类 Notifier,用于表示任意通知发送器:
    • 一个受保护的 std::string recipient 成员
    • 一个接收并保存收件人姓名的构造函数
    • 一个虚方法 send(const std::string& message),输出:Notifying <recipient>: <message>
    • 一个虚方法 getType(),返回字符串 "Generic"
    • 一个虚析构函数
  • EmailNotifier.h:定义一个继承自 Notifier 的 EmailNotifier 类:
    • 一个私有的 std::string domain 成员
    • 一个接收收件人和域名,并将收件人传给基类的构造函数
    • 重写 send(),输出:Emailing <recipient>@<domain>: <message>
    • 重写 getType() 并将其标记为 final。它应返回 "Email"

    然后定义一个继承自 EmailNotifier 的 UrgentEmailNotifier 类:

    • 一个接收收件人和域名,并将两者传给 EmailNotifier 的构造函数
    • 重写 send(),输出:[URGENT] Emailing <recipient>@<domain>: <message>
    • 注意:这里不能重写 getType(),因为它在 EmailNotifier 中被标记为 final
  • main.cpp:读取三个输入(每个输入各占一行):
    1. 收件人姓名
    2. 电子邮件域名
    3. 消息文本

    动态创建三个通知器对象:一个基类 Notifier、一个 EmailNotifier 和一个 UrgentEmailNotifier,它们都使用相同的收件人(适用时也使用相同的域名)。将它们存储在一个由 Notifier* 指针组成的数组中。

    遍历数组,对每个通知器使用 getType() 输出其类型,然后调用 send() 发送你的消息。每项按以下格式输出:

    Type: <type>
    <send output>

    在每个通知器的输出之间打印一个空行。完成后,清理动态分配的对象。

例如,输入 Alice、company.com 和 Meeting at 3pm 时:

Type: Generic
Notifying Alice: Meeting at 3pm

Type: Email
Emailing Alice@company.com: Meeting at 3pm

Type: Email
[URGENT] Emailing Alice@company.com: Meeting at 3pm

注意,UrgentEmailNotifier 可以重写 send() 来自定义消息格式,但它会从 EmailNotifier 继承 "Email" 类型,因为 getType() 被标记为 final。对所有重写的方法使用 override 关键字,以便在编译时发现签名不匹配的问题。

亲手试试

#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"

using namespace std;

int main() {
    // 读取输入
    string recipient;
    string domain;
    string message;
    
    getline(cin, recipient);
    getline(cin, domain);
    getline(cin, message);
    
    // TODO: 创建一个包含 3 个 Notifier* 指针的数组
    
    // TODO: 动态创建:
    // 用接收者创建一个基础 Notifier 对象
    // 用接收者和域名创建一个 EmailNotifier 对象
    // 用接收者和域名创建一个 UrgentEmailNotifier 对象
    
    // TODO: 遍历数组,对每个通知器:
    // 打印 "Type: ",然后打印 getType() 的结果
    // 用消息调用 send()
    // 在通知器之间打印一个空行(最后一个之后不要打印)
    
    // TODO: 清理动态分配的对象
    
    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器