一、定义
①从广义上说,多态性是指:一段程序能够处理多种类型对象的能力。在C++
语言中,这种多态性可以通过包含多态4种形式来实现。强制多态、重载多态、类型参数化多态、包含多态。
②从实现上来说,多态的分类:静态多态、动态多态。
二、 广义上的多态
-
强制多态(Coercive Polymorphism):
- 也称为强制类型转换,允许一个类型被当作另一个类型来使用。这种多态通常涉及到隐式或显式的类型转换。
-
重载多态(Overloading Polymorphism):
- 这是编译时多态的一个具体体现,包括函数重载和运算符重载。在这类多态中,同一名称的函数或运算符可以有不同的实现,这些实现根据参数类型和数量进行区分。
- 例如,多个同名函数可以处理不同参数类型,实现了根据上下文的不同调用适当的实现。
-
类型参数化多态(Parametric Polymorphism):
- 也称为泛型多态,是指函数或数据结构的行为不依赖于特定类型,而是可以在不同类型上使用。
- C++ 中的模板(templates)就是一个例子。模板允许你编写可接受任何类型的函数或类。
- 示例:
template <typename T> T add(T a, T b) { return a + b; }
-
包含多态(Inclusion Polymorphism):
- 指的是通过基类指针或引用来引用派生类对象,通常涉及继承和虚函数。
- 这种多态性允许调用被重写的成员函数,从而实现运行时动态绑定。
三、 多态的实现
编译时多态和运行时多态是从多态的实现时机和机制的角度进行分类的。这两种多态各自代表了不同的编程概念和技术,主要体现在以下几个方面:
编译时多态(Compile-time Polymorphism)
-
定义:编译时多态是在编译阶段决定具体的函数或操作,编译器根据参数类型和数量选择相应的函数实现。这种多态通常无法在运行时改变。
-
实现机制:
- 函数重载(Function Overloading):允许同一函数名根据参数类型或数量的不同,提供不同的实现。例如:
void print(int i); void print(double d);
- 运算符重载(Operator Overloading):允许用户定制运算符的行为。例如:
class Complex { public: Complex operator+(const Complex& other); };
- 模板(Templates):可以在编译时生成针对不同类型的代码。例如:
template <typename T> T max(T a, T b);
- 函数重载(Function Overloading):允许同一函数名根据参数类型或数量的不同,提供不同的实现。例如:
-
特点:
- 在编译阶段就已确定具体的函数或操作。
- 相对效率高,因为没有运行时开销。
- 类型安全性好,因为所有类型检查在编译时完成。
运行时多态(Run-time Polymorphism)
-
定义:运行时多态是在程序运行时根据对象的实际类型决定调用哪个函数。这种多态通常依赖于继承和虚函数机制。
-
实现机制:
- 虚函数(Virtual Functions):基类中的函数声明为虚函数,允许派生类重写。在运行时,通过基类指针或引用调用转换为派生类的实现。例如:
class Base { public: virtual void show(); }; class Derived : public Base { public: void show() override; }; Base* b = new Derived(); b->show(); // 根据实际对象类型调用 Derived::show()
- 虚函数(Virtual Functions):基类中的函数声明为虚函数,允许派生类重写。在运行时,通过基类指针或引用调用转换为派生类的实现。例如:
-
特点:
- 决定于运行时对象的实际类型,具有动态绑定。
- 可能会带来一定的运行时开销(例如通过虚表实现调用),但可以实现更灵活的设计。
- 允许动态添加和改变行为,更符合面向对象编程的特性。
总结
编译时多态和运行时多态是为了满足不同编程需求而设计的多态性机制。编译时多态多用于类型静态已知的场合,如函数重载和模板,以增强性能和类型安全。而运行时多态则为程序提供了灵活性和可扩展性,是实现多态行为的主要手段,特别是在使用继承和多态时。
四、为何引入多态?
多态是面向对象编程(OOP)中一个重要的特性,它允许使用统一的接口处理不同类型的数据和对象。引入多态有几个重要的原因:
-
灵活性和可扩展性:
- 多态允许程序员使用基类的指针或引用来指向派生类的对象,从而使得代码能够很容易地扩展,而无需修改已有的代码。例如,添加新的派生类只需要实现相应的方法,无需更改使用基类的任何代码。
-
代码复用:
- 通过使用虚函数和继承,多个类可以共享相同的接口,而不需要重复实现相似的功能。这减少了代码的重复,提高了代码的可维护性。
-
接口一致性:
- 用多态,客户端代码可以使用一个统一的接口来操作不同类型的对象。这意味着客户代码不需要关心具体的类类型,只需依赖基类的接口。这使得代码更易于理解。
-
运行时绑定:
- 多态通过动态绑定在运行时确定调用哪个方法,而不是在编译时就固定下来。这允许根据具体的对象类型来决定行为,增加了程序的灵活性。
-
简化程序结构:
- 使用多态,可以将复杂的控制结构(如条件分支)简化为更加简单和整洁的对象交互。例如,可以通过一个单一的接口调用不同的行为,而不需要使用大量的
if
条件或switch
语句。
- 使用多态,可以将复杂的控制结构(如条件分支)简化为更加简单和整洁的对象交互。例如,可以通过一个单一的接口调用不同的行为,而不需要使用大量的
-
增强程序的灵活性和动态性:
- 多态使得程序能够在运行时根据用户的输入或其他变量决定使用哪个具体的实现,而不是在编译时就固定下来。这样可以使得系统更加动态和灵活。
例子
考虑一个图形程序,其中有不同的图形类型(如圆形和方形)。可以定义一个基类 Shape
,然后派生出 Circle
和 Square
类。使用多态可以让程序使用 Shape
类型的指针或引用来处理具体的图形,而不需要关心具体的图形类型:
class Shape {
public:
virtual void draw() = 0; // 纯虚函数
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing a circle." << std::endl;
}
};
class Square : public Shape {
public:
void draw() override {
std::cout << "Drawing a square." << std::endl;
}
};
// 使用多态
void renderShape(Shape* shape) {
shape->draw(); // 调用具体形状的 draw 方法
}
int main() {
Circle circle;
Square square;
renderShape(&circle); // 输出: Drawing a circle.
renderShape(&square); // 输出: Drawing a square.
return 0;
}
在这个例子中,无论是 Circle
还是 Square
,都可以通过 Shape
接口来处理,使得代码更加可扩展和灵活。
总之,多态是构建灵活、可维护和可扩展的面向对象设计的基石,其引入极大地提升了软件开发的效率和质量。