再探虚函数
本课属于 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。它能说明你的意图,并让编译器验证你是否确实重写了现有的虚函数。
挑战
简单我们来构建一个通知系统,展示虚函数中 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:读取三个输入(每个输入各占一行):- 收件人姓名
- 电子邮件域名
- 消息文本
动态创建三个通知器对象:一个基类
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;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器