C++多态有多种实现方式,在面对对象编程时,采用的是运行期多态,也称动态多态。在泛型编程中,多态基于模板的具现化与函数的重载解析,这种多态在编译期进行,也称编译器多态或者静态多态。
运行期多态
运行期多态归根结底就是类继承的一个特性。我们习惯于抽象出不同功能的对象的共有功能集合,在基类中将这些共有功能声明为虚函数(也称虚接口),然后由子类去重写这些虚函数,以实现多态。
class Animal
{
public :
void Animal();
virtual void shout() = 0;
};
void Animal::Animal()
{
std::cout << "Constructor of Animal" << std::endl;
}
class Dog :public Animal
{
public:
void Dog();
virtual void shout(){ cout << "汪汪!"<<endl; }
};
void Dog::Dog()
{
std::cout << "Constructor of Dog" << std::endl;
}
class Cat :public Animal
{
public:
virtual void shout(){ cout << "喵喵~"<<endl; }
};
class Bird : public Animal
{
public:
virtual void shout(){ cout << "叽喳!"<<endl; }
};
int main()
{
Animal * anim1 = new Dog;
Animal * anim2 = new Cat;
Animal * anim3 = new Bird;
//指针的类型都是基类,通过指针(或引用)调用的接口,
//在运行期确定指针(或引用)所指对象的真正类型,调用该类型对应的接口
anim1->shout();
anim2->shout();
anim3->shout();
//delete 对象
delete anim1;
delete anim2;
delete anim3;
return 0;
}
当某个类声明了虚函数时,编译器为该对象安插一个虚函数表指针,并且为该类对象安插一个虚函数表指针,并且为该类设置一个唯一的虚函数表
如果一个类有一个或者多个成员函数是虚函数,编译器就为这个类创建一个虚函数表。这个表为每一个虚成员函数记录一个指针(内存地址)。指针指向相应成员函数代码的入口地址。如果一个虚函数被继承下来,并且没有改变,那么虚函数表中相应项指向的就是这个函数在父类(或者其他祖先类)的定义。如果一个虚函数有新的定义,那么表中这个虚函数的指针就会指向新的定义。记住,虚函数的属性是会继承的,一旦一个类有一个虚函数表,那么所有它的子孙类也会有一个虚函数表。
一旦我们创建了一个包含虚函数的类的对象,那么C++运行时系统就会自动增加另外一个指针类描述这个存储在内存中的对象。这个指针指向类的虚函数表。当我们使用一个指向该对象的指针来调用成员函数时,运行时系统会使用虚函数表来确定调用哪一个版本的成员函数,而不是根据这个指针的类型。
编译期多态
对于模板参数而言,多态是通过模板具现化和函数重载解析实现的。不同的模板参数调用不同的函数,这就是编译期多态。
class Animal
{
public :
void shout() { cout << "发出动物的叫声" << endl; };
};
class Dog
{
public:
void shout(){ cout << "汪汪!"<<endl; }
};
class Cat
{
public:
void shout(){ cout << "喵喵~"<<endl; }
};
class Bird
{
public:
void shout(){ cout << "叽喳!"<<endl; }
};
template <typename T>
void animalShout(T & t)
{
t.shout();
}
int main()
{
Animal anim;
Dog dog;
Cat cat;
Bird bird;
animalShout(anim);
animalShout(dog);
animalShout(cat);
animalShout(bird);
getchar();
}
在编译之前,函数模板中t.shout()调用的是哪个接口并不确定。在编译期间,编译器推断出模板参数,因此确定调用的shout是哪个具体类型的接口。不同的推断结果调用不同的函数,这就是编译器多态。这类似于重载函数在编译器进行推导,以确定哪一个函数被调用。
总结
静态绑定和动态绑定都是用父类指向子类。区别就是有无虚函数。
如果没有虚函数就是静态绑定,生成一个子类对象,但是当调用函数的时候,是根据指针类型判断,所以仍然会调用父类的函数。
而动态绑定会查询虚函数表,从而调用子类函数。