当前位置:编程学习 > C/C++ >>

重载,多态虚函数

初学C++,虚函数这部分感觉博大精深啊。C++正是通过虚函数实现了多态。在C++中,以virtual关键字开始的函数是虚函数,虚函数是基类希望派生类进行重新定义的函数,不希望派生类重新定义而完全继承的不要定义为虚函数。一旦函数在基类中声明为虚函数,派生类就无法改变这个事实,派生类中重新定义虚函数时,关键字virtual可有可无。

      虚函数发生动态绑定一定要同时满足两个条件:1)这个函数是虚函数2)必须通过基类类型的引用或指针进行函数的调用。首先要清楚一个事实,那就是每个派生类的对象都包含基类部分,存在从派生类到基类的转换,即可以使用基类类型的指针和引用来引用派生类的对象,因此当使用一个基类类型的指针或引用时,实际上我们是无法立刻确认到底绑定的对象类型是基类的还是派生类的,只能在运行时确定。当使用基类对象调用虚函数时,调用基类中的虚函数版本,当使用派生类调用时,调用的是派生类中重定义的版本。当想克服虚函数机制时,可以使用作用域操作符进行显示约束。

从学习到面试,个人总结了关于虚函数的几个问题,如下:

1.虚函数表;

2.构造函数可不可以是虚函数;

3.static成员函数可不可以是虚函数;

4.C++中虚函数与重载,为什么要用虚函数;

5.纯虚函数

要搞懂2.3问题,首先要明白虚函数表的概念和工作原理,之前也读过一些资料,但是不是特清晰,找了一个大牛写的blog,受益匪浅:


之后我们来看看后面问题的回答

2.构造函数可不可以是虚函数:

answer:

1)从上面多提到的知识,我们可以看出虚函数的执行是通过虚函数表来实现的,因此这个虚函数表一定要在虚函数调用之前初始化完成。生成一个类的对象要执行构造函数,而设置VPTR也是在构造函数中完成的。其实在虚函数运行之前,对象的类型应该是完全的。而构造函数执行之前,对象的类型是不完全的。如果构造函数也是虚函数的话,那么VPTR就没人来初始化,不完全的类型的对象也无法执行这个虚的构造函数。2)试想一下,虚函数是一个基类中的函数在子类中的重写,如果构造函数成了虚函数,那岂不是派生类中的构造函数名是基类类型的名字?这显然是不合理的。

3.static成员函数可不可以是虚函数:

answer:

在虚函数表的原理中,编译器在每个类型对象里偷偷的插入了一个VPTR,这个VPTR指向虚函数表。虚成员函数在执行时可以通过this这个隐藏形参所指对象判断是什么类型的,再通过虚函数表调用正确版本的函数。而static成员函数无this指针,而且他是静态的,子类继承后仍然是原来那个静态的函数,不会出现多态的应用,所以static函数不能是虚函数。

4.
      重载overload是根据函数的参数列表来选择要调用的函数版本,而多态是根据运行时对象的实际类型来选择要调用的虚virtual函数版本,多态的实现是通过派生类对基类的虚virtual函数进行覆盖override来实现的,若派生类没有对基类的虚virtual函数进行覆盖override的话,则派生类会自动继承基类的虚virtual函数版本,此时无论基类指针指向的对象是基类型还是派生类型,都会调用基类版本的虚virtual函数;如果派生类对基类的虚virtual函数进行覆盖override的话,则会在运行时根据对象的实际类型来选择要调用的虚virtual函数版本,例如基类指针指向的对象类型为派生类型,则会调用派生类的虚virtual函数版本,从而实现多态。

      使用多态的本意是要我们在基类中声明函数为virtual,并且是要在派生类中覆盖override基类的虚virtual函数版本,注意,此时的函数原型与基类保持一致,即同名同参数类型;如果你在派生类中新添加函数版本,你不能通过基类指针动态调用派生类的新的函数版本,这个新的函数版本只作为派生类的一个重载版本。还是同一句话,重载只有在当前类中有效,不管你是在基类重载的,还是在派生类中重载的,两者互不牵连。

      重载是静态联编的,多态是动态联编的。进一步解释,重载与指针实际指向的对象类型无关,多态与指针实际指向的对象类型相关。若基类的指针调用派生类的重载版本,C++编绎认为是非法的,C++编绎器只认为基类指针只能调用基类的重载版本,重载只在当前类的名字空间作用域内有效,继承会失去重载的特性,当然,若此时的基类指针调用的是一个虚virtual函数,那么它还会进行动态选择基类的虚virtual函数版本还是派生类的虚virtual函数版本来进行具体的操作,这是通过基类指针实际指向的对象类型来做决定的,所以说重载与指针实际指向的对象类型无关,多态与指针实际指向的对象类型相关。

这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,具体规则我们也来做一小结:

       如果派生类的函数与基类的函数同名,但是参数不同。此时,若基类无virtual关键字,基类的函数将被隐藏。(注意别与重载混淆,虽然函数名相同参数不同应称之为重载,但这里不能理解为重载,因为派生类和基类不在同一名字空间作用域内。这里理解为隐藏)

       如果派生类的函数与基类的函数同名,但是参数不同。此时,若基类有virtual关键字,基类的函数将被隐式继承到派生类的vtable中。此时派生类vtable中的函数指向基类版本的函数地址。同时这个新的函数版本添加到派生类中,作为派生类的重载版本。但在基类指针实现多态调用函数方法时,这个新的派生类函数版本将会被隐藏。

       如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏。(注意别与覆盖混淆,这里理解为隐藏)。

       如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数有virtual关键字。此时,基类的函数不会被“隐藏”。(在这里,你要理解为覆盖哦)。

5. 纯虚函数

有时候,基类仅仅作为其派生类的一个接口,而不希望用户实际创建一个基类的对象。此时可以在基类中加入至少一个纯虚函数,使得基类变为抽象基类。纯虚函数在普通的虚函数后面加上=0.当派生类继承一个抽象基类时,必须实现所有的纯虚函数,否则该派生类仍然是一个抽象类,不能创建对象。

 作者“志在千里”

补充:软件开发 , C语言 ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,