往期回顾:
C++ 入门08:运算符重载-CSDN博客
C++ 入门09:友元函数和友元类-CSDN博客
C++ 入门10:继承和派生类-CSDN博客
C++ 入门第十一天:虚函数和多态
一、前言
在前面的文章学习中,我们了解了类和对象的基础知识、构造函数、拷贝构造函数、静态成员、常量成员、运算符重载、友元函数、友元类以及继承和派生类。今天,我们将学习 C++ 中的虚函数和多态。多态是面向对象编程中的一个重要概念,它使得相同的操作可以作用于不同的对象,从而提高代码的灵活性和可扩展性。
二、虚函数和多态
2.1、 虚函数
(1)什么是虚函数?
虚函数是面向对象编程中一个极其重要的概念,特别是在使用如C++这样的语言进行多态性设计时。它是在基类中通过virtual
关键字明确声明的函数,其主要目的是允许派生类(子类)根据自身的需要来重写(Override)这个函数的行为,而无需修改基类的代码。这种机制使得基类指针或引用在指向派生类对象时,能够根据对象的实际类型来调用相应的函数版本,从而实现了多态性。
(2)声明虚函数
在C++中,声明一个虚函数非常简单,只需在基类函数的声明前加上virtual
关键字即可。
声明示例:
class Base {
public: // 声明一个虚函数 virtual void display() { cout << "Displaying Base class" << endl; }
}; class Derived : public Base {
public: // 在派生类中重写虚函数 void display() override { // 注意:C++11中引入了override关键字作为可选标记,用于明确表示该函数是重写自基类的虚函数 cout << "Displaying Derived class" << endl; }
};
在这个例子中,Base
类有一个虚函数display()
,而Derived
类继承自Base
并重写了display()
函数。通过基类指针或引用来调用display()
时,如果指针或引用实际指向的是Derived
类的对象,那么将调用Derived
类中重写的display()
函数,而不是Base
类中的原始函数。这种根据对象实际类型动态调用函数的能力就是多态性的体现。
(3)虚函数的作用
- 实现多态:允许通过基类的引用或指针来调用派生类中的函数,从而增加代码的灵活性和可扩展性。
- 接口设计:在基类中定义虚函数但不实现它们(即纯虚函数),可以创建抽象基类,作为派生类的接口规范。
- 解耦:通过虚函数,可以降低程序各部分之间的耦合度,提高代码的可维护性和复用性。
(4)注意事项
|
|
|
运用示例:
我们定义一个基类 Shape
,表示几何形状,然后定义两个派生类 Circle
和 Rectangle
,表示圆形和矩形。我们在基类中声明一个虚函数 draw
,并在派生类中重写它。
#include <iostream>
using namespace std;// 基类 Shape
class Shape {
public:virtual void draw() {cout << "Drawing a shape" << endl;}
};// 派生类 Circle
class Circle : public Shape {
public:void draw() override {cout << "Drawing a circle" << endl;}
};// 派生类 Rectangle
class Rectangle : public Shape {
public:void draw() override {cout << "Drawing a rectangle" << endl;}
};int main() {Shape *shape1 = new Circle();Shape *shape2 = new Rectangle();shape1->draw(); // 调用 Circle 的 draw 函数shape2->draw(); // 调用 Rectangle 的 draw 函数delete shape1;delete shape2;return 0;
}
在这个示例中,Shape
类声明了一个虚函数 draw
。Circle
和 Rectangle
类重写了这个函数。在 main
函数中,我们创建了指向 Circle
和 Rectangle
对象的 Shape
指针,通过这些指针调用 draw
函数,实际调用的是派生类的重写函数。
2.2、 纯虚函数和抽象类
(1)什么是纯虚函数?
纯虚函数是面向对象编程中用于定义接口的一个关键概念,特别是在使用C++等语言时。它是虚函数的一个特殊形式,但与普通的虚函数不同,纯虚函数没有函数体,即没有具体的实现代码。纯虚函数的主要目的是强制要求从该类派生的所有子类都必须提供该函数的具体实现,从而确保派生类具有特定的行为或功能。
(2)纯虚函数的声明
纯虚函数的声明方式是在函数声明的末尾加上= 0
。这个= 0
标记表明该函数是一个纯虚函数,它不需要(也不允许)在基类中提供实现。
声明示例:
class Base {
public: // 声明一个纯虚函数 virtual void pureVirtualFunction() = 0;
};
在这个例子中,Base
类中的pureVirtualFunction
就是一个纯虚函数。由于它没有被实现(即没有函数体),因此Base
类本身不能被实例化。任何尝试创建Base
类对象的操作都将导致编译错误。
(3)什么是抽象类?
包含至少一个纯虚函数的类被称为抽象类(Abstract Class)。抽象类的主要作用是作为一个基类,用于定义一组接口(即一组纯虚函数),这些接口将由派生类通过提供具体实现来继承和实现。由于抽象类不能实例化,它通常用作基类来指导派生类的设计和实现。
(4)抽象类的特点
- 不能被实例化:由于抽象类至少包含一个纯虚函数,它不能用来创建对象。
- 强制派生类实现接口:通过定义纯虚函数,抽象类可以强制要求所有派生类都实现这些函数,从而确保派生类具有特定的接口。
- 作为接口规范:抽象类通常用于定义一组操作的规范,这些操作将由派生类根据具体情况来实现。
示例:
class Shape {
public: // 纯虚函数,定义在形状类中,但由具体形状类来实现 virtual void draw() = 0;
}; class Circle : public Shape {
public: void draw() override { // 实现绘制圆形的逻辑 cout << "Drawing a circle" << endl; }
}; // 使用
Shape* shape = new Circle(); // 基类指针指向派生类对象
shape->draw(); // 调用Circle类的draw()函数
delete shape; // 清理资源
在这个例子中,Shape
是一个抽象类,它定义了一个纯虚函数draw()
。Circle
类继承自Shape
并重写了draw()
函数,提供了具体的实现。通过基类指针shape
调用draw()
函数时,实际上调用的是Circle
类的draw()
函数,这体现了多态性的特点。
2.3、 虚析构函数
在涉及到多态性时,确保对象的析构过程正确无误是极其重要的。多态性允许我们通过基类的指针或引用来操作派生类的对象,这在许多设计模式中非常有用,如工厂模式、策略模式等。然而,如果不正确地处理析构函数,就可能会引发资源泄漏或其他严重问题。
为了确保派生类对象在销毁时能够正确调用其析构函数,我们需要将基类的析构函数声明为虚函数。
(1)虚析构函数的重要性
当基类的指针或引用指向派生类对象,并通过该指针或引用删除对象时(例如,使用delete
操作符在C++中),如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数。这意味着派生类中定义的任何资源清理代码(如动态分配的内存释放、文件关闭、网络连接关闭等)都不会被执行,从而导致资源泄漏。
为了避免这种情况,我们需要将基类的析构函数声明为虚函数。通过这样做,当通过基类指针或引用删除派生类对象时,会首先调用派生类的析构函数(如果有的话),然后依次向上调用基类链中每个基类的析构函数,直到最顶层的基类。这样确保了对象在销毁时能够正确地释放其占用的所有资源。
(2)声明虚析构函数
声明虚析构函数非常简单,只需在基类的析构函数前加上virtual
关键字即可。这告诉编译器该析构函数是虚函数,需要在运行时根据对象的实际类型来确定要调用的析构函数。
声明示例:
class Base {
public: // 构造函数通常不声明为虚函数 Base() { // 构造函数体 } // 声明虚析构函数 virtual ~Base() { // 析构函数体,可能包含资源释放代码 // 但通常保持为空,因为具体资源释放将在派生类中处理 } // 其他成员函数...
}; class Derived : public Base {
public: ~Derived() override { // 派生类析构函数体,包含派生类特有的资源释放代码 } // 其他成员函数...
}; // 使用示例
Base* ptr = new Derived();
// ...
delete ptr; // 正确调用Derived的析构函数,然后是Base的析构函数
在这个例子中,Base
类的析构函数被声明为虚函数,而Derived
类继承了Base
并可能在其析构函数中添加了额外的资源释放代码。当通过基类指针ptr
删除对象时,由于Base
的析构函数是虚函数,所以首先会调用Derived
的析构函数(如果有的话),然后调用Base
的析构函数。这样就保证了无论对象的实际类型是什么,其析构过程都能正确无误地执行。
应用示例:
我们在 Shape
类中声明虚析构函数,并在派生类中定义析构函数。
#include <iostream>
using namespace std;// 基类 Shape
class Shape {
public:virtual void draw() = 0; // 纯虚函数virtual ~Shape() { // 虚析构函数cout << "Destroying Shape" << endl;}
};// 派生类 Circle
class Circle : public Shape {
public:void draw() override {cout << "Drawing a circle" << endl;}~Circle() {cout << "Destroying Circle" << endl;}
};// 派生类 Rectangle
class Rectangle : public Shape {
public:void draw() override {cout << "Drawing a rectangle" << endl;}~Rectangle() {cout << "Destroying Rectangle" << endl;}
};int main() {Shape *shape1 = new Circle();Shape *shape2 = new Rectangle();shape1->draw(); // 调用 Circle 的 draw 函数shape2->draw(); // 调用 Rectangle 的 draw 函数delete shape1; // 调用 Circle 的析构函数delete shape2; // 调用 Rectangle 的析构函数return 0;
}
在这个示例中,Shape
类声明了一个虚析构函数。在 main
函数中,当我们删除 shape1
和 shape2
时,首先调用派生类的析构函数,然后调用基类的析构函数,从而确保资源正确释放。
以上就是 C++ 程序的虚函数和多态的基础知识点了。包括虚函数的声明和使用、纯虚函数和抽象类的概念以及虚析构函数的作用。多态是面向对象编程中的一个重要特性,它使得相同的操作可以作用于不同的对象,从而提高代码的灵活性和可扩展性。
都看到这里了,点个赞再走呗朋友~
加油吧,预祝大家变得更强!