多态
所谓多态,其实就是“多种形态”,C++的多态分为静态多态和动态多态。
1. 静态多态就是重载,因为是在编译期决议确定,所以称为静态多态。
2. 动态多态就是通过继承重写基类的虚函数实现的多态,因为是在运⾏时决议确定,所以称为动态多态。
C++中虚函数的主要作⽤——就是实现多态。简单说⽗类的指针/引⽤调⽤重写的虚函数,当⽗类指针/引⽤指向⽗类对象时调⽤的是⽗类的虚函数,指向⼦类对象时调⽤的是⼦类的虚函数。
ps:父类即基类,子类即派生类。
虚函数
虚函数的定义在父类中进行,它是在父类中需要定义为虚函数的成员函数的声明中冠以关键字 virtual ,从而提供一种接口界面。并且虚函数可以在一个或者多个子类中重新定义。定义时,其函数名、返回类型、参数个数、参数类型的顺序,都必须和父类中原型完全相同。举例如下:
virtual 返回类型 函数名(形参表)
{
函数体
}
#include<iostream>
using namespace std;
class Grandma
{
public:
virtual void introduce_self()
{
cout << "I am Grandma" << endl;
}
};
class Mother:public Grandma
{
public:
void introduce_self()
{
cout << "I am Mother" << endl;
}
};
class Daughter :public Mother
{
public:
void introduce_self()
{
cout << "I am Daughter" << endl;
}
};
int main()
{
Grandma* ptr;
Grandma g;
Mother m;
Daughter d;
ptr = &g;
ptr->introduce_self();
ptr = &m;
ptr->introduce_self();
ptr = &d;
ptr->introduce_self();
system("pause");
return 0;
}
结果如下:
程序只有Grandma中显式定义了introduce_self()是虚函数。C++中规定,若没有virtual显式声明,将依据以下条件判断是否是虚函数。1、该函数与虚函数有相同名称。2、该函数与虚函数有相同参数个数及相同对应参数类型3、该函数与虚函数有相同返回值类型或者满足赋值兼容规则的指针、引用类型的返回值。
赋值兼容规则
1. ⼦类对象可以赋值给⽗类对象(切割/切⽚)
2. ⽗类对象不能赋值给⼦类对象
3. ⽗类的指针/引⽤可以指向⼦类对象
4. ⼦类的指针/引⽤不能指向⽗类对象(可以通过强制类型转换完成)
ps:纯虚函数—— 声明纯虚函数一般形式如下:
virtual 函数类型 函数名(参数表)=0;
纯虚函数不具备函数的功能,不能被调用。因为包含至少一个纯虚函数的抽象类不能实例化出对象。
虚函数表
虚函数表是通过⼀块连续内存来存储虚函数的地址。这张表解决了继承、虚函数(重写)的问题。在有虚函数的对象实例中都存在⼀张虚函数表,虚函数表就像⼀张地图,指明了实际应该调⽤的虚函数。虚函数表的地址可以通过内存窗口看到。
以单继承虚表举例:
#include<iostream>
using namespace std;
class Base
{
public:
virtual void func1()
{
cout << "Base::func1" << endl;
}
virtual void func2()
{
cout << "Base::func2" << endl;
}
private:
int a;
};
class Derive :public Base
{
public:
virtual void func1()
{
cout << "Derive::func1" << endl;
}
virtual void func3()
{
cout << "Derive::func3" << endl;
}
virtual void func4()
{
cout << "Derive::func4" << endl;
}
private:
int b;
};
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 Test1()
{
Base b1;
Derive d1;
int* VTable1 = (int*)(*(int*)&b1);
int* VTable2 = (int*)(*(int*)&d1);
PrintVTable(VTable1);
PrintVTable(VTable2);
}
int main()
{
Test1();
system("pause");
return 0;
}
可以看到派⽣类Derive::func1重写基类Base::func1,覆盖了相应虚表位置上的函数。
ps:可以看到这⾥没有看到派⽣类Derive中的func3和func4,这两个函数就放在func2的后⾯,这⾥没有显⽰是VS的问题(bug)。
下面的图有助于更好理解。
那么多继承在内存中布局是怎样的?下面例子告诉你 !
#include<iostream>
using namespace std;
class Base1
{
public:
virtual void func1()
{
cout << "Base::func1()" << endl;
}
virtual void func2()
{
cout << "Base::func2()" << endl;
}
public:
int b1;
};
class Base2
{
public:
virtual void func1()
{
cout << "Base::func1()" << endl;
}
virtual void func3()
{
cout << "Base::func3()" << endl;
}
public:
int b2;
};
class Derive:public Base1,public Base2
{
public:
virtual void func1()
{
cout << "Derive::func1()" << endl;
}
virtual void func4()
{
cout << "Derive::func4()" << endl;
}
public:
int d1;
};
typedef void(*FUNC) ();
void PrintVTable(int* Vtable)
{
cout << "虚表地址>" << Vtable << endl;
int ** ppvtable = (int**)Vtable;
for (int i = 0; ppvtable[i] != 0; ++i)
{
printf(" 第%d个虚函数地址 :0X%x,->", i, ppvtable[i]);
FUNC f = (FUNC)ppvtable[i];
f();
}
cout << endl;
}
void Test1()
{
Derive d;
d.b1 = 1;
d.b2 = 2;
d.d1 = 3;
PrintVTable(*((int**)(&d)));
// Base2虚函数表在对象Base1后⾯
PrintVTable(*((int**)((char*)&d + sizeof(Base1))));
}
int main()
{
Test1();
system("pause");
return 0;
}
在多继承模型中我们在打印虚函数表时应注意类型的转换和不同虚表在内存中的布局关系。在之后的菱形继承模型中,我们会用到虚继承来解决二义性和数据冗余问题,对内存布局加以更深了解。