动态多态
再讲虚函数之前,提一下,我在刚开始接触总会混淆虚函数和虚继承。
总结一下:
a虚继承会产生一个偏移指针指向一个偏移表
定义形式:class B:virtual public A (是继承)
b虚函数会产生一个指针地址指向虚表
定义形式:
class B
{
Public:
Virtual void Fun()(是函数)
{}
}
动态多态:在程序执行期间判断引用对象的实际类型,根据其实际类型调用相应的方法,动态多态使用虚函数实现的。
虚函数:所谓的虚函数,就是基类声明的函数时虚拟的然后在派生类中才真正的定义此函数
动态绑定的条件:
1)必须是虚函数,该虚函数被派生类重写
2)通过基类类型的指针或引用调用虚函数
我们先来看下虚函数
:用关键字Virtual修饰类的成员函数
public:
virtual void Testfun(int i)
{};
};
class B:public A
{
public:
virtual void Testfun(int i) //重写基类,这个virtual也可以不写,//但同样继承基类虚函数的特性
{
cout<<a<<endl;
}
int a;
};
void Test()
{
A a;
B b;
A *p;
p=&b;
p->Testfun(2);
}
我们可以惊奇的发现,我用一个基类指针访问了派生类属于派生类自己的函数成员,这就是虚函数
提一下重写:即覆盖
1)在不同的作用域(基类和派生类)
2)函数名,参数,返回值相同(除协变)
3)基类函数必须有virtual关键字
4)访问修饰符可以不同(但仍遵循继承的访问原则)
注意:
1)构造函数不要定义为虚函数,析构函数可以定义为虚函数。
2)不要在构造函数和析构函数内部调用虚函数,因为在构造函数和析构函数中,对象是不完整的。
3)虚函数可以在类内声明,类外定义,在类外定义是不用加virtual。
4)虚表是所有类对象共用的。
可见b,b1都是B类型,虚表地址都是0x00875804(共享虚表)
二.下面我们一步一步来分析虚表
(1)对一个简单的虚函数
class A
{
public:
virtual void Testfun()
{ cout<<"A::Testfun 1"<<endl; }
int i;
};
void Test()
{
A p;
p.i =7;
}
图1
我们在Test添加以下代码看看这个地址里的是不是该虚函数
typedef void(* PFV)(); //一个返回值为空,参数列表为空的函数类型
PFV *pfv= (PFV*)*(int *)(&p);
(*pfv)();
结果:
在0x00b6109b里面的是函数Testfun1
单继承中虚函数
(2.1)没有覆盖的
void Test()
{
B p;
p.i =7;
p.b=9;
PFV *pfv=(PFV*)*(int *)(&p);
for(int i=0;i<3;i++)
{
(*pfv)();
pfv++;
}
}
}
来看看P::A的虚表指针里面有什么
(图1)
可见B的虚函数的地址加在它所继承的A的虚表的后头,相当于把A的虚表 拷贝了一份然后加上自己的那部分。
(2.2)有重写
class B:public A,public C
在上个代码的classB中加入以下代码,重写void Testfun1()
virtual void Testfun1()
{
cout<<"B::Testfun1"<<endl;
}
在测试代码中加如下代码,即打印婆中属于A的部分的虚表
A &pv=p;
PFV *pfv=(PFV*)*(int *)(&pv);
for(int i=0;i<4;i++)
{
(*pfv)();
pfv++;
}
()(图片2)
可见和(图1)的一样,相当于把A的虚表 拷贝了一份然后追加上自己的那部分。并用自己重写的去覆盖与A相同的部分。
3.虚拟的多继承
(3.1)没有覆盖的
class B:public A,public C
在(2.1)的代码加入新类class C
class C
{ public:
virtual void Testfun4()
{
cout<<"C::Testfun4"<<endl;
}
int c;
};
在加人打印p中属于C的部分虚表指针所指的内容,打印出p中属于A的虚表指针所指的内容
可见,派生类的自己的虚函数追加到了拷贝自顺序继承的第一位A(class B:public A,public C)的虚表中,属于p的基类C的有自己的虚表(A有自己的虚表)
注意:图2的虚表是属于B类型的p的,与A类自己的虚表并没有关系,是B继承A是拷贝了一份A的虚表,并 或追加或修改
(3.2)有覆盖的多继承
在上个代码的classB中,重写void Testfun1()
virtual void Testfun1()
{
cout<<"B::Testfun1"<<endl;
}
在加人打印p中属于C的部分虚表指针所指的内容,打印出p中属于A的虚表指针所指的内容
可见:与3.1相比B类因为重写了Testfun1,所以覆盖了A类的Testfun1,(覆盖可类比2.2)。