C++面向对象核心:什么是多态(Polymorphism)
——同一个指令,不同对象表现不同
对于小学生,可以先记一句话:
多态就是:同一个动作,让不同的对象表现出不同的结果。
例如:
我们说:
“动物叫!”
不同动物:
狗:汪汪叫
猫:喵喵叫
鸭子:嘎嘎叫
命令一样:
叫但是结果不同。
这就是:
多态。
一、C++面向对象三大特点
我们之前学习:
1. 封装
把数据和操作放一起。
例如:
class Student { private: int score; };保护数据。
2. 继承
子类拥有父类功能。
例如:
class Dog:public Animal { };3. 多态
同一个接口,不同表现。
例如:
Animal | | Dog Cat 叫()调用:
叫()结果:
狗:汪汪 猫:喵喵二、先看没有多态的问题
我们写动物:
class Animal { public: void speak() { cout<<"动物叫"<<endl; } };狗:
class Dog:public Animal { };猫:
class Cat:public Animal { };现在:
Dog d; d.speak();输出:
动物叫但是:
我们希望:
汪汪怎么办?
三、让子类重新定义函数
我们可以:
class Dog:public Animal { public: void speak() { cout<<"汪汪"<<endl; } };猫:
class Cat:public Animal { public: void speak() { cout<<"喵喵"<<endl; } };现在:
Dog d; d.speak();输出:
汪汪四、但是这还不是多态
为什么?
看:
Dog d; Cat c; d.speak(); c.speak();编译器知道:
你是Dog。
你是Cat。
所以直接调用对应函数。
这叫:
函数重定义(隐藏)
不是多态。
五、真正的多态需要什么?
C++多态核心:
两个条件:
条件1:
父类有虚函数:
virtual条件2:
用父类指针或引用指向子类对象。
六、虚函数实现多态
完整代码:
#include<iostream> using namespace std; class Animal { public: virtual void speak() { cout<<"动物叫"<<endl; } }; class Dog:public Animal { public: void speak() { cout<<"汪汪"<<endl; } }; class Cat:public Animal { public: void speak() { cout<<"喵喵"<<endl; } }; int main() { Animal* p; Dog dog; Cat cat; p=&dog; p->speak(); p=&cat; p->speak(); }输出:
汪汪 喵喵七、这里发生了什么?
重点来了。
代码:
Animal* p;表示:
一个动物指针。
然后:
p=&dog;虽然:
p类型:
Animal*但是:
实际指向:
Dog调用:
p->speak();C++会问:
p真正指向谁?
发现:
Dog于是调用:
Dog::speak()后来:
p=&cat;现在:
p真正指向:
Cat于是调用:
Cat::speak()这就是:
一个父类指针,根据实际对象不同,调用不同函数。
八、生活中的例子
假设学校有:
老师 | +--数学老师 | +--英语老师学校系统只保存:
Teacher*上课:
teach();数学老师:
输出:
讲数学英语老师:
输出:
讲英语系统不用知道:
到底是哪种老师。
只需要:
teacher->teach();这就是多态的价值。
九、为什么需要多态?
假设动物园:
有:
100只动物如果不用多态:
你可能写:
if(type=="dog") { dog.speak(); } else if(type=="cat") { cat.speak(); }动物越来越多:
代码越来越复杂。
有多态:
统一:
Animal* animals[100]; animals[i]->speak();每个动物自己决定怎么叫。
十、多态的底层秘密:虚函数表
这是进阶知识。
当类有:
virtual编译器会给对象增加:
虚函数表指针 vptr例如:
Dog对象:
+----------------+ | vptr | +----------------+ | Dog数据 | +----------------+vptr指向:
虚函数表 speak() ---> Dog::speak()所以:
p->speak();程序通过:
vptr ↓ 虚函数表 ↓ 找到真正函数这叫:
动态绑定。
十一、静态绑定和动态绑定
这是考试常考。
普通函数:
void speak()调用时:
编译阶段决定。
叫:
静态绑定
虚函数:
virtual void speak()运行时决定。
叫:
动态绑定
十二、多态和继承的关系
多态不是单独存在的。
通常:
继承 + 虚函数 + 父类指针 = 多态十三、为什么析构函数经常写virtual?
例如:
class Animal { public: virtual ~Animal() { } };为什么?
看:
Animal* p=new Dog; delete p;如果析构函数不是虚函数:
可能:
只调用:
Animal析构Dog自己的资源没有释放。
如果:
virtual ~Animal()会:
Dog析构 ↓ Animal析构所以:
有继承关系的父类,析构函数通常应该写成虚函数。
十四、记忆口诀
继承:
孩子拥有爸爸能力多态:
同一个命令 不同对象 不同结果C++实现多态:
父类指针 + 子类对象 + virtual函数 = 多态十五、最后总结一张图
Animal | ---------------- | | Dog Cat Animal* p; p=&dog; p->speak(); ↓ 汪汪 p=&cat; p->speak(); ↓ 喵喵一句话总结:
继承解决“是不是一种”的问题,组合解决“有没有”的问题,多态解决“同一个动作,不同对象怎么做不同事情”的问题。
掌握多态以后,C++面向对象三大核心:
封装 → 继承 → 多态
就完整串起来了。