【C++进阶学习】第四弹——多态——迈向C++更深处的关键一步

CSDN 2024-07-17 11:05:31 阅读 83

前言:

在前面我们已经学习了C++中继承的相关知识,已经体会到C++在与C语言的对比中的便捷性,但是有一些问题并没有被解决,比如继承中如何使不同的派生类公用基类的一个函数,这就需要多态的知识,而且,有一个很重要的点要知道,多态是以后找工作的时候经常经常被问到的一个知识

目录

一、多态的概念

二、多态的实现

2.1 多态的构成条件

2.2 override 和 final 关键字(C++11)

2.3 重载、覆盖(重写)、隐藏(重定义)的对比

三、抽象类

四、总结


一、多态的概念

C++中多态的概念通俗来讲就是多种形态,同样的东西,在不同场景下发挥着不同的作用;体现在代码上其实就是同一个虚函数,在不同的派生类中可能发挥着不同的作用,就比如一把雨伞既可以用来挡雨,也可以用来遮阳

二、多态的实现

2.1 多态的构成条件

在C++中,要实现多态,需要满足以下几个条件:

1、基类中必须包含至少一个虚函数 虚函数是在基类中声明的,并在派生类中重写的函数。通过在函数声明前加上virtual关键字来声明虚函数。虚函数是实现动态多态的关键,因为它允许在运行时根据对象的实际类型来调用相应的函数。

2、通过基类指针或引用调用虚函数: 多态通常通过基类的指针或引用来实现。当使用基类指针或引用指向派生类对象时,调用虚函数将根据对象的实际类型(而不是指针或引用的类型)来决定调用哪个函数。

3、派生类必须重写(override)基类的虚函数: 派生类需要重写基类中的虚函数,以提供特定于派生类的实现。重写时,函数签名(包括返回类型、函数名和参数列表)必须与基类中的虚函数完全匹配。在C++11及以后的版本中,可以使用override关键字显式声明派生类中的函数是重写基类的虚函数,这有助于编译器检查是否正确重写了虚函数。

4、使用虚析构函数: 如果基类中使用了虚函数,通常建议也将析构函数声明为虚函数。这是因为当通过基类指针删除派生类对象时,如果析构函数不是虚函数,将只会调用基类的析构函数,而不会调用派生类的析构函数,这可能导致资源泄漏。

下面是一个简单的示例,展示了多态的构成条件:

<code>#include <iostream>

class Base {

public:

    virtual void show() {  // 虚函数

        std::cout << "Base show()" << std::endl;

    }

    virtual ~Base() {}  // 虚析构函数

};

class Derived : public Base {

public:

    void show() override {  // 重写基类的虚函数

        std::cout << "Derived show()" << std::endl;

    }

};

int main() {

    Base* ptr = new Derived();  // 基类指针指向派生类对象

    ptr->show();  // 调用派生类的show(),显示 "Derived show()"

    delete ptr;  // 正确调用析构函数

    return 0;

}

在这个例子中,Base类有一个虚函数show()和一个虚析构函数。Derived类重写了show()函数。在main()函数中,通过基类指针ptr调用show()函数,实际执行的是Derived类的show()函数,展示了动态多态的效果。同时,删除ptr时,会正确调用DerivedBase的析构函数。

2.2 override final 关键字(C++11)

通过上面的限制条件,我们已经可以看出要想构成多态还是不容易的,在我们平时写多态的时候,经常可能因为某些小差错而导致构建失败,而这种编译错误是不会指出来的,所以在C++11中提供了这两个关键字来帮助我们更容易的实现多态

override 关键字

override 关键字用于在派生类中明确地指示一个成员函数是重写了基类中的虚函数。这样做可以增加代码的可读性,并且能够帮助编译器检测错误,比如当试图重写一个基类中并不存在的虚函数时。

用法示例:

class Base {

public:

    virtual void display() const {

        // 基类实现

    }

};

class Derived : public Base {

public:

    void display() const override { // 明确这是一个重写的函数

        // 派生类实现

    }

};

如果不使用 override,编译器仍然可以正确地识别出重写的函数,但使用 override 可以让意图更加明确,并且能够检测出一些错误。

final 关键字

final 关键字用于阻止类被进一步继承,或者阻止虚函数被进一步重写。

当 final 用于类时,表示该类不能被继承。

当 final 用于虚函数时,表示该函数不能被派生类重写。

用法示例:

class Base {

public:

    virtual void display() const final {

        // 基类实现

    }

};

class Derived : public Base {

    // 下面的重写会失败,因为基类的 display 函数被标记为 final

    // void display() const override {

    //     // 派生类实现

    // }

};

class FinalClass final { // 这个类不能被继承

    // ...

};

// 下面的继承会失败,因为 FinalClass 被标记为 final

// class DerivedFromFinal : public FinalClass {

//     // ...

// }

使用 final 可以确保类的接口不会被意外地改变,这对于维护代码的稳定性和可预测性非常有帮助。

总结

override final C++11中用于控制虚函数行为的两个关键字。override 用于指示派生类中的成员函数是重写了基类的虚函数,而 final 用于阻止类被继承或虚函数被重写。这两个关键字都有助于提高代码的清晰性和安全性。

2.3 重载、覆盖(重写)、隐藏(重定义)的对比

三、抽象类

在 C++ 中,抽象类是一个不能直接实例化的类,它主要用于定义一组接口,要求其子类必须实现这些接口。

抽象类通常包含以下特点:

1、纯虚函数抽象类中至少有一个或多个函数声明为 virtual 并且没有实现(即函数体为空,没有 = 0 后面的函数体),这些函数被称为纯虚函数(Pure Virtual Function)。例如:

<code>class MyAbstractClass {

public:

    virtual void abstractMethod() = 0; // 纯虚函数

};

2、继承:子类可以继承抽象类,但是不能直接实例化抽象类。子类必须实现抽象类中所有纯虚函数,否则子类也将成为抽象类。例如:

class DerivedClass : public MyAbstractClass {

public:

    void abstractMethod() override; // 必须实现抽象方法

};

3、设计目的:抽象类通常用于为一组相关的类提供一个共同的行为框架,或者作为接口的定义,避免代码重复。

4、使用场景:抽象类常用于模式设计,如工厂模式、策略模式等,以及多态和模板编程中。

当你试图创建一个抽象类的对象时,编译器会报错,因为不能创建抽象类的实例。抽象类只有在将其中的纯虚函数重写之后才能实例化对象。抽象类主要用于定义接口,实际的业务逻辑通常由继承它的具体子类来实现。

四、总结

以上就是C++中多态的基本知识,总的来说并不是很难,但我们还没有将多态底层的原理——虚函数表的问题,这个与前面继承那个类似,都是很重要的知识点,这些我们放在后面统一讲解。

感谢各位大佬观看,创作不易,还请各位大佬点赞支持!!!



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。