在面试中常常会有面试官问道,c++的多态的实现机制。那么,多态到底该如何实现呢?
多态的简单介绍
一般来说,多态分为两种,静态多态
和动态多态
。静态多态也称编译时多态,主要包括模板
和重载
。而动态多态则是通过类的继承和虚函数来实现,当基类和子类拥有同名同参同返回的方法,且该方法声明为虚方法,当基类对象,指针,引用指向的是派生类的对象的时候,基类对象,指针,引用在调用基类的虚函数,实际上调用的是派生类函数。这就是动态多态。
静态多态的实现
静态多态靠编译器来实现,简单来说就是编译器对原来的函数名进行修饰,在c语言中,函数无法重载,是因为,c编译器在修饰函数时,只是简单的在函数名前加上下划线"_" ,不过从gcc编译器编译之后发现函数名并不会发生变化。而c++编译器不同,它根据函数参数的类型,个数来对函数名进行修饰,这就使得函数可以重载,同理,模板也是可以实现的,针对不同类型的实参来产生对应的特化的函数,通过增加修饰,使得不同的类型参数的函数得以区分。
以下段程序为例
#include <iostream>
using namespace std;
template <typename T1, typename T2>
int fun(T1 t1, T2 t2){}
int foofun(){}
int foofun(int){}
int foofun(int , float){}
int foofun(int , float ,double){}
int main(int argc, char *argv[])
{
fun(1, 2);
fun(1, 1.1);
foofun();
foofun(1);
foofun(1, 1.1);
foofun(1, 1.1, 1.11);
return 0;
}
经过编译之后:
只选取main函数部分来看:
可以发现,调用的函数名均发生了变化,都加了相应的修饰,使得调用的函数是不一样的,静态多态就是如此。
动态多态的实现
声明一个类时,如果类中有虚方法,则自动在类中增加一个虚函数指针,该指针指向的是一个虚函数表,虚函数表中存着每个虚函数真正对应的函数地址。动态多态采用一种延迟绑定技术,普通的函数调用,在编译期间就已经确定了调用的函数的地址,所以无论怎样调用,总是那个函数,但是拥有虚函数的类,在调用虚函数时,首先去查虚函数表,然后在确定调用的是哪一个函数,所以,调用的函数是在运行时才会确定的。
在声明基类对象时,如果基类拥有虚函数,就会自动生成一个虚函数指针,这个虚函数指针指向基类对应的虚函数表。在声明派生类对象时,虚函数指针指向的是派生类对应的虚函数表。在对象被创建之后(以指针为例),无论是基类指针还是派生类指针指向这个对象,虚函数表是不会改变的,虚表指针的指向也是不会变的。
以下段程序为例:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void fun()
{
cout << "this is base fun" << endl;
}
};
class Derived : public Base
{
public:
void fun()
{
cout << "this is Derived fun" << endl;
}
};
int main(int argc, char *argv[])
{
Base b1;
Derived d1;
Base *pb = &d1;
Derived *pd = (Derived *)&b1;
b1.fun();
pd->fun();
d1.fun();
pb->fun();
return 0;
}
运行结果如下:
从结果可以看出,当一个对象被创建之后,在调用虚函数的时候,无论是派生类指针还是基类指针指向这个对象,调用虚函数的结果是一样的。因为,虚函数表是不变。当然,有可能在多继承中会有多个虚函数表从而导致函数调用时调用不同的虚函数表,这里不做考虑。