前言
本篇文章来给大家讲解一下C++中的多态,学习多态是了解C++特性必不可少的。
一、多态的概念
多态(Polymorphism)是面向对象编程中一个重要的概念,它允许基于对象的实际类型来调用相应的方法,以实现更灵活和可扩展的代码。
在C++中,多态通常通过虚函数(Virtual Function)和指向基类的指针或引用来实现。下面我们来详细讲解多态的概念及其应用。
1.虚函数(Virtual Function):
在基类中,可以将某个成员函数声明为虚函数。虚函数通过使用关键字 virtual 来标识。当一个成员函数声明为虚函数后,在派生类中可以进行重写(覆盖)该虚函数。这样,通过基类指针或引用调用虚函数时,会根据实际对象的类型来决定要执行的函数版本。
2.多态性(Polymorphism):
多态性是指同一个方法可以根据调用时的对象类型的不同而表现出不同的行为。在运行时,可以根据对象的实际类型来调用相应的方法。
3.基类指针和引用:
基类指针或引用可以指向派生类的对象。这样做的好处是可以通过基类指针或引用来调用派生类中的虚函数,实现多态性。在编译时,编译器将根据指针或引用的静态类型来进行函数调用的语法检查,但在运行时,根据对象的实际类型来决定调用的具体函数。
下面是一个示例来说明多态的概念:
#include <iostream> class Shape { public: virtual void draw() { std::cout << "绘制图形" << std::endl; } }; class Circle : public Shape { public: void draw() { std::cout << "绘制圆形" << std::endl; } }; class Rectangle : public Shape { public: void draw() { std::cout << "绘制矩形" << std::endl; } }; int main() { Shape* shapePtr; Circle circle; Rectangle rectangle; // 指向派生类对象的基类指针 shapePtr = &circle; shapePtr->draw(); // 输出:绘制圆形 shapePtr = &rectangle; shapePtr->draw(); // 输出:绘制矩形 return 0; }
在上述示例中,定义了一个基类 Shape 和两个派生类 Circle 和 Rectangle。基类中的 draw() 函数被声明为虚函数。在主函数中,通过基类指针 shapePtr 分别指向派生类对象 circle 和 rectangle。通过调用 shapePtr->draw() 实现多态性,根据指针所指向对象的实际类型来调用相应的 draw() 函数。
在输出中,当 shapePtr 指向 circle 对象时,调用 shapePtr->draw() 实际上调用了 Circle 类中重写的 draw() 函数,输出结果为 “绘制圆形”。当 shapePtr 指向 rectangle 对象时,调用 shapePtr->draw() 调用了 Rectangle 类中重写的 draw() 函数,输出结果为 “绘制矩形”。
通过使用多态性,可以实现更灵活和可扩展的代码。在实际应用中,多态性常用于实现基类的统一接口,而具体的实现则由派生类来完成。这样,在程序设计中可以更加方便地扩展和修改代码,提高代码的可维护性和可重用性。
二、什么是虚函数
虚函数是在面向对象编程中使用的一种特殊类型的函数。它在基类中声明,并在派生类中进行重写(覆盖)。关键字 virtual 用于在基类中标记函数为虚函数。
虚函数的作用是允许在运行时决定要调用的函数版本,而不是在编译时确定。通过使用基类指针或引用指向派生类对象,并调用虚函数,可以实现多态性。
当使用基类指针或引用调用虚函数时,编译器会根据实际对象的类型来决定要调用的函数版本,而不是根据指针或引用的类型。这样就可以根据对象的实际类型来执行相应的代码。
三、什么函数都可以被声明为虚函数吗
1.普通函数(非成员函数):虚函数是用于实现多态性的成员函数,而普通函数不属于任何类的成员函数,因此不能被声明为虚函数。
2.静态成员函数:静态成员函数是类的一部分,但它们与特定对象实例无关。虚函数的调用是基于实际对象类型的动态绑定,而静态成员函数在编译时就确定了调用的对象类型,因此它们不需要虚函数的机制。
3.内联成员函数:内联成员函数的定义通常在类声明中的头文件中,并通过内联展开来提高执行效率。虚函数需要通过虚函数表进行动态绑定,因此不能被内联。
4.构造函数:构造函数负责对象的初始化,在对象创建时自动调用。由于构造函数在对象创建期间被调用,并且在构造函数过程中对象的类型已知,因此不需要虚函数的动态绑定机制。
5.友元函数:友元函数是在类外部声明的函数,它可以访问类的私有成员。由于友元函数不是类的成员函数,它不参与类的继承关系,因此不能声明为虚函数。
四、为什么基类的析构函数必须被声明为虚函数
析构函数一般被声明为虚函数的原因是为了确保正确的对象清理和资源释放。
当在一个父类指针指向一个派生类对象时,如果父类的析构函数不是虚函数,那么当使用delete关键字删除指向派生类对象的父类指针时,只会调用父类的析构函数,而不会调用派生类的析构函数。这可能导致派生类中分配的资源无法正确释放,从而引发内存泄漏或其他资源管理问题。
通过将父类的析构函数声明为虚函数,可以实现动态绑定,确保在删除指向派生类对象的父类指针时,能正确调用派生类的析构函数,实现对派生类中资源的释放。
总结一下为什么析构函数要用虚函数:
1.确保基类指针删除对象时能正确调用派生类的析构函数,释放派生类资源。
2.支持多态性,通过基类指针来操作派生类对象时,能够根据实际对象类型调用正确的析构函数。
3.安全地释放内存和其他资源,避免内存泄漏和资源管理问题。
4.符合面向对象设计原则,实现良好的继承和多态性。
class Animal { public: Animal() { cout << "Animal constructor" << endl; } ~Animal() { cout << "Animal destructor" << endl; } }; class Dog : public Animal { public: Dog() { cout << "Dog constructor" << endl; } ~Dog() { cout << "Dog destructor" << endl; } };
现在,我们使用一个基类指针来指向派生类对象:
int main() { Animal* animal = new Dog(); delete animal; return 0; }
如果基类Animal的析构函数不是虚函数,那么在执行delete animal;时,只会调用Animal的析构函数,输出结果为:
Animal destructor
由于基类Animal的析构函数不是虚函数,虽然animal指向的实际对象是一个Dog对象,但只会调用父类Animal的析构函数,导致Dog类中的资源没有被正确释放。
现在,我们将Animal类的析构函数声明为虚函数:
class Animal { public: Animal() { cout << "Animal constructor" << endl; } virtual ~Animal() { cout << "Animal destructor" << endl; } };
再次执行delete animal;时,会正确调用Dog类的析构函数,输出结果为:
Dog destructor Animal destructor
通过使用虚函数,我们确保了在删除基类指针时调用了派生类的析构函数,正确释放了Dog类中的资源。这展示了为什么析构函数通常被声明为虚函数的重要性。
总结
本篇文章就讲解到这里,多态是C++中比较重要的一个概念,希望大家牢牢掌握。