相关文章:
从0开始C++(一):从C到C++
从0开始C++(二):类、对象、封装
从0开始C++(三):构造函数与析构函数详解
从0开始C++(四):作用域限定符、this指针、static与const关键字
从0开始C++(五):友元函数&运算符重载
从0开始C++(六):模板与容器的使用
从0开始C++(七):继承
目录
什么是多态
函数覆盖
虚函数
多态的实现
多态的原理
虚析构函数
什么是多态
多态是面向对象编程中的一个重要概念,它指的是同一种行为或方法可以根据不同的对象来表现出不同的形态或结果。简单来说,多态就是同一个方法在不同的对象上产生不同的效果。
在面向对象编程中,多态是通过继承和方法重写来实现的。当一个父类有多个子类时,可以使用父类的引用来指向任意一个子类的对象,然后通过调用相同的方法来实现不同的行为。这样可以极大地提高代码的灵活性和可扩展性。
多态的使用可以提高代码的可读性和可维护性,简化代码的逻辑结构,使代码更加灵活和易于扩展。它是面向对象编程的重要特性之一,也是面向对象编程的核心思想之一。
静态多态:
● 静态多态是指在编译时就能确定要调用的方法,通过函数重载和运算符重载、模板来实现。
动态多态:
● 动态多态是指在运行时根据对象的实际类型来确定要调用的函数,通过继承和函数覆盖来实现。
注意:本文中后续说的多态均为动态多态。
多态的使用具有三个前提条件下面会进行详细介绍:
● 公有继承(继承权限为 public )
● 函数覆盖
● 基类的指针/引用指向派生类的对象
函数覆盖
函数覆盖、函数隐藏。这两个比较相似,但是函数隐藏不支持多态。而函数覆盖是多态的必要条件。函数覆盖和函数隐藏的区别有以下几点:
● 函数隐藏是派生类中存在与基类同名同参的函数,编译器会将基类的同名同参数的函数进行隐藏。
● 函数覆盖是基类中定义了一个虚函数,派生类编译写一个同名同参数的函数将基类中的虚函数进行重写并覆盖。注意:覆盖的函数必须是虚函数。
虚函数
虚函数(Virtual Function)是一种特殊类型的成员函数,用于实现多态性。虚函数可以在基类中声明,并在派生类中重新定义和实现。通过使用虚函数,可以实现基类指针或引用调用派生类对象的特定成员函数,从而实现运行时的动态绑定。
要将函数声明为虚函数,需要在函数声明前加上关键字 virtual 。基类中的虚函数的定义和实现是通用的,而派生类可以根据自己的需求对这些虚函数进行重写。
需要注意
虚函数具有传递性:基类中被覆盖的函数是虚函数,派生类中新覆盖的函数也是虚函数。
只有普通成员函数与析构函数可以被声明为虚函数
使用虚函数的步骤如下:
1、在基类中声明虚函数:在基类的函数声明前加上关键字 virtual ,如下所示:
class Base {
public:
virtual void foo() {
// 函数实现
}
};
2、在派生类中重写虚函数:在派生类中实现与基类中虚函数具有相同名称和参数列表的函数,同时加上 override 关键字,明确表明这是对基类中虚函数的重写,如下所示:
class Derived : public Base {
public:
void foo() override {
// 函数实现
}
};
多态的实现
我们在开篇时提到过,要实现多态,需要有三个前提条件:
● 公有继承(已经实现)
● 函数覆盖(已经实现)
● 基类的指针/引用指向派生类的对象(待实现)
为什么要基类的指针/引用指向派生类的对象?
● 实现运行时多态:当使用基类的指针或引用指向派生类的对象时,程序在运行时会根据对象的实际类型来调用相应的函数,而不是根据指针或者引用类型。
● 统一接口:基类的指针可以作为一个通用的接口,用于操作不同类型的派生类对象,这样可以使代码更灵活,减少重复的代码。并且的支持和拓展更好进行维护。
使用方法如下:
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "动物爱吃饭" << endl;
}
};
class Dog:public Animal
{
public:
void eat()override
{
cout << "狗爱吃骨头" << endl;
}
};
class Cat:public Animal
{
public:
void eat()override
{
cout << "猫爱吃鱼" << endl;
}
};
int main()
{
// 基类指针指向派生类对象
Animal *a1 = new Dog;
// 调用派生类覆盖的虚函数
a1->eat(); // 狗爱吃骨头
Animal *a3 = new Cat;
a3->eat(); // 猫爱吃鱼
Dog d1;
Animal &a2 = d1;
a2.eat(); // 狗爱吃骨头
return 0;
}
多态的原理
实现多态性的机制是通过虚函数表 vtable 来实现的。每个包含虚函数的类都有一个虚函数表,其中存储了指向各个虚函数的指针。每个类的对象内部会有一个隐藏的虚函数表指针成员变量,指向当前类的虚函数表。当调用虚函数时,会根据对象的虚函数表中相应位置的指针找到正确的函数进行调用。
虚析构函数
如果不使用虚析构函数,且基类的指针指向派生类的对象,使用delete销毁对象时,只能触发基类的析构函数,如果在派生类中申请内存等资源,则会导致内存无法释放,出现内存泄漏的问题。
解决方案是给基类的析构函数使用virtual修饰为虚析构函数,通过传递性可以把各个派生类的析构函数都变为虚析构函数,因此建议给一个可能为基类的类中的析构函数设置成虚析构函数。
示例如下:
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "动物爱吃饭" << endl;
}
// 虚析构函数
virtual ~Animal()
{
cout << "Animal析构函数被调用了" << endl;
}
};
class Dog:public Animal
{
public:
void eat()override
{
cout << "狗爱吃骨头" << endl;
}
~Dog()
{
cout << "Dog 析构函数被调用了" << endl;
}
};
int main()
{
// 基类指针指向派生类对象
Animal *a1 = new Dog;
// 调用派生类覆盖的虚函数
a1->eat(); // 狗爱吃骨头
delete a1;
return 0;
}