多态是面向对象编程的核心特性之一,它使得对象可以通过同一接口以不同的方式表现。多态允许我们通过父类的指针或引用来调用子类的函数。C++ 中的多态主要分为两种类型:
- 编译时多态(静态多态):通过函数重载和运算符重载实现。
- 运行时多态(动态多态):通过继承和虚函数实现。
下面我们分别来详细了解这两种多态。
1. 编译时多态(静态多态)
1.1 函数重载(Function Overloading)
函数重载允许在同一作用域内定义多个同名函数,但它们的参数个数或类型不同。编译器根据调用时的参数来选择对应的函数。
#include <iostream>
using namespace std;
class Print {
public:
void display(int x) {
cout << "Display integer: " << x << endl;
}
void display(double x) {
cout << "Display double: " << x << endl;
}
};
int main() {
Print p;
p.display(10); // 调用 display(int)
p.display(10.5); // 调用 display(double)
return 0;
}
输出:
Display integer: 10
Display double: 10.5
在编译时,编译器根据参数的类型自动选择重载的 display 函数,这就是编译时多态。
1.2 运算符重载(Operator Overloading)
C++ 允许重载运算符,重载的运算符可以用来自定义类型的对象进行常见操作,例如 +、- 等运算符。
#include <iostream>
using namespace std;
class Complex {
private:
int real, imag;
public:
Complex(int r, int i) : real(r), imag(i) {}
// 重载 + 运算符
Complex operator + (const Complex& c) {
return Complex(real + c.real, imag + c.imag);
}
void display() const {
cout << "Real: " << real << ", Imag: " << imag << endl;
}
};
int main() {
Complex c1(3, 4), c2(1, 2);
Complex c3 = c1 + c2; // 使用重载的 + 运算符
c3.display();
return 0;
}
输出:
Real: 4, Imag: 6
在此例中,重载的 + 运算符允许两个 Complex 类型的对象进行相加操作,这种方式称为编译时多态。
2. 运行时多态(动态多态)
运行时多态是指在程序运行时通过基类指针或引用来调用派生类的方法。C++ 中的运行时多态通过虚函数(virtual functions)实现。虚函数使得编译器能在运行时根据对象的类型决定调用哪个版本的函数。
2.1 虚函数(Virtual Function)
虚函数是在基类中声明,并且在派生类中可以被重写的函数。使用虚函数时,通过基类指针或引用调用函数时,会调用实际对象的函数,而不是基类中的函数。
2.1.1 示例:虚函数和多态
#include <iostream>
using namespace std;
class Animal {
public:
// 基类中的虚函数
virtual void sound() {
cout << "Animal makes a sound" << endl;
}
};
class Dog : public Animal {
public:
// 重写基类的虚函数
void sound() override {
cout << "Dog barks" << endl;
}
};
class Cat : public Animal {
public:
// 重写基类的虚函数
void sound() override {
cout << "Cat meows" << endl;
}
};
int main() {
Animal* animal1 = new Dog(); // 基类指针指向派生类对象
Animal* animal2 = new Cat(); // 基类指针指向派生类对象
animal1->sound(); // 调用 Dog 类的 sound
animal2->sound(); // 调用 Cat 类的 sound
delete animal1;
delete animal2;
return 0;
}
输出:
Dog barks
Cat meows
在上述代码中,sound() 是一个虚函数,在基类 Animal 中声明,并在派生类 Dog 和 Cat 中重写。当我们通过基类指针调用 sound() 时,程序会在运行时决定调用哪个版本的函数,分别输出 "Dog barks" 和 "Cat meows"。
2.2 虚函数的工作原理
- 虚函数表(vtable):C++ 编译器为每个包含虚函数的类生成一个虚函数表。在运行时,根据对象的实际类型选择正确的函数版本。
- 动态绑定:通过虚函数实现运行时多态,也叫动态绑定(Dynamic Binding)。虚函数使得函数调用的解析推迟到运行时。
2.3 虚函数的析构函数
如果一个类有虚函数,它通常也应该有一个虚析构函数。否则,当通过基类指针删除派生类对象时,可能不会调用派生类的析构函数,从而导致资源泄漏。
class Animal {
public:
virtual ~Animal() {
cout << "Animal Destructor" << endl;
}
};
class Dog : public Animal {
public:
~Dog() {
cout << "Dog Destructor" << endl;
}
};
2.4 纯虚函数(Abstract Function)
纯虚函数是没有实现的虚函数,它强制派生类必须实现该函数。包含纯虚函数的类称为抽象类,不能实例化。
class Shape {
public:
virtual void draw() = 0; // 纯虚函数
};
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing Circle" << endl;
}
};
int main() {
// Shape shape; // 错误,不能实例化抽象类
Circle c;
c.draw(); // 输出 "Drawing Circle"
return 0;
}
3. 多态的应用
- 接口和抽象类:多态使得通过基类指针或引用来操作派生类对象成为可能,从而支持不同类的统一操作。
- 扩展性:通过继承和重写虚函数,可以轻松扩展程序而不需要修改现有代码。
- 设计模式:许多设计模式,如策略模式、工厂模式、命令模式等,都依赖于多态来实现灵活和可扩展的代码。
4. 小结
- 编译时多态:通过函数重载和运算符重载实现。
- 运行时多态:通过虚函数实现,允许通过基类指针或引用调用派生类的函数。
- 虚函数:使得函数调用的解析推迟到运行时,从而实现动态绑定。
多态是面向对象编程的一个重要特性,它使得代码更具可扩展性和灵活性。通过继承和虚函数机制,我们可以实现不同类型对象的统一操作,而不需要关心它们的具体类型。