虚函数出现的原因
C++多态通过虚函数来实现,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖,或者称为重写。
最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,动态绑定。由于编写代码时不能确定被调用的是基类还是哪个派生类的函数,所以被称为“虚函数”。如果没有使用虚函数的话,即没有利用C++的多态性,则利用基类指针调用相应的函数时,总被限制在基类函数本身,而无法调用到子类中被重写过的函数。
#include<iostream>
using namespace std;
class A
{
public:
void foo()
{
printf("A::foo()1\n");
}
virtual void fun()
{
printf("A::fun 2\n");
}
};
class B : public A
{
public:
void foo() //隐藏:派生类的函数屏蔽了与其同名的基类函数
{
printf("B::foo3\n");
}
void fun() //多态、覆盖
{
printf("B::fun4\n");
}
};
int main(void)
{
A a;
B b;
A *p = &a;
p->foo(); //输出1
p->fun(); //输出2
p = &b;
p->foo(); //取决于指针类型,输出1
p->fun(); //取决于对象类型,输出4,体现了多态
return 0;
}
虚函数调用:在运行时确定,取决于指针指向实例的类型。
C++虚函数的实现机制
虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。虚函数表是类对象之间共享的,而非每个对象都保存了一份。
虚函数的地址存放于虚函数表之中。运行期间多态就是通过虚函数和虚函数表实现的。类的对象内部会有指向类内部的虚表地址的指针。通过这个指针调用虚函数。
虚函数在c++中的实现机制就是用虚表和虚指针。每个类用了一个虚表,每个类的对象用了一个虚指针。指向虚表。
具体用法如下:
class A{
public:
virtual void f();
virtual void g();
private:
int a;
};
class B:public A{
public:
void g();
private:
int b;
};
因为A由virtual void f()和g(),所以编译器为A类准备了一个虚表vtableA,内容如下:
B因为继承了A,所以编译器也为B准备了一个虚表vtableB,内容如下
注意:因为B::g是重写了的,所以B的虚表的g存放的是B::g的入口地址,但是f是从A继承下来的,所以f的地址是A::f的入口地址。
实例化B, B bB;编译器分配空间时,除了A的int a,B的成员int b以外还分配了一个虚指针vptr,指向B的虚表vtableB,bB的布局如下:
当如下语句时
A *pa = &bB;
pa的结构就是A的布局,就是说pa只能访问到bB对象的前两项,访问不到第三项int b。
pa->g(); //编译器知道g是一个声明为virtual的成员函数,而且其入口地址放在表格(无论是vtableA表还是vtableB表)的第2项,那么编译器编译这条语句的时候就转换为call *(pa->vptr)[1]
这一项放的是B::g()的入口地址,就实现了多态。(bB的vptr指向的是B的虚表vtableA)
虚函数调用:在运行时确定,取决于指针指向实例的类型。
来源:CSDN
作者:Summer8918
链接:https://blog.csdn.net/qq_38677031/article/details/104703333