多态分为两类
1.静态多态:函数重载和运算符重载属于静态多态,复用函数名
2.动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
1.静态多态的函数地址早绑定,编译阶段确定函数地址
2.多态多态的函数地址晚绑定,运行阶段确定函数地址
首先让我们看这段代码:
#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.speak();}void test01() {Cat cat;dospeak(cat);}int main() {test01();system("pause");return 0;
}
结果为:动物在说话
我们想要的是猫在说话,可最后输出的结果却是动物在说话,为什么呢?
因为dospeak函数地址早绑定了,在编译阶段就确定了函数地址
那么我们要怎样做才能让猫说话呢?
非常简单,就是利用动态多态,就是要让dospeak函数的地址晚绑定(在运行阶段进行绑定),我们只要在父类的同名speak函数前面加virtual关键字,现在speak函数就变成了虚函数,就解决了上述问题。
代码如下:
#include <iostream>
using namespace std;class Animal {public:virtual void speak() {cout << "动物在说话" << endl;}
};class Cat : public Animal {public:void speak() {cout << "小猫在说话" << endl;}
};void dospeak(Animal &animal) {animal.speak();}void test01() {Cat cat;dospeak(cat);}int main() {test01();system("pause");return 0;
}
结果为:猫在说话
重写的概念:
1.函数的返回类型相同
2.函数名相同
3.参数列表相同
动态多态的满足条件:
1.有继承关系
2.子类要重写父类的虚函数
动态多态的使用:
父类的指针或引用指向子类的对象
现在我们来看看多态的原理,首先我们看一下下面这段代码:
#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.speak();}void test01() {cout << "size of Animal = " << sizeof(Animal) << endl;}int main() {test01();system("pause");return 0;
}
结果为size of Animal = 1
现在我们在Animal的speak函数前面加上virtual,写成多态的形式,结果又是多少呢?
#include <iostream>
using namespace std;class Animal {public:virtual void speak() {cout << "动物在说话" << endl;}
};class Cat : public Animal {public:void speak() {cout << "小猫在说话" << endl;}
};void dospeak(Animal &animal) {animal.speak();}void test01() {cout << "size of Animal = " << sizeof(Animal) << endl;}int main() {test01();system("pause");return 0;
}
结果为:size of Animal = 4
这说明,加了virtual后,这个类的内部结构发生了改变。
那到底多了什么东西,导致这个类变成了4个字节呢?
结果:
多了指针。
现在让我们看看这其中发生的变化,
首先我们要知道:
vfptr - 虚函数(表)指针
v - virtual
f - function
ptr - pointer
vftable - 虚函数表
v - virtual
f - function
table - table
现在让我们看看Animal类内部结构:
Cat内部结构:
当子类重写父类的虚函数时,子类中的虚函数表内部会替换成子类的虚函数地址
也就是说,当我们往父类Animal的speak函数前面加上virtual时,Cat的内部结构就会变成这样:
当父类中的指针或者引用指向子类对象的时候,就发生了多态。
我们就相当于写了这样一段代码:
Animal &animal = cat;
animal,speak();
当我们调用animal的speak函数时,由于指向的是Cat对象,所以编译器会从Cat的虚函数表中找speak函数,就相当于在运行阶段发生了多态。
原理:
由于我们写了一个虚函数,类的内部发生了结构的改变,多了一个虚函数表指针,指向虚函数表,虚函数表内部写的是虚函数的函数入口地址,当子类重写了虚函数表,会把自身的虚函数表给替换掉,这里的替换就是Cat类中发生的替换。