Menu
CoddyTech

抽象类

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

抽象类是指至少包含一个纯虚函数的类。由于它的接口不完整,你不能直接创建抽象类的对象,它只用于被继承。

class Shape {
public:
    virtual double area() = 0;      // 纯虚函数,使 Shape 成为抽象类
    virtual double perimeter() = 0; // 另一个纯虚函数
    
    void printInfo() {              // 普通方法,有具体实现
        std::cout << "Area: " << area() << std::endl;
    }
    
    virtual ~Shape() = default;
};

Shape s;  // 错误:不能实例化抽象类

抽象类可以同时包含纯虚函数(没有实现)、普通虚函数(带有默认实现)和非虚函数。这样,你就可以定义通用行为,同时要求派生类实现特定功能。

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double area() override { return 3.14159 * radius * radius; }
    double perimeter() override { return 2 * 3.14159 * radius; }
};

Circle c(5.0);      // OK:Circle 实现了所有纯虚函数
c.printInfo();      // 使用继承的 printInfo(),调用 Circle 的 area()

派生类必须实现所有纯虚函数,才能成为具体类(可实例化)。如果有任何函数未实现,它自身仍然是抽象类。这种机制可以确保类层次结构中的每个具体类都提供所需的功能。

challenge icon

挑战

简单

让我们构建一个媒体播放器系统,演示抽象类如何为不同媒体类型定义契约。你将创建一个抽象基类,规定每个媒体播放器都必须实现的功能,然后为音频和视频内容实现具体的播放器。

你需要将代码分为三个文件:

  • MediaPlayer.h:定义一个抽象的 MediaPlayer 类,作为所有媒体类型的蓝图。抽象类应包含:
    • 一个受保护的 std::string title 成员
    • 一个受保护的 int duration 成员(单位为秒)
    • 一个用于初始化这两个值的构造函数
    • 一个纯虚 play() 方法:每种媒体类型的播放方式都不同
    • 一个纯虚 getMediaType() 方法,返回 std::string
    • 一个常规(非虚)showInfo() 方法,用于打印:<title> (<duration>s) - Type: <mediaType>。此方法应调用 getMediaType() 获取媒体类型
    • 一个虚析构函数
  • Players.h:实现两个继承自 MediaPlayer 的具体媒体播放器:

    AudioPlayer:

    • 一个私有的 std::string artist 成员
    • 一个接收标题、时长和艺术家作为参数的构造函数
    • 实现 play(),打印:Playing audio: <title> by <artist>
    • 实现 getMediaType(),返回 "Audio"

    VideoPlayer:

    • 一个私有的 std::string resolution 成员
    • 一个接收标题、时长和分辨率作为参数的构造函数
    • 实现 play(),打印:Playing video: <title> in <resolution>
    • 实现 getMediaType(),返回 "Video"
  • main.cpp:读取 4 个输入(每个输入占一行):
    1. 音轨标题
    2. 音频时长(整数)
    3. 视频标题
    4. 视频时长(整数)

    创建一个艺术家为 "Unknown Artist" 的 AudioPlayer,以及一个分辨率为 "1080p" 的 VideoPlayer。将它们都存储在一个由 MediaPlayer* 指针组成的数组中。

    遍历数组,对每个播放器先调用 showInfo(),再调用 play()。在每个媒体项目之间打印一个空行。完成后,清理动态分配的对象。

例如,输入为 Summer Vibes、180、Nature Documentary 和 3600 时:

Summer Vibes (180s) - Type: Audio
Playing audio: Summer Vibes by Unknown Artist

Nature Documentary (3600s) - Type: Video
Playing video: Nature Documentary in 1080p

注意,showInfo() 方法只在抽象基类中定义一次,但它会调用纯虚方法 getMediaType()。这展示了抽象类如何将具体实现与必须由派生类提供的方法结合起来。记得在所有重写的方法上使用 override 关键字。

亲手试试

#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "Players.h"

int main() {
    // 读取输入
    std::string audioTitle;
    int audioDuration;
    std::string videoTitle;
    int videoDuration;
    
    std::getline(std::cin, audioTitle);
    std::cin >> audioDuration;
    std::cin.ignore();
    std::getline(std::cin, videoTitle);
    std::cin >> videoDuration;
    
    // TODO:创建一个艺术家为 "Unknown Artist" 的 AudioPlayer
    
    // TODO:创建一个分辨率为 "1080p" 的 VideoPlayer
    
    // TODO:将两者存入 MediaPlayer* 指针数组
    
    // TODO:遍历数组,对每个播放器执行以下操作:
    //       1. 调用 showInfo()
    //       2. 调用 play()
    //       3. 在每个媒体项之间输出一个空行
    
    // TODO:清理动态分配的对象
    
    return 0;
}
quiz icon自我测试

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

面向对象编程中的所有课

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