原文见www.louhang.xin
多态是C++中极为重要的一部分,前几篇博客我回顾了继承,菱形虚拟继承等,今天则要重新温估一下多态。
多态(Polymorphism):多种形态,在面向对象编程过程中,接口的多种不同的实现方式即为多态。归根到底实则为可以将子类类型的指针或对象赋给父类的指针或引用,进而通过父类的指针或引用来完成对重写的函数的不同方式的调用。
多态通过对统一操作作用于不同的对象,可以产生不同的 解释,产生不同的执行结果。
在C++中可以通过虚函数(见菱形继承博客),抽象类,模板来实现多态。
虚函数的重写:当在子类的定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写(也称覆盖)了父类的这个虚函数。
上面程序的运行结果如下:
那么问题来了,多态情况下,虚函数是如何存放的?
是不是如菱形虚拟继承那样存在一个虚基表来存放呢?
多态对象模型:
虚函数表:存在多态时,会产生一张类似虚基表的表,名为虚表(虚函数表),虚表内部存储的就是虚函数的地址,这张表解决了继承,虚函数重写的问题。在有虚函数的对象实例中都存在一张虚表,虚表就类似一个灯塔,指明了应该调用的虚函数。
单继承模型:
代码片段
#include
using namespace std;
//单继承模型
class Father
{
public:
virtual void fun1()
{
cout << "Father::fun1()" << endl;
}
virtual void fun2()
{
cout << "Father::fun2()" << endl;
}
protected:
int _a = 10;
};
class Son : public Father
{
public:
virtual void fun2()
{
cout << "Son::fun2()" << endl;
}
virtual void fun3()
{
cout << "Son::fun3()" << endl;
}
protected:
int _b = 20;
};
void Fun(Father& f)
{
f.fun2();
}
void test()
{
Father a;
Son b;
Fun(a);
Fun(b);
}
int main()
{
test();
system("pause");
return 0;
}
通过在监视窗口的观察来看:
我们可以看到在基类内部存在着一个地址,而这个地址指向了一张表,既虚表,虚表内部存储的正是虚函数。但是我们会发现派生类虚函数fun3却不在此表内部。实际上,fun3也在此表内部,只不过编译器做了优化,没有在监视窗口显示出来而已。
我们可以在内存中观察,也可以通过书写函数将虚表内的地址打印出来。
内存中观察:
#include
using namespace std;
class Father
{
public:
virtual void fun1()
{
cout << "Father::fun1()" << endl;
}
virtual void fun2()
{
cout << "Father::fun2()" << endl;
}
private:
int _a = 10;
};
class Son : public Father
{
public:
virtual void fun2()
{
cout << "Son::fun2()" << endl;
}
virtual void fun3()
{
cout << "Son::fun3()" << endl;
}
protected:
int _b = 20;
};
typedef void(*FUNC) ();
void PrintVTable(int* VTable)
{
cout << " 虚表地址>" << VTable << endl;
for (int i = 0; VTable[i] != 0; ++i)
{
printf(" 第%d个虚函数地址 :0X%x,->", i, VTable[i]);
FUNC f = (FUNC)VTable[i];
f();
}
cout << endl;
}
void test()
{
Father a;
Son b;
int* VTable1 = (int*)(*(int*)&a);
int* VTable2 = (int*)(*(int*)&b);
PrintVTable(VTable1);
PrintVTable(VTable2);
}
int main()
{
test();
system("pause");
return 0;
}
程序运行结果为:
可以看到在创建的两个对象中都存在着虚表,而正是通过对于不同虚表中的重写的虚函数的调用来实现多态的。
2.多继承模型:
代码如下
#include
using namesapce std;
class Father
{
public:
virtual void fun1()
{
cout << "Father::fun1()" << endl;
}
virtual void fun2()
{
cout << "Father::fun2()" << endl;
}
protected:
int _a = 10;
};
class Mother
{
public:
virtual void fun1()
{
cout << "Mother::fun1()" << endl;
}
virtual void fun3()
{
cout << "Mother::fun3()" << endl;
}
protected:
int _b = 20;
};
class Son : public Father, public Mother
{
public:
virtual void fun1()
{
cout << "Son::fun1()" << endl;
}
virtual void fun4()
{
cout << "Son::fun4()" << endl;
}
protected:
int _c = 30;
};
typedef void(*FUNC) ();
void PrintVTable(int* VTable)
{
cout << " 虚表地址>" << VTable << endl;
for (int i = 0; VTable[i] != 0; ++i)
{
printf(" 第%d个虚函数地址 :0X%x,->", i, VTable[i]);
FUNC f = (FUNC)VTable[i];
f();
}
cout << endl;
}
void MoreTest()
{
Son c;
int* VTable1 = (int*)(*(int*)&c);
int* VTable2 = (int*)(*((int*)&c + sizeof (Father) / 4));
PrintVTable(VTable1);
PrintVTable(VTable2);
}
int main()
{
//test1();
MoreTest();
system("pause");
return 0;
}
通过内存窗口来查看其虚函数的存放:
程序运行结果为:通过简单的图来分析:
可以看到,派生类重写的虚函数在两个基类的虚表中都有,而派生类独有的虚函数则存放在继承的第一个基类里面即Father类内。
《C++ primer plus》中将多态分为两种:静态多态,静态多态。
1. 静态多态就是重载,因为是在编译期决议确定,所以称为静态多态。
2. 动态多态就是通过继承重写基类的虚函数实现的多态,因为是在运行时决议确定,所以称为动态多态。
具体的就不在此多做陈述,感兴趣的伙伴可以去看相关的材料或书籍。