当前位置: 首页 > news >正文

我们在开发时,什么时候用到虚函数和纯虚函数?

在曾经学习面向对象的概念上,对虚函数和纯虚函数的区别,我们都会止于这样的理解层面:虚函数是用于被子类可继承可重写的函数,而纯虚函数是子类继承后就必须重写的函数。但是在开发工作上,却有很多开发者是没法彻底参透到其中的代码设计艺术,于是会把代码写得一塌糊涂。

面向对象其实是个很好的设计理念,如果能够掌握好如何使用它,那么我们就会把开发的事情做得游刃有余。

以下我稍微讲讲它们该如何使用的恰当,才能展现出代码的扩展性和提高代码质量。

举个简单的例子(这个例子可能不是很恰当,但我不想花太多心思去选择合适的例子,暂且就这样吧),我要实现一个打印文件的功能。

功能需求是这样的:

1、同时支持打印不同格式的文件内容,如 PDF、Excel.
2、打印 PDF 分为两种打印方式:PDF 文件 和 打印机打印纸质文件。
3、PDF 可选是否打印彩色内容。

纯虚函数的使用方式

纯虚函数的特点:

  • 基类定义的纯虚函数,是子类继承后必须要实现的函数。
  • 至少存在一个纯虚函数的基类,一定是一个抽象类,不能直接实例化。

实现一个通用的打印类:


// 打印基类抽象
class Printer
{
public:
    Printer() { }
    ~Printer() {}

    virtual void Print(std::string filePath) = 0;
    virtual void Read(std::string filePath) = 0;

};

// PDF 打印类
class PDFPrinter : public Printer
{
public:
    explicit PDFPrinter():Printer() {}
    ~PDFPrinter() {}


    void Print(std::string filePath) override
    {
        std::cout << "print a PDF.";
    }

    void Read(std::string filePath) override
    {
        std::cout << "Read content from a PDF file.";
    }

};

// Excel 打印类
class ExcelPrinter : public Printer
{
public:
    explicit ExcelPrinter():Printer() {}
    ~ExcelPrinter() {}


    void Print(std::string filePath) override
    {
        std::cout << "print a Excel.";
    }

    void Read(std::string filePath) override
    {
        std::cout << "Read content from a Excel file.";
    }

};

// 简单工厂模式
class PrinterFactory
{
public:
    static bool CreatePrinter(std::string tag)
    {
        Printer* printer = nullptr;
        if (tag == "PDF")
        {
            printer = new PDFPrinter();
        }
        else if(tag == "Excel")
        {
            printer = new ExcelPrinter();
        }

        if (printer != nullptr)
        {
            printerVector[tag] = printer;
            return true;
        }

        return false;

        
    }

    static Printer* getPrinter(std::string tag)
    {
       return printerVector[tag];
    }

private:
    static std::map<std::string, Printer*> printerVector;
};
std::map<std::string, Printer*> PrinterFactory::printerVector = { };

int main()
{
    // 在外部创建时
    PrinterFactory::CreatePrinter("PDF");
    PrinterFactory::CreatePrinter("Excel");
    // 在外部引用时
    Printer* pdfPrinter = PrinterFactory::getPrinter("PDF"); 
    pdfPrinter->Print("xxxxfilePath");
    // 代码的低耦合,可以灵活调整软件需求。
    //如果需求改为我只想要打印 Excel 功能,只需要改为 getPrinter("Excel"); 就可以了。 

}

从上面例子看出,纯虚函数的作用如下:

  • 纯虚函数一般适用在子类都有共同的功能,PDF 和 Excel 打印类都有打印和读取的功能 。但是各自相同功能的实现逻辑不相同。
  • 在外部使用时,统一使用抽象接口来处理这些行为函数,以提高代码变动的灵活性和扩展性。

虚函数的使用方式

虚函数的特点:

  • 基类定义的虚函数,是子类可继承也可重写的行为函数。
  • 不存在纯虚函数的基类,可以被实例化。

在打印功能扩展一些操作:

