1 什么是多态
多态(Polymorphism)是C++面向对象编程的三大特性(“封装”、“继承”、“多态”)之一。它允许同一个接口在不同对象上展现不同的行为。多态的核心价值在于接口复用、代码解耦、扩展性强,实现了“对扩展开放,对修改封闭”的设计原则。
C++中的多态分为两类:
静态多态(编译时多态):如函数重载、模板、运算符重载等,编译期决定调用关系。
动态多态(运行时多态):如虚函数、接口继承等,运行期决定调用关系。
2 动态多态(运行时多态)
2.1 动态多态的原理
动态多态依赖于虚函数和指针/引用。其核心机制是虚函数表(vtable)。每个含虚函数的类都有一张vtable,表中存储虚函数的地址。通过基类指针或引用调用虚函数时,实际会查表并调用派生类的实现。
2.2 虚函数与重写
示例:
#include <iostream>
// 动物基类
class Animal {
public:
// 虚函数:允许子类提供自己的实现
virtual void Speak() const {
std::cout << "Animal makes a sound." << std::endl;
}
// 虚析构函数:确保delete基类指针时能正确析构派生类对象
virtual ~Animal() = default;
};
// 狗类,继承自Animal
class Dog : public Animal {
public:
// 重写Speak方法
void Speak() const override {
std::cout << "Dog barks." << std::endl;
}
};
// 猫类,继承自Animal
class Cat : public Animal {
public:
void Speak() const override {
std::cout << "Cat meows." << std::endl;
}
};
// 接口演示
void MakeAnimalSpeak(const Animal& animal) {
animal.Speak(); // 实际调用的版本取决于animal的真实类型
}
int main() {
Dog dog;
Cat cat;
MakeAnimalSpeak(dog); // 输出:Dog barks.
MakeAnimalSpeak(cat); // 输出:Cat meows.
return 0;
}
virtual关键字声明虚函数,子类用override修饰重写。通过基类引用
MakeAnimalSpeak调用Speak,实现了接口统一和动态分派。
2.3 纯虚函数与抽象类
纯虚函数用来定义接口,不能被实例化,只能被继承实现。
// 图形基类,抽象类
class Shape {
public:
// 纯虚函数:无实现,派生类必须实现
virtual double Area() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
explicit Circle(double r) : radius_(r) {}
double Area() const override {
return 3.14159 * radius_ * radius_;
}
private:
double radius_;
};
纯虚函数
= 0,Shape不可实例化。Circle实现了Area,才能被实例化。
2.4 虚析构函数的重要性
若基类指针指向派生类对象,析构时需虚析构,否则只会调用基类析构,造成资源泄露。
class Base {
public:
virtual ~Base() { std::cout << "Base destroyed" << std::endl; }
};
class Derived : public Base {
public:
~Derived() override { std::cout << "Derived destroyed" << std::endl; }
};
int main() {
Base* ptr = new Derived();
delete ptr; // 先调用Derived析构,再调用Base析构
return 0;
}
virtual ~Base()保证delete时能调用到~Derived()。
2.5 动态多态的适用场景
接口统一:如操作不同类型的设备、文件、UI控件等。
便于扩展:新增功能只需继承基类并实现虚函数。
设计模式:如工厂、策略、观察者等。
2.6 动态多态的注意事项
构造函数不可为虚函数:对象构造时类型未定。
避免对象切片:用指针/引用传递,值传递会丢失子类特性。
虚函数的默认参数由静态类型决定:避免混淆。
3 静态多态(编译时多态)
3.1 静态多态的定义
静态多态在编译期决定调用关系,常见有:
函数重载(Overloading)
运算符重载(Operator Overloading)
模板(Templates)
CRTP(Curiously Recurring Template Pattern)
3.2 函数重载
同名函数不同参数,编译器自动选择合适的重载版本。
#include <iostream>
void Print(int value) {
std::cout << "int: " << value << std::endl;
}
void Print(double value) {
std::cout << "double: " << value << std::endl;
}
int main() {
Print(42); // int: 42
Print(3.14); // double: 3.14
return 0;
}
Print(int)和Print(double)由编译器根据参数类型自动选择。
3.3 运算符重载
为自定义类型赋予类似内置类型的运算能力。
#include <iostream>
class Vector2D {
public:
Vector2D(double x, double y) : x_(x), y_(y) {}
// 重载+运算符
Vector2D operator+(const Vector2D& other) const {
return Vector2D(x_ + other.x_, y_ + other.y_);
}
void Print() const {
std::cout << "(" << x_ << ", " << y_ << ")" << std::endl;
}
private:
double x_;
double y_;
};
int main() {
Vector2D a(1.0, 2.0);
Vector2D b(3.0, 4.0);
Vector2D c = a + b;
c.Print(); // (4, 6)
return 0;
}
通过重载
operator+,Vector2D对象可以直接相加,增强了代码的可读性和可维护性。
3.4 模板与泛型编程
模板允许编写与类型无关的代码,编译器根据实际类型实例化代码。
#include <iostream>
#include <string>
// 函数模板
template<typename T>
void Print(const T& value) {
std::cout << value << std::endl;
}
int main() {
Print(123); // 123
Print(3.14); // 3.14
Print("hello"); // hello
return 0;
}
类模板示例:
#include <iostream>
template<typename T>
class Box {
public:
explicit Box(const T& value) : value_(value) {}
void Print() const {
std::cout << value_ << std::endl;
}
private:
T value_;
};
int main() {
Box<int> int_box(10);
Box<std::string> str_box("C++");
int_box.Print(); // 10
str_box.Print(); // C++
return 0;
}
Box<T>可存储任意类型的数据,类型安全且无需重复实现。
3.5 CRTP(奇异递归模板模式)
CRTP是一种高级静态多态技术,允许在编译期实现类似虚函数的行为,无虚表开销。
#include <iostream>
// 基类模板,T为派生类类型
template <typename T>
class Base {
public:
void Interface() const {
// 强制将this转换为派生类指针,调用派生类的实现
static_cast<const T*>(this)->Implementation();
}
};
// 派生类
class Derived : public Base<Derived> {
public:
void Implementation() const {
std::cout << "Derived Implementation" << std::endl;
}
};
int main() {
Derived d;
d.Interface(); // Derived Implementation
return 0;
}
CRTP在编译期绑定,实现了零运行时开销的多态。
3.6 静态多态的适用场景
泛型算法与容器:如STL。
性能敏感场合:如数值计算、图像处理。
类型集合固定:如编译期已知所有类型。
4 动态多态与静态多态对比
举例说明:
静态多态适合已知类型、性能敏感的场景,如STL容器、算法库。
动态多态适合类型未知、需运行时扩展的场景,如插件系统、UI组件库。
5 多态与设计模式实战
多态是设计模式的基础,工厂模式即是经典应用。
#include <memory>
#include <string>
// 工厂类
class AnimalFactory {
public:
static std::unique_ptr<Animal> CreateAnimal(const std::string& type) {
if (type == "dog") {
return std::make_unique<Dog>();
} else if (type == "cat") {
return std::make_unique<Cat>();
}
return nullptr;
}
};
int main() {
auto animal = AnimalFactory::CreateAnimal("dog");
if (animal) {
animal->Speak(); // Dog barks.
}
return 0;
}
工厂模式结合动态多态,屏蔽了对象创建细节,便于扩展新类型。
6 总结与建议
多态是C++高内聚、低耦合的核心特性,分为静态多态和动态多态。
动态多态依赖虚函数与继承,适合接口抽象、运行时扩展。
静态多态依赖模板、重载、CRTP,适合泛型和高性能场景。
合理选择多态类型:性能敏感选静态多态,灵活扩展选动态多态。
注意虚析构、对象切片、默认参数等细节问题,确保代码健壮。
多态是设计模式的基础,理解其原理和用法,是成为C++高手的必经之路。
评论区