关于C++多态的问题:(基于Visual Studio 2012编译器)
一、多态引入
1、对象的类型:
(1) 静态的类型:对象声明时的类型,在编译的时候确定
(2) 动态的类型:目前所指对象的类型,在程序运行时确定的
EG:
class Derived1:public Base
{};
class Derived2:public Base
{};
void FunTest()
{
Derived1* pD1 = new Derived1;//对象pD1的静态类型是Derived1, 动态类型是Derived1*
Base* pB = pD1;//对象pB的静态类型是Base*, 动态类型是Derived1*
Derived2* pD2 = new Derived2;
pB = pD2;//对象pB的静态类型是Base*,动态类型是Derived2
}
2、多态:
(1) 多态的概念:一词最初来源于希腊语,意思是具有多种形式或形态的情形,在C++中主要体现在想不通的对象发送同一个消息,不同的对象会产生不同的行为。
(2) 多态的类型:
A:一种为编译时的多态,称为静态多态或静态联编。编译时的多态性是指程序在编译以前就确定的多态性,可以通过重载、泛型编程来实现。
B:另一种是运行时的多态,也称为静态联编,运行时的多态是指在程序运行中才可以确定的多态性,是通过继承和虚函数实现的。
(3) 动态的多态:
动态绑定(早绑定):在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。
class CWashRoom
{
public:
void GoToManWashRoom ()
{
cout<< "Man--->Please Left" <<endl;
}
void GoToWomanWashRoom ()
{
cout<< "Woman--->Please Right" <<endl ;
}
};
class CPerson
{
public:
virtual void GoToWashRoom(CWashRoom & _washRoom ) = 0;//virtual修饰函数为虚函数,在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
};
class CMan:public CPerson
{
public:
virtual void GoToWashRoom(CWashRoom & _washRoom )//对基类函数的重写,进行动态绑定,实现不同的功能。
{
_washRoom.GoToManWashRoom();
}
};
class CWoman:public CPerson
{
public:
virtual void GoToWashRoom(CWashRoom & _washRoom )
{
_washRoom.GoToWomanWashRoom ();
}
};
注:必须是虚函数,通过基类类型的引用或者指针调用虚函数
voidFunTest()
{
CWashRoom washRoom;
for ( int iIdx = 1 ; iIdx <= 10 ; ++iIdx)
{
CPerson* pPerson;
int iPerson = rand ()%iIdx;
if (iPerson&0x01)
{
pPerson = new CMan ;
}
else
{
pPerson = new CWoman ;
}
pPerson->GoToWashRoom (washRoom); //通过基类类型的指针调用在派生类中//进行重写的虚函数,
delete pPerson;
pPerson = NULL ;
Sleep(1000 );
}
}
3、 继承体系中同名成员函数的关系和区别
4、 小结
1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)
2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
3、只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。
4、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。
5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆
6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,没有传递this指针,可能会出现未定义的行为。
7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
8、虚表是所有类对象实例共用的
5、 虚表分析
(1) 单继承:
派生类中没有重写基类的虚函数
classBase
{
public:
Base()
{}
virtualvoid Test1()
{
//data = 10;
//cout<<this<<endl;
//cout<<data<<endl;
cout<<"BaseTest1()"<<endl;
}
virtualvoid Test2()
{
cout<<"BaseTest2()"<<endl;
}
virtualvoid Test3()
{
cout<<"BaseTest3()"<<endl;
}
int data;
};
classDerited:publicBase
{
public:
virtualvoid Test4()//复制一份基类虚函数,如果在派生类中对基类虚函数进行重写,则用重写的虚函数代替
{
cout<<"DeritedTest4()"<<endl;
}
virtualvoid Test5()
{
cout<<"DeritedTest5()"<<endl;
}
virtualvoid Test6()
{
cout<<"Deritedtest6()"<<endl;
}
};
void FunTest2(Base& b)
{
b.Test2();
}
typedef void (*VFP)();
void printVpf()
{
Base b;
b.data1 = 1;
cout<<"Base::virtable of B:"<<endl;
VFP* vfp = (VFP *)*(int *)&b; //未传递this指针。
//最好不要访问类的成员变量
while(*vfp)
{
(*vfp)();
++vfp;
}
cout<<endl;
Derited d;
d.data1 = 1;
d.data2 = 2;
cout<<"Base::virtable of D:"<<endl;
VFP* vfp1 = (VFP *)*(int *)&d;
while(*vfp1)
{
(*vfp1)();
++vfp1;
}
cout<<endl;
}
在派生类中重写基类的虚函数
classBase
{
public:
Base()
{}
virtualvoid Test1()
{
//data = 10;
//cout<<this<<endl;
//cout<<data<<endl;
cout<<"BaseTest1()"<<endl;
}
virtualvoid Test2()
{
cout<<"BaseTest2()"<<endl;
}
virtualvoid Test3()
{
cout<<"BaseTest3()"<<endl;
}
int data;
};
classDerited:publicBase
{
public:
virtualvoid Test1()//复制一份基类虚函数,如果在派生类中对基类虚函数进行重写,则用重写的虚函数代替
{
cout<<"DeritedTest1()"<<endl;
}
virtualvoid Test3()
{
cout<<"DeritedTest3()"<<endl;
}
virtualvoid Test4()
{
cout<<"test4()"<<endl;
}
};
在重写了基类虚函数的派生类虚表中先复制了一份基类的虚表,然后将在派生类中重写的虚函数覆盖掉基类的虚函数,派生类自己的虚函数跟在复制下来的虚表后边。通过基类的指针或引用调用虚函数时调用的是被覆盖之后的虚表里的虚函数。
(2) 多继承:
class Base1
{
public:
virtual void Test1()
{
cout<<"Base1Test1()"<<endl;
}
virtual void Test2()
{
cout<<"Base1Test2()"<<endl;
}
virtual void Test3()
{
cout<<"Base1Test3()"<<endl;
}
int data1;
};
class Base2
{
public:
virtual void Test4()
{
cout<<"Base2Test4()"<<endl;
}
virtual void Test5()
{
cout<<"Base2 Test5()"<<endl;
}
virtual void Test6()
{
cout<<"Base2Test6()"<<endl;
}
int data2;
};
class Derited:public Base1,public Base2
{
public:
Derited()
{
data3 = 3;
}
virtual void Test2()
{
cout<<"DeritedTest2()"<<endl;
}
virtual void Test4()
{
cout<<"DeritedTest4()"<<endl;
}
virtual void Test7()
{
cout<<"DeritedTest7()"<<endl;
}
int data3;
} ;
typedef void (* VFP)();
void printVpf()
{
Derited d;
d.data1 = 1;
d.data2 = 2;
d.data3 = 3;
Base1 &b1 = d;
VFP* vfp = (VFP *)*(int *)&b1;
cout<<"&b1 ="<<&b1<<endl;
while(*vfp)
{
(*vfp)();
++vfp;
}
cout<<endl;
Base2 &b2 = d;
cout<<"&b2 ="<<&b2<<endl;
cout<<"&b2 ="<<(Base2 *)((int)&b1+sizeof(Base1))<<endl;
VFP* vfp1 = (VFP *)*(int *)&b2;
while(*vfp1)
{
(*vfp1)();
++vfp1;
}
cout<<endl;
在多继承中,基类的虚函数如果在子类中重写,则用基类的指针或引用调用虚函数,调用的是被覆盖的虚表里的虚函数。
(3) 菱形继承
class Base
{
public:
virtual void Test1()
{
cout<<"Base::Test1()"<<endl;
}
int _data1;
};
class C1:public Base
{
public:
virtual void Test1()
{
cout<<"C1::Test1()"<<endl;
}
virtual void Test2()
{
cout<<"C1::Test2()"<<endl;
}
int _data2;
};
class C2:public Base
{
public:
virtual void Test1()
{
cout<<"C2::Test1()"<<endl;
}
virtual void Test3()
{
cout<<"C2::Test3()"<<endl;
}
int _data3;
};
class D:public C1,public C2
{
public:
virtual void Test1()
{
cout<<"D::Test1()"<<endl;
}
virtual void Test2()
{
cout<<"D::Test2()"<<endl;
}
virtual void Test3()
{
cout<<"D::Test3()"<<endl;
}
virtual void Test4()
{
cout<<"D::Test4()"<<endl;
}
int _data4;
};
typedef void(*VFP)();
void PrintVfp()
{
D d;
VFP* vfp1 = (VFP*)*(int *)&d;
cout<<"D virtable:"<<endl;
while(*vfp1)
{
(*vfp1)();
++vfp1;
}
cout<<endl;
C2& c2 = d;
VFP* vfp2 = (VFP*)*(int *)&c2;
cout<<"C2 virtable:"<<endl;
while(*vfp2)
{
(*vfp2)();
++vfp2;
}
cout<<endl;
C1& c1 = d;
VFP* vfp3 = (VFP*)*(int *)&c1;
cout<<"C1 virtable:"<<endl;
while(*vfp3)
{
(*vfp3)();
++vfp3;
}
cout<<endl;
Base& b = d;
}
int main()
{
PrintVfp();
D d;
d.C1::_data1 = 0;
d.C2::_data1 = 1;
d._data2 = 2;
d._data3 = 3;
d._data4 = 4;
system("pause");
return 0;
}
在菱形继承中C1的大小为C1的成员加上Base的成员大小再加上C1的虚表大小
D的大小为C1的大小加上C2的大小加上D自己的成员变量大小。在派生类中存储了两份Base类的对象,故访问时存在二义性问题,由此引入了菱形虚拟继承。
(4) 菱形虚拟继承:
class Base
{
public:
virtual void Test1()
{
cout<<"Base::Test1()"<<endl;
}
int _data1;
};
class C1:virtual public Base
{
public:
virtual void Test1()
{
cout<<"C1::Test1()"<<endl;
}
virtual void Test2()
{
cout<<"C1::Test3()"<<endl;
}
int _data2;
};
class C2:virtual public Base
{
public:
virtual void Test1()
{
cout<<"C2::Test2()"<<endl;
}
virtual void Test3()
{
cout<<"C2::Test4()"<<endl;
}
int _data3;
};
class D:public C1,public C2
{
public:
virtual void Test1()
{
cout<<"D::Test1()"<<endl;
}
virtual void Test4()
{
cout<<"D::Test4()"<<endl;
}
virtual void Test5()
{
cout<<"DTest5()"<<endl;
}
int _data4;
};
typedef void(*VFP)();
void PrintVfp()
{
D d;
VFP* vfp1 = (VFP*)*(int *)&d;
cout<<"D virtable:"<<endl;
while(*vfp1)
{
(*vfp1)();
++vfp1;
}
cout<<endl;
C1 &c1 = d;
VFP* vfp2 = (VFP*)*(int *)&c1;
cout<<"C1 virtable:"<<endl;
while(*vfp2)
{
(*vfp2)();
++vfp2;
}
cout<<endl;
Base& b = d;
VFP* vfp3 = (VFP*)*(int *)&b;
cout<<"Base virtable:"<<endl;
while(*vfp3)
{
(*vfp3)();
++vfp3;
}
cout<<endl;
}
int main()
{ cout<<sizeof(C1)<<endl;
cout<<sizeof(D)<<endl;
PrintVfp();
system("pause");
return 0;
}
C1的大小为20字节:基类Base成员data1大小+派生类C1成员data2大小+偏移指针+虚表指针
D的大小为36个字节大小:C1成员大小+C1虚表指针+C1偏移指针+C2成员大小+C2虚表指针+C2偏移指针+派生类D成员大小+基类Base虚表+Base成员
(5) 派生类带有构造函数+析构函数、构造函数、析构函数其中一个成员函数的虚表分析:
class Base
{
public:
Base()
:data1(1)
{}
~Base()
{}
virtual void Test1()
{
cout<<"BaseTest1()"<<endl;
}
virtual void Test2()
{
cout<<"BaseTest2()"<<endl;
}
virtual void Test3()
{
cout<<"BaseTest3()"<<endl;
}
int data1;
};
class Derited:virtual public Base
{
public:
Derited()
{}
~Derited()
{}
virtual void Test1()
{
cout<<"DeritedTest1()"<<endl;
}
virtual void Test3()
{
cout<<"Deritedtest3()"<<endl;
}
virtual void Test4()
{
cout<<"DeritedTest4()"<<endl;
}
int data2;
};
typedef void (*VFP)()
void printVpf()
{
Derited d;
d.data1 = 1;
d.data2 = 2;
cout<<"vir table ofD:"<<endl;
VFP* vfp1 = (VFP *)*(int *)&d;
while(*vfp1)
{
(*vfp1)();
++vfp1;
}
cout<<endl;
Base& b = d;
cout<<"vir table ofB:"<<endl;
VFP* vfp2 = (VFP *)*(int *)&b;
while(*vfp2)
{
(*vfp2)();
++vfp2;
}
cout<<endl;
}
int main()
{
printVpf();
system("pause");
return 0;
}
构造函数在这个地方的作用:
(1) 偏移量表地址的填写
(2) 调用基类构造函数,填写基类虚表地址:
(3) 填写派生类虚表地址:
(4) 重新填写基类虚表地址
(5) 插入派生类与基类分割0x000000
虚表:
此时派生类的大小为24字节:派生类虚表指针+派生类偏移指针+派生类成员大小+基类虚表指针+基类成员大小+分割的 :0x00000000