前言
通常我们说的多态是面向对象泛型编程,C++通过类继承和虚函数来支持多态,这里就是一般指所谓的动态多态性。而模板运行将不同的特定行为与单个泛型表示关联起来,通常在在编译时候进行处理,我们称之为静态多态性。
由于历史原因,C++一开始只能通过使用继承和虚函数使用(动态)多态
动态多态
这里是一个使用的多态的例子
#include <iostream>
#include <string>
// 基类 Shape
class Shape {
public:
// 虚析构函数
virtual ~Shape() = default;
// 虚函数,用于计算面积
virtual double getArea() const = 0;
};
// 派生类 Circle
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// 重写基类的虚函数
double getArea() const override {
return 3.14 * radius * radius;
}
};
// 派生类 Rectangle
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
// 重写基类的虚函数
double getArea() const override {
return width * height;
}
};
// 函数,接受一个 Shape 指针,并打印其面积
void printArea(const Shape* shape) {
std::cout << "Area: " << shape->getArea() << std::endl;
}
int main() {
// 创建 Circle 和 Rectangle 对象
Circle circle(5);
Rectangle rectangle(4, 6);
// 使用基类指针指向派生类对象
Shape* shapePtr = &circle;
printArea(shapePtr); // 输出 Circle 的面积
shapePtr = &rectangle;
printArea(shapePtr); // 输出 Rectangle 的面积
return 0;
}
-
基类与派生类:我们定义了一个基类 Shape 和两个派生类 Circle 和 Rectangle。Shape 类有一个纯虚函数 getArea(),这使得它成为一个抽象基类,不能被直接实例化。
-
虚函数:在基类中,我们使用 virtual 关键字声明了一个虚函数 getArea()。在派生类中,我们使用 override 关键字重写了这个函数。这允许我们在运行时根据对象的实际类型来调用正确的函数。
-
动态多态:在 main() 函数中,我们创建了一个 Circle 对象和一个 Rectangle 对象。然后,我们使用一个基类指针 shapePtr 来指向这两个对象,并调用 printArea() 函数。尽管 printArea() 函数接受一个基类指针,但它能够正确地调用派生类中的 getArea() 函数,这就是动态多态的体现。
-
虚析构函数:在基类中,我们还声明了一个虚析构函数。这是为了防止在通过基类指针删除派生类对象时出现内存泄漏。虽然在这个例子中我们没有直接这样做,但这是一个好的实践。
通过这个例子,我们可以看到动态多态允许我们在运行时根据对象的实际类型来调用相应的方法,从而增加了程序的灵活性和可扩展性。
静态多态
通过模板实现的多态性叫做静态多态,当模板使用具体的类进行实例化,这就是一种多态。
我们将上面例子改写一下,实现如何叫做静态多态:
#include <iostream>
// 模板基类 Shape
template<typename T>
class Shape {
public:
// 计算面积的函数
double getArea() const {
return T::getArea(*this);
}
};
// 模板特化:Circle
template<>
class Shape<Circle> {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// 计算面积的函数
double getArea() const {
return 3.14 * radius * radius;
}
};
// 模板特化:Rectangle
template<>
class Shape<Rectangle> {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
// 计算面积的函数
double getArea() const {
return width * height;
}
};
// 函数,接受一个 Shape 对象,并打印其面积
template<typename T>
void printArea(const Shape<T>& shape) {
std::cout << "Area: " << shape.getArea() << std::endl;
}
int main() {
// 创建 Circle 和 Rectangle 对象
Shape<Circle> circle(5);
Shape<Rectangle> rectangle(4, 6);
// 打印面积
printArea(circle); // 输出 Circle 的面积
printArea(rectangle); // 输出 Rectangle 的面积
return 0;
}
在这个示例中,我们使用了模板特化来定义不同形状的面积计算方法。Shape 类成为了一个模板类,它接受一个类型参数 T,该类型必须提供 getArea 函数的实现。我们通过为 Circle 和 Rectangle 类提供模板特化来实现这一点。 因为静态多态性在编译时确定行为,所以我们没有使用虚函数和继承。在这里,我们通过模板特化来为每个特定类型提供不同的行为。这使得编译器在编译时就能够确定正确的行为,而不是在运行时。
动态和静态多态的分类和对比
动态多态:
- 有界:多态行为类型的接口是由公共基类设计预先确定的。
- 动态:接口绑定在运行时完成(动态)。
- 优点:
-
灵活性:动态多态允许在运行时动态地改变对象的行为。这使得代码更加灵活,可以适应更多的应用场景。
-
扩展性:由于多态行为在运行时确定,因此可以在不修改现有代码的情况下添加新的行为。这有助于提高代码的扩展性。
-
易于理解和使用:动态多态的概念更加直观,易于理解和使用。它符合面向对象编程的核心理念,即“对象的行为由其类型决定”。 需要注意的是,静态多态和动态多态各有其适用场景,选择哪种多态性形式应根据具体的需求和场景来决定。
-
静态多态(模板):
- 无界:多态行为的类型接口不是预先确定的。
- 静态:接口的绑定在编译时完成。
- 优点:
-
编译时优化:静态多态可以在编译时进行优化,提高代码的执行效率。因为所有的多态行为在编译时就已经确定,编译器可以进行一些针对性的优化。
-
类型安全:由于多态行为在编译时就已经确定,因此静态多态可以提供更好的类型安全。这有助于减少运行时的错误。
-
无运行时开销:由于所有的多态行为都在编译时完成,因此在运行时没有额外的开销。
-