多态是C++面向对象三大特性之一
多态分为两类
- 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
- 动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
下面通过案例进行讲解多态
class Animal
{
public:
//Speak函数就是虚函数
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void DoSpeak(Animal & animal)
{
animal.speak();
}
//
//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
void test01()
{
Cat cat;
DoSpeak(cat);
Dog dog;
DoSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
这个代码通过动物、猫、狗的类层次结构,很好地展示了C++多态的使用方式和效果。
主要逻辑:
-
Animal类中有一个虚函数speak
-
Cat和Dog作为子类继承Animal,并override了speak函数
-
DoSpeak这个普通函数接收一个Animal引用作为参数
-
在test01中,分别创建Cat和Dog对象,传递给DoSpeak
-
在DoSpeak内部,调用的是Cat和Dog独有的speak实现
通过这种方式, DoSpeak函数可以适用于任何继承自Animal的对象,都会调用对应的speak实现。
满足了“定义统一接口,具体实现 deferred 到子类中”的多态特性。
这种作用机制提高了代码的灵活性和可扩展性,非常值得学习。
再举一个例子:
C++ 中多态的主要作用是实现运行时 polymorhphic 行为——允许基于具体对象类型调用对应的方法。
多态常用于:
-
处理相关但是行为有区别的类层次结构
-
实现可复用和灵活的代码
一个典型例子是工资系统:
class Employee {
public:
virtual void pay() {
// 付款逻辑
}
};
class Manager : public Employee {
public:
void pay() override {
// 管理者有不同的付款逻辑
}
};
void payrollSystem(vector<Employee*> employees) {
for(auto e : employees) {
e->pay(); // 调用各自的pay方法
}
}
这里通过基类指针可以很方便地遍历多种类型的员工,运行时会根据实际对象类型调用他们各自的pay方法实现,无需员工类型判断,这样实现灵活多变。
除了工资系统,多态在图形编程里也有很好的应用。这里有一个使用多态绘制不同图形的例子:
class Shape {
public:
virtual void draw() = 0;
};
class Rectangle : public Shape {
public:
void draw() override {
// 绘制矩形
}
};
class Circle : public Shape {
public:
void draw() override {
// 绘制圆形
}
};
void drawAllShapes(list<Shape*> shapes) {
for(auto s : shapes) {
s->draw(); // 调用每个shape的draw绘制
}
}
这里通过定义一个基类Shape,然后矩形类,圆形类继承实现自己的draw方法。
在绘制所有图形时,就可以统一使用Shape指针遍历,调用每种Shape子类的draw方法。
不需要判断具体类型,实现解耦可扩展。
这种基于多态实现的图形编程架构也非常常见,可以方便新增更多类型的图形,而不影响已有代码。
这就是多态性在行为扩展与复用上的优势。