目录
在C++中,多态(Polymorphism)是面向对象编程的重要特性之一。
多态允许使用基类的指针或引用来调用派生类的函数,具体调用哪个函数由运行时对象的类型决定。
这使得同一个函数名在不同的上下文中表现出不同的行为,从而实现代码的灵活性和可扩展性。
一、多态的类型
C++ 中的多态主要有两种类型:
-
编译时多态(静态多态)
通过 函数重载 和 运算符重载 实现。
在编译时决定调用哪一个重载函数。
-
运行时多态(动态多态)
通过 继承 和 虚函数 实现。
在运行时决定调用哪一个派生类的函数。这种多态是通过指针或引用来实现的。
二、编译时多态的实现(静态多态)
之前文章详细讲解过重载,所以在这里不过多讲述。
-
函数重载
-
函数重载是指在同一个作用域中定义多个同名函数,但它们的参数列表不同(参数的类型、数量或顺序不同)。
-
编译器根据函数调用时传递的参数类型和数量来选择调用哪个函数。
-
-
运算符重载
-
运算符重载允许为用户定义的类型(如类)重载C++内置的运算符,使其能够用于类的对象。
-
运算符重载可以视为一种特殊的函数重载。
-
编译时多态的意义
-
效率:编译时多态在编译时就确定了具体的函数调用,避免了运行时的动态绑定,执行效率高。
-
代码可读性:通过函数重载和运算符重载,可以让代码看起来更直观、易读。
例如,自定义类的对象可以像内置类型一样使用运算符进行操作。
-
灵活性:允许函数和运算符根据上下文自动选择适当的实现,提供了极大的灵活性。
三、运行时多态的实现(动态多态)
运行时多态是通过虚函数实现的,具体步骤如下:
-
基类定义虚函数
在基类中定义一个虚函数(使用
virtual
关键字)。 -
派生类重写虚函数
在派生类中重写该虚函数。
-
通过基类指针或引用调用虚函数
在使用基类指针或引用指向派生类对象时,调用的虚函数会根据对象的实际类型来决定。
代码示例
#include <iostream>
using namespace std;
// 基类:动物
class Animal {
public:
// 虚函数:发出声音
virtual void sound() const {
cout << "Animal makes a sound" << endl;
}
// 虚析构函数
virtual ~Animal() {
cout << "Animal destroyed" << endl;
}
};
// 派生类:狗
class Dog : public Animal {
public:
// 重写基类的虚函数
void sound() const override {
cout << "Dog says: Woof!" << endl;
}
~Dog() {
cout << "Dog destroyed" << endl;
}
};
// 派生类:猫
class Cat : public Animal {
public:
// 重写基类的虚函数
void sound() const override {
cout << "Cat says: Meow!" << endl;
}
~Cat() {
cout << "Cat destroyed" << endl;
}
};
int main() {
Animal* myDog = new Dog(); // 基类指针指向派生类对象
Animal* myCat = new Cat(); // 基类指针指向派生类对象
myDog->sound(); // 调用 Dog 的 sound 函数
myCat->sound(); // 调用 Cat 的 sound 函数
delete myDog; // 销毁 Dog 对象
delete myCat; // 销毁 Cat 对象
return 0;
}
输出
Dog says: Woof!
Cat says: Meow!
Dog destroyed
Cat destroyed
Animal destroyed
Animal destroyed
解析
在上述代码中,
Animal
类有一个虚函数sound()
,并且Dog
和Cat
类重写了该函数。在
main
函数中,Animal
类型的指针myDog
和myCat
分别指向Dog
和Cat
对象。虚函数的调用是在运行时决定的,因此当调用
myDog->sound()
和myCat->sound()
时,分别调用了Dog
和Cat
类中的sound
函数。最后销毁对象时,基类的虚析构函数确保派生类的析构函数被正确调用,避免内存泄漏。
四、多态的意义
-
灵活性
通过使用多态,可以在不修改代码的情况下增加新功能。
例如,添加一个新的
Animal
派生类时,现有代码无需更改即可使用新类型。 -
代码重用
基类提供通用接口,派生类实现具体行为,减少代码重复。
-
可扩展性
通过多态机制,可以轻松扩展程序的功能,同时保持代码的简洁和可读性。
五、虚函数表(多态内部机制)
当一个类声明了虚函数时,编译器会为这个类创建一个虚函数表。这个表中存储了该类所有虚函数的地址。
(每个包含虚函数的类都有一个虚函数表)
虚函数表是用于支持动态多态性的一种内部机制。
工作原理
-
虚函数指针(虚函数表指针)
当一个类包含虚函数时,编译器会在该类的对象中添加一个指向虚函数表的指针,通常称为虚函数指针(vptr)。
-
虚函数表
对于每个有虚函数的类,编译器都会生成一个虚函数表。
虚函数表是一个指针数组,其中每个元素都是类中一个虚函数的地址。
当一个派生类继承了包含虚函数的基类时,那么这个派生类也会拥有自己的虚函数表。
当子类继承了基类并重写了虚函数时,子类的虚函数表中对应的虚函数指针会指向子类实现的函数。
-
动态绑定
当通过基类指针或引用调用虚函数时,程序会通过虚函数指针(vptr)找到虚函数表(vtable),然后根据虚函数表中存储的地址调用实际的函数实现。这样就实现了动态多态。
例子
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() {
cout << "Base class show function" << endl;
}
virtual ~Base() = default;
};
class Derived : public Base {
public:
void show() override {
cout << "Derived class show function" << endl;
}
};
int main() {
Base* b = new Derived();
b->show(); // 通过基类指针调用虚函数
delete b;
return 0;
}
解释:
当基类
Base
定义了虚函数show()
时,编译器为Base
类生成了一个虚函数表。当
Derived
类继承了Base
并重写了show()
函数,编译器也为Derived
类生成了一个虚函数表。此表中的
show()
函数指针指向Derived
类中的show()
函数。在
main
函数中,使用Base
类的指针指向Derived
类的对象。当调用
b->show()
时,程序会通过vptr
找到Derived
类的虚函数表,从而调用Derived
类的show()
函数。