C++ 多态
多态(Polymorphism)是C++面向对象编程的核心特性之一,允许同一个函数、操作符或对象在不同情况下表现出不同的行为。多态的主要目标是提高代码的灵活性和可扩展性。
分类
C++的多态分为两种主要类型:
1. 静态多态(编译时多态)
静态多态是在编译时决定调用哪个函数,主要通过函数重载和运算符重载实现。
函数重载
#include <iostream>
using namespace std;
class Print {
public:
void display(int i) {
cout << "Integer: " << i << endl;
}
void display(double d) {
cout << "Double: " << d << endl;
}
void display(string s) {
cout << "String: " << s << endl;
}
};
int main() {
Print p;
p.display(10); // 调用第一个display
p.display(3.14); // 调用第二个display
p.display("Hello"); // 调用第三个display
return 0;
}
运算符重载
#include <iostream>
using namespace std;
class Complex {
public:
int real, imag;
Complex(int r = 0, int i = 0) : real(r), imag(i) {}
// 重载 + 运算符
Complex operator+(const Complex &c) {
return Complex(real + c.real, imag + c.imag);
}
};
int main() {
Complex c1(1, 2), c2(3, 4);
Complex c3 = c1 + c2; // 调用重载的 +
cout << "Real: " << c3.real << ", Imag: " << c3.imag << endl;
return 0;
}
模版
2. 动态多态(运行时多态)
动态多态是在运行时决定调用哪个函数,主要通过继承和虚函数实现。
实现方式
- 基类声明虚函数(
virtual
)。 - 子类覆盖基类的虚函数。
- 通过基类指针或引用调用虚函数,实际调用由对象类型决定。
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() {
cout << "Drawing Shape" << endl;
}
};
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing Circle" << endl;
}
};
class Rectangle : public Shape {
public:
void draw() override {
cout << "Drawing Rectangle" << endl;
}
};
int main() {
Shape *s1 = new Circle();
Shape *s2 = new Rectangle();
s1->draw(); // 调用 Circle::draw()
s2->draw(); // 调用 Rectangle::draw()
delete s1;
delete s2;
return 0;
}
动态多态的关键点
- 虚函数表(vtable):
- 编译器为含有虚函数的类生成一张虚函数表(vtable),表中存储指向虚函数实现的指针。
- 每个对象都有一个虚函数表指针(vptr)指向它对应的vtable。
- 运行时绑定:
- 使用基类指针或引用调用虚函数时,根据对象类型在运行时从vtable中找到正确的函数并调用。
- 虚函数开销:
- 动态多态需要额外的内存和时间开销(vtable和vptr)。
- 如果不需要动态绑定,应尽量避免使用虚函数。
多态的优点
- 代码复用:
- 基类定义通用接口,派生类实现具体功能。
- 可扩展性:
- 添加新派生类时,无需修改已有代码。
- 灵活性:
- 可以在运行时根据对象类型选择正确的行为。
静态多态和动态多态对比
特性 | 静态多态 | 动态多态 |
---|---|---|
实现方式 | 函数重载、运算符重载 | 虚函数、继承 |
绑定时间 | 编译时绑定 | 运行时绑定 |
性能开销 | 无额外开销 | 存在运行时开销(vtable) |
可扩展性 | 扩展性较弱 | 扩展性强 |
多态的限制
- 静态多态:
- 重载函数的参数类型和个数必须不同,不能仅依赖返回类型区分。
- 动态多态:
- 必须通过基类指针或引用调用虚函数。
- 需要额外的运行时开销。
常见问题
- 为什么需要虚函数?
- 虚函数是实现动态多态的关键。没有虚函数,编译器无法在运行时决定调用哪个函数。
- 析构函数为什么需要声明为虚函数?
- 防止对象通过基类指针销毁时,只调用基类析构函数,而未调用派生类的析构函数,导致资源泄漏。
- 可以将构造函数声明为虚函数吗?
- 不可以。构造函数用于对象创建,而虚函数需要对象的vptr指针,二者矛盾。
总结
一句话概括就是同一种事物不同的表现形态,C++多态主要分为静态多态(编译时多态)和动态多态(运行时多态)静态多态主要通过函数重载,运算符重载,模版实现,会在编译时实现。动态多态通过继承和虚函数实现在运行时通过虚函数表找到正确的函数并且调用。虚函数通过虚函数指针和虚函数表实现。