Day 18 C++ 多态

C++ 多态

  • 什么是多态
  • 示例
  • 多态分为两类
      • 静态多态
      • 动态多态
      • 静态多态和动态多态区别
  • 虚函数
      • 定义
      • 一般形式
      • 注意
      • 纯虚函数
          • 定义
          • 语法
          • 示例
      • 虚析构和纯虚析构
          • 虚析构和纯虚析构共性
          • 虚析构和纯虚析构区别
          • 示例
          • 总结
  • 抽象类
      • 定义
      • 特点
      • 示例

什么是多态

多态(polymorphism)是面向对象编程中的一个重要概念,指的是同一类型的对象在不同的情况下表现出不同的行为。允许在基类和派生类之间进行多态操作。多态性允许使用基类指针或引用来调用派生类对象的成员函数,以实现不同类型对象的统一操作。通过多态,可以实现代码的灵活性、扩展性和可维护性。多态是C++面向对象三大特性之一

多态性的实现主要依赖于虚函数和继承。在基类中,通过将函数声明为虚函数,即使用virtual关键字,可以为其子类提供一个公共接口。然后,在派生类中,可以重写这些虚函数并提供特定于派生类的实现。

示例

#include 

class Shape {
public:
    virtual void draw() {
        std::cout << "Drawing a shape." << std::endl;
    }
};

class Circle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a circle." << std::endl;
    }
};

class Square : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a square." << std::endl;
    }
};

int main() {
    Shape* shapePtr;

    Circle circle;
    Square square;

    // 使用基类指针调用派生类的函数
    shapePtr = &circle;
    shapePtr->draw();  // 输出 "Drawing a circle."

    shapePtr = &square;
    shapePtr->draw();  // 输出 "Drawing a square."

    return 0;
}

在上述示例中,Shape类是基类,Circle和Square类是它的派生类。基类中的draw()函数被声明为虚函数,并在派生类中重写。在主函数中,通过使用基类指针shapePtr分别指向Circle和Square对象,并通过调用draw()函数实现多态性。

通过运行程序,我们可以看到虽然使用了相同的函数调用语句shapePtr->draw(),但根据所指向的具体对象类型,输出的结果是不同的。这就是C++中多态性的实现方式。

多态分为两类

静态多态

函数重载 和 运算符重载属于静态多态(复用函数名)

静态多态性发生在编译时,通过函数重载和运算符重载来实现。编译器在编译阶段就确定了函数的具体地址,因此函数调用是早绑定的。在静态多态性中,根据函数参数的类型和数量来选择执行哪个函数。

动态多态

派生类和虚函数实现运行时多态

动态多态性发生在运行时,通过派生类和虚函数来实现。虚函数允许基类指针或引用调用派生类对象的成员函数,并且在运行时根据实际对象的类型来确定函数的具体地址,所以函数调用是晚绑定的。在动态多态性中,根据对象的实际类型来选择执行哪个函数。

静态多态和动态多态区别

  • 静态多态的函数地址绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址绑定 - 运行阶段确定函数地址

我们希望传入什么对象,那么就调用什么对象的函数 如果函数地址在编译阶段就能确定,那么静态联编
(虚函数在编译的时候不能确定函数调用,运行阶段才能确定)
如果函数地址在运行阶段才能确定,就是动态联编

虚函数

定义

虚函数是在基类中声明并定义的特殊函数,它可以被派生类重写。在 C++ 中,通过在函数声明前加上 virtual关键字来将函数声明为虚函数

一般形式

class Base {
public:
    virtual void func() {
        // 基类中的虚函数实现
    }
};

class Derived : public Base {
public:
    void func() override {
        // 派生类中的虚函数实现
    }
};

注意

虚函数本身是抽象的,不能直接使用
虚函数需要在具体的类中进行重写和实现,然后通过类的对象、指针或引用来调用。
虚函数使得基类指针或引用在运行时可以根据对象的实际类型来调用相应的派生类函数,实现了动态绑定(late binding)。
虚函数使得我们可以使用父类的指针或引用来调用子类的方法,而不需要知道具体的子类类型。这提供了更高的灵活性和可扩展性,使得程序更易于维护和拓展。
通过将虚函数声明为基类中的虚函数,并在派生类中进行重写,可以实现多态性和动态绑定

纯虚函数

定义

纯虚函数是在基类中声明的虚函数,但没有提供具体的实现代码。在 C++ 中,可以通过在函数声明后添加 “= 0” 来将虚函数声明为纯虚函数。

语法

virtual 返回值类型 函数名 (参数列表)= 0 ;**

class Base {
public:
    virtual void pureVirtualFunc() = 0;
};

在上述示例中,pureVirtualFunc() 是一个纯虚函数,没有提供实际的函数实现。这使得 Base类成为一个抽象类,无法直接创建其对象。

派生类必须重写基类中的纯虚函数才能成为可实例化的类。否则,派生类也会被视为抽象类。

示例
#include 

class Shape {
public:
    virtual void draw() const = 0;  // 纯虚函数
};

class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing a circle." << std::endl;
    }
};

class Square : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing a square." << std::endl;
    }
};

