多态是面向对象的三大特性之一:封装、继承、多态。
多态分为两类:
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名。
- 动态多态:派生类和虚函数实现运行时多态。
静态多态和动态多态的区别:
- 静态多态的函数地址 早 绑定 —— 编译阶段确定函数地址。
- 动态多态的函数地址 晚 绑定 —— 运行阶段确定函数地址。
#include<iostream>
using namespace std;
class Animal
{
public:
void Speak()
{
cout << "动物会说话" << endl;
}
};
class Cat :public Animal
{
public:
void Speak()
{
cout << "小猫在说话" << endl;
}
};
void doSpeak(Animal& animal) // Animal& animal = cat;
{
animal.Speak();
}
void test01()
{
Cat cat;
doSpeak(cat); // 输出:"动物会说话" 相当于把cat传递给参数Animal& animal。
}
int main()
{
test01();
system("pause");
return 0;
}
输出:不是“小猫在说话”。
输出:"动物会说话"
当我们通过doSpeak调用时:将cat传递给Animal& animal;
等价于:
Animal& animal = cat;
C++中允许父子之间的类型转换。不需要做强制类型转换。
父类的引用或者指针可以直接指向子类对象。
为什么输出不是我们期望的cat呢?
void doSpeak(Animal& animal) // Animal& animal = cat;
{
animal.Speak();
}
原因就是现在函数的地址是早绑定:在编译阶段就能确定函数的地址:他绑定的地址是animal中的Speak。而不是运行时传参传入的子类Cat中的Speak。所以调用这个函数,不论我们传入任何Animal子类的动物,都不会变,都是“动物会说话”。
为了达到我们:输入任一动物,就输出这一动物的Speak。就需要在运行时,动态多态。也就是说,让函数地址 晚 绑定。
如果想要执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,也就是地址晚绑定。
如何实现?
在父类Speak返回类型之前加上关键字:virtual;此时这个函数被称为虚函数。此时的代码就可以实现动态多态效果。
#include<iostream>
using namespace std;
class Animal
{
public:
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& animal = cat;
{
animal.Speak();
}
void test01()
{
Cat cat;
doSpeak(cat); // 输出:"动物会说话" 相当于把cat传递给参数Animal& animal。
Dog dog;
doSpeak(dog);
}
int main()
{
test01();
system("pause");
return 0;
}
动态多态满足条件:
1、有继承关系。
2、子类重写父类的虚函数。(类似C#中的override)。子类中的virtual可写可不写。
3、父类中虚函数:virtual。
重写:函数返回值类型、函数名、参数列表 完全相同。
动态多态使用条件
父类的指针或者引用指向子类的对象。里氏转换法。
Animal& animal = cat;
总结:
子类通过重写父类虚函数,来实现动态多态。调用时,父类的指针或者引用指向子类的对象。
C#中设计里氏转换法则:可以参照。
- 子类可以赋值给父类
- 如果 一个父类装的是子类的对象,那么父类可以直接强转成子类。
- 父类数组可以装多种子类。