多态以及它的单继承、多继承、菱形继承的对象模型
什么是多态
同一件事物在不同的场景下表现忽的多种形态。不同类的对象对同一消息做出响应,同一消息可以根据发送对象的不同而采用多种不同的行为方式。
静态多态
在编译期间,确定程序的行为(确定具体调用哪个函数)
动态多态
程序运行期间,才能确定程序的运行行为
通常,虚函数是动态绑定,非虚函数是静态绑定,缺省参数值也是静态绑定
- 实现动态多态的条件:
1.基类中必须包含虚函数,在派生类中必须对基类中的虚函数进行重写。 ------例外: a>析构函数(函数名不同) b>协变:基类虚函数返回基类的引用/指针 派生类函数返回派生类的引用/指针 2.通过基类的指针/引用去调用虚函数
-
那什么是重写呢?
前提是:在继承体系中
1.把基类中的某虚函数再在派生类中定义一个一样的虚函数,这个虚函数必须与基类中虚函数原型保持一致。
2.派生类中函数virtual可以不写,仍旧保持虚函数特性
3.基类与派生类虚函数的访问权限可以不同,但是基类中虚函数必须是公有的。 -
什么是同名隐藏
继承体系中,基类与派生类中具有相同名称的成员(成员变量或成员函数),当通过派生类对象调用相同名称的成员时,优先调用派生类。
注意:
virtual和static一样只能和函数的声明放在一块。
virtual不能和static放在一块。
多态的原理:
虚函数表:通过一块连续内存来存储虚函数的地址。按照虚函数在基类中的声明次序添加到虚函数表中。
//构造函数里插了一条语句,把虚表的地址放到类的前四个字节,如果没有给构造函数,自动合成一个。
//如果给了,什么也没有做,那编译器就把它改写了。
class Base
{
public:
Base()
{}
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
int _b;
};
int main()
{
Base b;
return 0;
}
不同继承下,虚函数的对象模型
-
1.单继承
定义这样的基类和派生类:
class Base
{
public:
Base()
{}
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Base::FunTest2()" << endl;
}
virtual void FunTest3()//加或者删了虚函数,记得重新生成解决方案
{
cout << "Base::FunTest3()" << endl;
}
int _b;
};
class Derived : public Base
{
public:
virtual void FunTest5()
{
cout << "Derived::FunTest5()" << endl;
}
virtual void FunTest2()
{
cout << "Derived::FunTest2()" << endl;
}
virtual void FunTest4()
{
cout << "Derived::FunTest4()" << endl;
}
int _d;
};
把虚表打印出来:
typedef void(*PVTF)();//PVTF是void(*)()的别名
void PrintfVTF(Base& b,const string& str)//传基类引用的好处:传派生类和基类的对象都可以打印(赋值兼容规则)
{
cout << str << endl;
//先找到对象前四个字节,转换后在解引用,但是解引用后只是一个整数
PVTF* pVTF = (PVTF*)*(int*)&b;
while (*pVTF)
{
(*pVTF)();//调这个函数
pVTF++;
}
cout << endl;
}
int main()
{
Base b;
PrintfVTF(b,"Base VTF:");
Derived d1;
Derived d2;
PrintfVTF(d1, "Derived VTF:");
return 0;
}
一个类的多个对象共享同一张虚表:
对于重写的虚函数和没有重写的虚函数,是如何来调的:
-
2.多继承
首先,我们定义这样的基类和派生类:
对于单继承来说,在派生类里没有重写的函数是在基类的虚表后面,那对于多继承,派生类里没有重写的函数是在哪个基类的虚表后面呢?
那我们把两个基类的虚函数表打印出来看看:
3.菱形继承
同样,我们声明这样的类:
对于菱形继承存在二义性问题,那我们把这个多态里的菱形继承让它虚拟继承,来看看对象模型:
现在我们来看,这连个地址哪个是虚表地址,哪个是偏移量表格地址:
所以,多态中的虚拟继承里,第一个地址是虚表地址,第二个地址是虚拟继承里偏移量表格的地址。