先占个位置,稍微写点,这两天忙完了写代码版:
关于虚函数与多态,一般是用微软的解释:基类指针或引用指向派生类对象时,如果调用虚函数,则调用派生类虚函数。
我看倒不如这样来解释:另写一个函数,如 func (A&a) {} 其中一个参数是基类指针或引用,那么我在主函数中调用它时,传入参数是基类对象则是a是基类引用,传入派生类对象则a是派生类引用,那么就可以用a调用基类或者派生类虚函数,即在基类指针或引用和虚函数的支持下可以实现动态绑定。
借用c++primer上的一段话:当我们使用基类的引用或指针调用基类中定义的一个函数时,我们并不知道该函数真正作用的对象是什么类型,因为它可能是一个基类的对象也可能是一个派生类对象。如果该函数是虚函数,则直到运行时才会决定到底执行哪个版本,判断的依据是引用或指针所绑定的对象的真实类型。即对非虚函数的调用在编译时进行绑定。
举例如下:
#include <iostream>
using namespace std;
class father
{
public:
virtual void fA()
{
cout << "father fA" << endl;
}
virtual void fB()
{
cout << "father fB" << endl;
}
};
class son : public father
{
public:
virtual void fA()
{
cout << "son fA" << endl;
}
virtual void fC()
{
cout << "son fC" << endl;
}
};
void func(father *obj)
{
obj->fA();
}
int main()
{
father *f = new father;
son *s = new son;
func(f);
func(s);
return 0;
}
输出如下:
上面就是多态的一个使用样例。
那么c++语言具体如何实现多态的呢?需要知道两个东西!
第一:虚函数表,什么意思?
就是说一个类一旦拥有虚函数,则会拥有一张虚函数表,这个表独立于对象,是属于类本身的,且这张表会被派生类继承,其中子类重写的虚函数则会使用子类的虚函数地址替代,比如上面的连个类,它们的虚函数表大致如下:
父类:
father::fA()
father::fB()
子类:
father::fA()
son::fB()
son::fC()
格式:首先是父类虚函数,然后是子类虚函数,父类被重写部分会被子类的虚函数替代
第二:位于对象头部的虚表地址,什么意思?
当一个类被实例化的时候,这个实例化对象的首地址其实是一个指向该类虚表的指针。
因此当程序运行到这里时,会通过头部的地址查找虚表中的函数来进行调用。
那么刚刚说的两点:虚表?+ 在头部?真的是这样吗,不如来验证一下,毕竟实践出真知:
计划:既然虚函数是一个函数,那么假设刚刚的两点是真的,就一定能按照上述的方法使用函数指针来调用到虚函数
代码如下:
int main()
{
father *f = new father;
void( *p )( );
//首先取到对象的地址,也就是: f
//然后地址的第一个位置存储的便是虚函数表的地址:*(int*)f
//然后通过虚表就可以访问函数,比如第一个函数的地址就是:*(int*)*(int*)f
p = ( void(*)( ) )*( ( int * )*( (int*) f ) ); //第一个函数
p();
p = ( void(*)( ) )*( ( int * )*( (int*) f ) + 1 ); //第二个函数
p();
return 0;
}
结果如下:
可以发现,都顺利访问到了,因此上面说的那两点是对的!
这里解释一下,为什么取地址的时候需要转换为int*,因为取地址的时候面对的是一个数值串,取地址的时候转换成int*就是告诉计算机要从这个地方取4个字节的数值,将它当作地址,况且不这么做怎么知道+1的时候移动多少呢。
那么动态绑定实现原理这里就解释完了。
最后,再啰嗦一句,可能有人被基类指针这个地方迷糊住了,说为啥必须要用基类指针,而不能派生类指针呢,其实大可不必迷惑,因为这是语法上不合逻辑的,因为派生类本来就比基类的东西多,万一访问到基类不存在的呢,这不是出了岔子?其实简而言之,如果用派生类访问基类对象,编译这一关都过不了嘻嘻。