什么是多态?C++多态是如何实现的?
答:所谓多态,就是同一个函数名具有多种状态,或者说一个接口具有不同的行为;C++的多态分为编译时多态和运行时多态,编译时多态是通过重载和模板来实现的,运行时多态通过继承和虚函数来实现的。
多态按字面的意思就是多种形态。同一个事物在不同场景下的多种形态,即调用同一函数会产生不同的行为。
C++多态又分为两种编译期多态和运行期多态:
1.在面向对象C++编程中,多态是面向对象三大特性之一(封装,继承,多态),他的原理是通过虚函数机制,在运行期间通过虚函数表指针与虚函数表去确定该虚函数的真正实现,所以这种多态被称为运行期多态。
2.在泛型编程,多态基于template的巨现化与函数的重载解析。这种多态在编译器进行,所以这种多态被称为编译期多态。
3.函数重载解析也是编译器多态。
运行期多态
程序在运行的时候,动态找到被调用函数的入口地址,从而确定调用哪个函数。
运行期多态的实现机制很简单,在基类定义一个虚函数接口(virtual),子类继承基类并且重写虚函数,就能实现多态了。
下面看一个例子:
基类是Human 具有Attack虚函数
子类有Hero和Enmey,子类各自重写了Attack虚函数
在main函数分别以基类Human New出这三个对象,并调用Attack ,结果为:虽然他们都是Human但是他们的Attack函数的结果大相径庭,这就实现了简单的多态。
#include <iostream>
using namespace std;
class Human
{
public:
Human()
{
}
// 父类的虚函数Attack()
virtual void Attack()
{
cout << "Human Attack." << endl;
}
};
class Hero: public Human
{
public:
Hero ()
{
}
// 子类英雄 重写了 父类的 虚函数Attack()
void Attack()
{
cout << "Hero Attack." << endl;
}
};
class Enemy: public Human
{
public:
Enemy ()
{
}
// 子类敌人 重写了 父类的 虚函数Attack()
void Attack()
{
cout << "Enemy Attack." << endl;
}
};
int main()
{
Human* human=new Human();
Human* hero=new Hero();
Human* enemy = new Enemy();
human->Attack(); // 父类调用攻击
hero->Attack(); // 英雄调用攻击
enemy->Attack(); // 敌人调用攻击
return 0;
}
运行结果
运行期多态的实现依赖于虚函数机制。
「多态」的关键在于通过 基类指针或引用 调用一个 虚函数 时,编译时不能确定到底调用的是基类还是派生类的函数,运行时才能确定。
当某个类声明了虚函数时,编译器将为该类对象的一个虚函数表指针,并为该类设置一张唯一的虚函数表,虚函数表中存放的是该类虚函数地址。运行期间通过虚函数表指针与虚函数表去确定该类虚函数的真正实现。(你用sizeof可以验证他会多出8字节,这8字节就是指向虚函数表的指针,虚函数表列出了虚函数的地址)
编译器多态
编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作
通过模板和函数重载实现,相比动态多态不需派生关系。
看下面的例子:
#include <iostream>
using namespace std;
class Human
{
public :
void Attack()
{
cout << "Human Attack." << endl;
};
};
class Hero
{
public:
void Attack()
{
cout << "Hero Attack." <<endl;
}
};
class Enmey
{
public:
void Attack()
{
cout << "Enmey Attack." <<endl;
}
};
template <typename T>
void Attack(T & t)
{
t.Attack();
}
int main()
{
Human human;
Hero hero;
Enmey enemy;
Attack(human); // 人类调用攻击
Attack(hero); // 英雄调用攻击
Attack(enemy); // 敌人调用攻击
return 0;
}
运行结果:
在编译之前,函数模板中t.Attack()调用的是哪个接口并不确定。在编译期间,编译器推断出模板参数,因此确定调用的Attack是哪个具体类型的接口。不同的推断结果调用不同的函数,这就是编译器多态。这类似于重载函数在编译器进行推导,以确定哪一个函数被调用。