// PDF 基类,PDF 内容排版
class PDFBase
{
public:
    PDFBase() { }
    ~PDFBase() {}

    virtual void SetIsColor(bool isColor = false) {
        std::cout << "default Color:" << isColor; 
    }

    virtual void SetPrintType(std::string type) {
        std::cout << "A standard pdf format."; 
    }
};


// PDFBase 被 PDFPrinter 继承
class PDFPrinter : public Printer, public PDFBase
{
public:
    explicit PDFPrinter():Printer() {}
    ~PDFPrinter() {}

    void Print(std::string filePath) override
    {
        std::cout << "print a PDF.";
    }

    void Read(std::string filePath) override
    {
        std::cout << "Read content from a PDF file.";
    }

    void SetPrintType(std::string type) 
    { 
        std::cout << "print type:"<< type; 
    }
     
};

int main()
{
    // 在外部创建时
    PrinterFactory::CreatePrinter("PDF");
    // 在外部引用时
    Printer* pdfPrinter = PrinterFactory::getPrinter("PDF");
    PDFBase* pdfBase = dynamic_cast<PDFBase*>(pdfPrinter);
    pdfBase->SetPrintType("打印机");
    // 但是如果将 getPrinter("PDF") 改为Excel 类型,则转换 pdfBase 就会为 NUll。
    // 所以在转换类型时,要养成先判断实例是否为空的习惯。
    // 如果仅仅为了展示 PDF 内容样式,如编辑 PDF 内容样式后进行存储参数值,
    //但是不进行打印操作,则可以直接实例化 PDFBase。
     PDFBase* pdfBaseVar = new PDFBase();
     pdfBaseVar->SetIsColor(true);
     SaveConfig(pdfBaseVar); // 保存在本地里,以便下次打印操作时需要;
     
}

从上面例子看出,虚函数的作用如下:

  • 虚函数一般适用在子类只继承不需要重写的函数,如 SetIsColor。
  • 虚函数也一般适用于某些子类有扩展的特性,如 pdf 有虚拟打印和打印机打印,而 Excel 却只支持虚拟打印出文件。
  • 在外部使用时,对于局部区域的专注使用较为便利。如 PDF 打印时弹出打印内容排版选项,只使用 pdfBase 来专门处理内容的,而避免了 PDFPrinter 错误调用 read 等操作的影响。

睡前随便写写,写得可能有点乱,但应该不难理解,将就着看吧。

相关文章:

  • 修复ubuntu下找不到音频设备的问题
  • Docker开发工具安装大合集
  • 如何撰写专业技术书籍的序言?——完整指南
  • FreeRTOS概述
  • SpringBoot 接入 豆包 火山方舟大模型
  • 共享内存的原理和创建
  • #函数探幽
  • 《深度解析DeepSeek-M8:量子经典融合,重塑计算能效格局》
  • Zemax 中的 CAD 文件性能比较
  • LeetCode hot 100—爬楼梯
  • Android 线程池实战指南:高效管理多线程任务
  • leetcode日记(91)二叉树的最大深度
  • E1-106.租车骑绿道(贪心)
  • PDF文件中的颜色是什么原理?
  • 拉取gitlab项目时出现500的错误的权限问题
  • vue3通过render函数实现一个菜单下拉框
  • PIMPL模式
  • 深入理解 GPU 渲染加速与合成层(Composite Layers)
  • 008-生成不重复9位随机数
  • C++ 链表List使用与实现:拷贝交换与高效迭代器细致讲解
  • 四川资阳市原市长王善平被双开,“笃信风水,大搞迷信活动”
  • 全国人大常委会启动食品安全法执法检查
  • 援藏博士张兴堂已任西藏农牧学院党委书记、副校长
  • 第1现场 | 印巴冲突:印50多年来首次举行大规模民防演习
  • 外交部:印巴都表示不希望局势升级,望双方都能保持冷静克制
  • 李云泽:房地产“白名单”贷款审批通过金额增至6.7万亿元