多态性是一个接口多种实现,是面向对象的核心
正常情况下,函数的地址在编译过程中就已经确定,在运行中无法更改,但是通过晚绑定机制可以在运行阶段在确定函数的地址,从而实现多态的效果。
非多态继承情况下,早绑定示例
#include <iostream>
using namespace std;
class Animal
{
public:
/*virtual*/ void CreateAnimal();
/*virtual void Sound();*/
};
void Animal::CreateAnimal()
{
std::cout << "--- Animal---" << std::endl;
}
Dog继承与Animal,并且对CreateAnimal函数进行重写
#include "Animal.h"
class DogAnimal :
public Animal
{
public:
/* virtual*/ void CreateAnimal();
/* virtual void Sound();*/
};
void DogAnimal::CreateAnimal()
{
std::cout << "--- Dog---" << std::endl;
}
调用类对象打印函数
void fun(Animal* animal)
{
animal->CreateAnimal();
}
int main()
{
Animal* animal = new Animal;
Animal* doganimal = new DogAnimal;
fun(animal);
fun(doganimal);
delete animal;
animal = nullptr;
delete doganimal;
doganimal = nullptr;
}
我们发现在调用过程中,结果调用的都是基类的 CreateAnimal 。。。
这就是早绑定,函数参数本身为Animal,在编译过程中对其函数地址进行绑定,此时就算Dog类型成功转换,但是调用的仍旧是基类。
多态的实现
将CreateAnimal定义为虚函数,现在对虚函数进行重写操作。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual ~Animal();
virtual void CreateAnimal();
virtual void Sound();
};
#include "Animal.h"
class DogAnimal :
public Animal
{
public:
virtual void CreateAnimal();
virtual void Sound();
};
调用CreateAnimal函数,以及打印虚函数表中函数的地址。
int main()
{
Animal* animal = new Animal;
Animal* doganimal = new DogAnimal;
fun(animal);
fun(doganimal);
LL* pint = (LL *)animal;
LL* vptr = (LL*)(*pint);
std::cout << "虚函数表地址:" << *vptr << std::endl;
std::cout <<"Animal::CreateAnima:" << vptr[0] << std::endl;
std::cout <<"Animal::Soun:" << vptr[1] << std::endl;
LL* pint2 = (LL*)doganimal;
LL* vptr2 = (LL*)(*pint2);
std::cout << "虚函数表地址:" << *vptr2 << std::endl;
std::cout << "DogAnimal::CreateAnimal:" << vptr2[0] << std::endl;
std::cout << "DogAnimal::Sound:" << vptr2[1] << std::endl;
delete animal;
animal = nullptr;
delete doganimal;
doganimal = nullptr;
}
通过打印结果发现,重写的虚函数 CreaterAnImal 拥有了属于自己的地址,未重写的函数 Sound ,依旧是使用父类虚函数的地址,但是调用时,DogAnimal调用的是自己的CreateAnimal函数,也就是说在这次示例中,CreateAnimal函数的地址并没有在编译阶段就确定下来(否则的话就会调用父类的CreateAnimal函数),而是在运行时去确定对象的类型,根据对象中虚指针指向的虚表中的函数的地址来确定调用哪个函数
正常情况下,在编译过程中就已经确定了对象调用的函数的地址,要解决这个问题就要使用迟绑定(late binding)技术。当编译器使用迟绑定时,就会在运行时再去确定对象的类型以及正确的调用函数。而要让编译器采用迟绑定,就要在基类中声明函数时使用virtual关键字,这样的函数我们称为虚函数。一旦某个函数在基类中声明为virtual,那么在所有的派生类中该函数都是virtual,而不需要再显式地声明为virtual。
类的多态性,是指用虚函数和延迟绑定来实现的。函数的多态性是函数的重载。