int main() {
    // Shape shape;  // 错误!不能创建抽象类的对象

    Circle circle;
    Square square;

    circle.draw();  // 输出 "Drawing a circle."
    square.draw();  // 输出 "Drawing a square."

    return 0;
}

在上述示例中,Shape 类中的 draw() 函数被声明为纯虚函数,通过 “= 0” 来表示。因此,无法直接创建 Shape 类的对象。

派生类 Circle 和 Square 必须重写基类中的纯虚函数 draw() 才能成为可实例化的类。在主函数中,我们可以创建和使用 Circle 和 Square 类的对象,并调用它们的 draw() 函数。

虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区那么父类指针在释放时无法调用到子类的析构代码,此时就要将父类中的析构函数改为虚析构或者纯虚析构

虚析构函数是指在基类中声明的虚函数,用于正确释放派生类对象的资源。当基类指针指向一个派生类对象时,通过调用基类的虚析构函数,可以确保派生类的析构函数正确地被调用。

纯虚析构函数是指在基类中声明的纯虚函数,没有提供具体的实现代码,用于将基类定义为抽象类。与纯虚函数类似,纯虚析构函数也使得基类成为抽象类,无法直接创建基类对象,但可以用作指针或引用类型。

虚析构和纯虚析构共性
  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现
虚析构和纯虚析构区别
  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构函数和纯虚析构函数都是用于正确释放资源的重要机制。虚析构函数可以实现多态的析构,而纯虚析构函数则用于强制基类成为抽象类,并要求派生类提供自己的析构函数实现。

示例
#include 

class Base {
public:
    virtual ~Base() {
        std::cout << "Base::~Base()" << std::endl;
    }
};

class Derived : public Base {
public:
    ~Derived() override {
        std::cout << "Derived::~Derived()" << std::endl;
    }
};

class AbstractBase {
public:
    virtual ~AbstractBase() = 0;
};

AbstractBase::~AbstractBase() {
    std::cout << "AbstractBase::~AbstractBase()" << std::endl;
}

int main() {
    Base* basePtr = new Derived();
    delete basePtr;

    // AbstractBase abstractObj;  // 错误!无法创建抽象类的对象

    AbstractBase* abstractPtr = nullptr;
    // abstractPtr = new AbstractBase();  // 错误!无法创建抽象类的对象
    
    return 0;
}

在上述示例中,Base 类的析构函数被声明为虚函数,派生类 Derived 重写了该析构函数。我们通过基类指针 basePtr 来创建一个 Derived 类的对象,并使用 delete 运算符释放它。在这个过程中,首先调用派生类的析构函数 Derived::~Derived(),然后调用基类的析构函数 Base::~Base()。

另外,AbstractBase 类的析构函数被声明为纯虚函数,使得 AbstractBase 成为抽象类。由于无法直接实例化抽象类,因此无法创建 AbstractBase 类的对象。在示例中,我们尝试创建一个 AbstractBase 对象,但会导致编译错误。

注意,在纯虚析构函数的情况下,我们仍然需要提供默认的析构函数实现。在示例中,我们将纯虚析构函数 AbstractBase::~AbstractBase() 的定义放在类外部,以提供默认的实现。

总结

1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

3. 拥有纯虚析构函数的类也属于抽象类

虚析构函数和纯虚析构函数的主要作用是确保在继承层次结构中正确地释放对象的资源,以避免内存泄漏。


抽象类

定义

抽象类是指包含纯虚函数的类

它的主要目的是作为其他类的基类,提供一组共同的接口和行为规范。
当一个类声明了虚函数时,它成为了抽象类无法直接实例化对象
能通过该类的派生类来创建对象,并通过这些对象来调用虚函数
在子类中进行重写后,才能实现多态性,并且根据对象的实际类型决定调用哪个具体的函数。

特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

示例

#include 

class AbstractClass {
public:
    virtual void pureVirtualFunc() = 0;  // 纯虚函数

    void concreteFunc() {
        std::cout << "AbstractClass::concreteFunc()" << std::endl;
    }
};

class ConcreteClass : public AbstractClass {
public:
    void pureVirtualFunc() override {
        std::cout << "ConcreteClass::pureVirtualFunc()" << std::endl;
    }
};

int main() {
    // AbstractClass abstractObj;  // 错误!无法创建抽象类的对象

    ConcreteClass concreteObj;
    concreteObj.pureVirtualFunc();    // 输出 "ConcreteClass::pureVirtualFunc()"
    concreteObj.concreteFunc();       // 输出 "AbstractClass::concreteFunc()"

    AbstractClass* abstractPtr = &concreteObj;
    abstractPtr->pureVirtualFunc();   // 输出 "ConcreteClass::pureVirtualFunc()"
    abstractPtr->concreteFunc();      // 输出 "AbstractClass::concreteFunc()"

    return 0;
}

在上述示例中,AbstractClass 是一个抽象类,包含一个纯虚函数 pureVirtualFunc() 和一个具体函数 concreteFunc()。
派生类 ConcreteClass 继承了 AbstractClass,但是重写了纯虚函数 pureVirtualFunc()。
因此,ConcreteClass 可以被实例化,并可以调用基类的具体函数或实现纯虚函数。
通过基类指针可以访问派生类对象,并根据上下文中指针类型调用相应的函数。

你可能感兴趣的:(C,++,c++)