前面内容学习了继承,在面向对象三大核心概念,我们学习完了封装,继承,剩下一个多态。这篇开始学习多态的基本概念和C++中多态基本使用。
1. 多态分两类
静态多态:函数重载和运行符重载属于静态多态,服用函数名
动态多态:派生类和虚函数实现运行时多态
这块的知识点和Java中思想是一样的。例如Java中经常遇到的面试题,你能说一下Java中的多态吗。一般来说标准答案是这样的:
多态是面向对象三大核心,多态就是有多种形态,分两种情况。1)在单个类中,多态的表现为函数的重载,就是共用一个函数名,参数个数,参数类型不同,这些同名函数完成不同的功能,这就是单个类中的多态表现。 2)在继承中,子类继承父类,子类可以修改父类中已有的函数的行为,这个动作叫做重写。就是子类重写父类的方法,这也是多态。然后适当举例,工作中用到多态的例子,这样回答,面试官基本都满意,很清楚表达你对面向对象三大特点的掌握。
在C++中,多态也是这两点。也分成静态多态和动态多态。结合上面Java中知识,理解上面静态多态和动态多态两类不难。第一类也是函数重载,C++中多了一个运算符重载。派生类也就是子类继承中重写父类方法,又多了一个虚函数而已。
2. 静态多态和动态多态区别:
静态多态的函数地址早绑定-编译阶段确定函数地址
动态多态的函数地址晚绑定-运行阶段确定函数的地址
这两点区别,没什么解释的,一个发生在编译阶段,一个发生在运行阶段。
3.静态绑定的代码示例
先看看这段代码,理解下什么是静态绑定
#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");
}
在看运行结果之前,肯定会好奇,打印的是 “动物在叫” 还是猫在叫。在方法doSpeak中,参数是一个Animal的对象的引用。但是我们在test01中调用doSpeak()函数传入的是一个cat对象。
看到上面输出,输出的Animal基类中的speak(), 现象是传入的是一个派生类(Cat类)对象,执行的方法却是基类的方法,这个就是动态绑定,在编译阶段,编译器就把这个标记为执行基类中的speak()方法,所以输出调用的是基类中的speak方法。
本意上我们创建一个猫对象,应该是想打印“猫在叫”。也就是我们不希望进行静态绑定,而是进行动态绑定,也就是在运行的时候,动态绑定函数地址,调用派生类中方法,下面使用virtual来实现动态绑定。
4.虚函数实现动态绑定
这里虚函数就是在基类中成员函数前面加上关键字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()
{
Cat cat;
doSpeak(cat);
}
int main()
{
test01();
system("pause");
}
就添加了一个virtual关键字,让speak函数变成虚函数,输出的结果却是 “猫在叫”。
在test01函数中doSpeak(xx),在编译的时候不能进行静态绑定,确定函数地址。而是需要在运行的时候,根据传入的参数,例如这里是猫,或者传入的是狗,它就会动态去找猫里面的speak函数或狗中的speak函数,这个就叫动态搬绑定,动态确定函数speak()的地址。
总结:
动态多态满足条件
1)有继承关系
2)子类重写父类的虚函数
动态多态的使用:
父类的指针或引用,指向子类对象。在上面代码doSpeak(Animal &animal) 相当于 Animal & animal = cat; 当前还没有用指针写一个例子,后面学习。