详解C++虚函数原理

1、虚函数

虚函数是类中比较特殊的成员函数,通过在普通成员函数的前面加上“virtual”关键字声明。派生类可以重新定义(重写、覆盖)基类的虚函数,通过基类指针去指向其派生类对象,进行动态绑定(延迟绑定,即一个类成员函数的调用不在编译时确定,延迟到运行时确定),达到利用基类访问派生类虚函数的目的。如果没有虚函数,则总被限制在基类函数本身,无法调用派生类中被重写的函数。虚函数是C++中实现多态性的关键。

2、虚函数原理

有下面这个类A,包含两个成员变量、两个普通成员函数以及三个虚函数,析构函数也被定义为虚函数。虚函数的原理为:当一个类中包含虚函数时,编译器会在编译的时候为类生成一个虚函数表,这个表用于存放虚函数指针;另外,还会在类中插入一个指针变量,称为虚函数表指针,在构造函数中,将虚函数表的地址赋予该指针变量。

Class A{
private:
    int x;
    int y;
public:
    void func1(){}
    void func2(){}

    virtual void vfunc1(){}
    virtual void vfunc2(){}
    virtual void ~A(){}
};

现在,如果将类A实例化,其对象在内存中的布局如下图所示,可以看到虚函数表、虚函数、普通成员函数并不占用对象内存空间,它们属于类A的组成部分。

 3、使用虚函数实现多态性

为了实现多态性,这里从类A中派生出两个子类B、C,并重写基类A的虚函数vfunc1,如以下代码所示

Class B : public A{
public:
    virtual void vfunc1(){
        cout << "class B" << endl;
    }

private:
    int a;
};

Class C : public A{
public:
    virtual void vfunc1(){
        cout << "class C" << endl;
    }

private:
    int b;
};

现在观察一下这三个类对象的内存布局,由于子类只重写了vfunc1虚函数,所以子类的虚函数表要将基类的vfunc1地址改为子类的,其他虚函数地址不变,仍属于基类A的,如下图所示。那么,当用基类指针指向子类,并调用vfunc1函数的时候,执行的将是对应子类的功能,即实现了多态。

4、扩展

(1)基类析构函数为什么要声明为虚函数,而构造函数不能声明为虚函数?

构造函数用于实例化过程中对对象进行初始化,此时对象还未完全创建,虚函数表和虚函数表指针还未明确,所以构造函数不能声明为虚函数。

由对象的内存布局可知,如果不将基类的析构函数声明为虚函数,那么当用基类指向派生类对象,在析构的时候,只会调用基类的析构函数,而不会调用派生类的析构函数,此时派生类对象分配的内存可能得不到释放,从而造成内存泄漏

(2)纯虚函数和抽象类

纯虚函数在类中只是声明接口,没有定义,例如,如果将vfunc1()声明为纯虚函数,那么其形式为

virtual void vfunc1() = 0;

虽然基类中纯虚函数没有定义,但是要求其任何派生类必须定义实现纯虚函数,以实现多态性。

带有纯虚函数的类是抽象类,抽象类将一系列方法作为接口组织在一个继承层次结构中,为派生类提供公共接口,具体实现内容由各派生类自己定义。关于抽象类有以下两点需要注意:

  • 抽象类不能实例化对象,只能作为基类使用,指向其派生类对象
  • 如果派生类没有重新定义纯虚函数,那该派生类仍是一个抽象类。
  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值