虚函数的原理和使用

虚函数(实现类的多态性)

这里再解释一下类的多态

即接口的多种不同实现方式。更直观来说就是同一成员函数让不同的对象使用,可以实现不同的功能。这就是多态。

虚函数的函数调用发生在运行阶段,而不是发生在编译阶段,称为动态联编(动多态)。虚函数的调用需要间接的内存寻址动作,从而使动态联编的效率下降

注意:

1、非虚函数静态联编(静多态),效率要比虚函数高,但是代码会很臃肿。

2、如果使用了virtual关键字,程序将根据引用或指针指向的 对 象 类 型 来选择方法,否则使用引用类型或指针类型来选择方法。

虚函数的使用方法:

1、在基类用virtual声明成员函数为虚函数。

这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。在类外定义虚函数时,不必再加virtual

2、在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。

C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,

但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。

3、定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。

4、通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。

通过虚函数与指向基类对象的指针变量的配合使用,就能方便地调用同一类族中不同类的同名函数,只要先用基类指针指向即可。

如果指针不断地指向同一类族中不同类的对象,就能不断地调用这些对象中的同名函数。这就如同前面说的,不断地告诉出租车司机要去的目的地,

然后司机把你送到你要去的地方。   

虚函数的注意点

1、重写,重写是针对与虚函数的,即子类的同名函数重写父类的同名虚函数;而覆盖是针对于一般函数的

2、虚指针对于函数成员而不能使用于数据成员

3、子类重写的函数,即跟父类虚函数同名的函数,默认是虚函数,可以显式的加上virtual,也可以不加

4、重写要求较高,要求两个函数的输入参数得相同,同时在大部分情况下,也要求返回值的类型相同,除非协变,返回值是当前的类、类指针或类引用

5、虚函数不能是内联函数,就算写了inline关键字,系统也不认,没有任何作用

6、构造函数不能是虚函数

 

举个例子:实现一个求面积的功能

#include<iostream>
using namespace std;
//求面积
class S
{
public:
    virtual double Get_S(double a, double b)
    {
        cout << "基类" << endl;
        return -1;
    }
};
//求圆面积
class Aclass:public S
{
public:
    //重写基类的函数的时候参数类型和个数要一致,否则会调用失败
    virtual double Get_S(double r,double rr)
    {
        cout << "圆=";
        return 3.14*r*r;
    }
};
//求三角形面积
class Bclass :public S
{
public:
    virtual double Get_S(double a, double b)
    {
        cout << "三角形=";
        return a * b*0.5;
    }
};
//求矩形面积
class Cclass :public S
{
public:
    virtual double Get_S(double a, double b)
    {
        cout << "矩形=";
        return a * b;
    }
};
int main()
{
    S* A = new Aclass;
    cout << A->Get_S(1,1) << endl;
    S* B = new Bclass;
    cout << B->Get_S(1, 2) << endl;
    S* C = new Cclass;
    cout << C->Get_S(1, 2) << endl;
    delete A;
    delete B;
    delete C;
    system("pause");
    return 0;
}

输出

 

虚表:

虚表就是一个列表,里面存放的是父类中的虚函数的地址,当创建对象的时候,如果发现有重写,就将子类的虚函数的地址覆盖原来父类虚函数的地址。0代表虚表的结尾

虚表建立在编译过程,虚函数指针是在运行阶段确定的

具体执行过程:

1)编译器在编译父类时,发现一个函数是虚函数,就将起地址存放到虚表中,依次将虚函数的地址存放进去

2)在创建一个新的子类对象时,即new 子类名时,检测子类中函数有没有与父类中的虚函数同名的,如果有,就将子类中的这个函数的地址存放在原来存放父类同名虚函数的虚表位置上,覆盖原来父类虚函数的地址

3)当指针对象调用一个父类中的函数时,如果检测到这个函数是虚函数,则就去虚表里面查找这个虚函数的地址,然后转去相应的地址上去执行

4)虚表是属于类,同一个类的不同对象使用同一个虚表,在派生类只会继承基类的虚指针

类对象空间最开始四个字节就是虚表(虚函数列表)的地址,叫虚指针C++中虚表指针是放在类对象的开头,虚指针指向一个虚表,虚表中记录了虚基类与本类的地址偏移,通过这个地址偏移可以找到虚基类的成员指针的地址

 

参考博客:https://blog.csdn.net/qq_33757398/article/details/81413999

原文地址:https://www.cnblogs.com/-citywall123/p/12745654.html