虚函数总结

多态(Polymorphism),是面向对象程序设计(OOP,Object-Oriented Programming)的重要特征。

多态的定义:

多态的实现方式有:

(1)函数重载 

(2)运算符重载 

(3)模版  

(4)虚函数

前三种我们称为静态绑定,第四种称为动态绑定。

静态绑定:绑定过程出现在编译阶段,在编译期就已确定要调用的函数;

动态绑定:绑定过程工作在运行阶段时执行,在程序运行时才确定将要调用的函数。

(只有通过基类指针或引用调用虚函数才能引发动态绑定)

虚函数的概念:在基类中冠以关键字“virtual”的成员函数。

虚函数的定义:

(1)virtual 函数返回值类型函数名称 (参数列表)

(2)如果一个函数在基类中被声明为虚函数,则他在所有派生类中都是虚函数。

值得注意的是:

虚函数不能声明为静态函数,也不能是友元函数。

基类指针指向派生类对象:

//
//  VF2.cpp
//  C++
//
//  Created by Cheng Sun on 2017/6/21.
//  Copyright © 2017年 Cheng Sun. All rights reserved.
//

#include <stdio.h>
#include <iostream>

using namespace std;

class Base
{
public:
    virtual void Func1()
    {
        cout << "Base::Func1()..." << endl;
    }
    virtual void Func2()
    {
        cout << "Base::Func2()..." << endl;
    }
    void Func3()
    {
        cout << "Base::Func3()..." << endl;
    }
};

class Derived : public Base
{
public:
    /*virtual*/ void Func1()
    {
        cout << "Derived::Func1()..." << endl;
    }
    /*virtual*/ void Func2()
    {
        cout << "Derived::Func2()..." << endl;
    }
    void Func3()
    {
        cout << "Derived::Func3()..." << endl;
    }
};

int main()
{
    Base* p;
    Derived d;
    p = &d; // 基类指针指向派生类对象
    
    p->Func1(); //Func1是虚函数,基类指针指向派生类对象,调用的是派生类对象的虚函数。
    p->Func2();
    p->Func3(); //Func3非虚函数,根据p指针实际类型来调用相应类的成员函数。
    
    return 0;
}
程序运行结果:

Derived::Func1()...
Derived::Func2()...
Base::Func3()...
Program ended with exit code: 0


虚析构函数

同样的道理,也反映在析构函数的应用上。当基类指针指向派生类对象,我们delete基类指针时,若基类的析构函数的不是虚函数,则只会释放基类对象的内存空间。想要同时释放派生类的内存空间,则需要将析构函数设为虚函数。(同样的,只需要将基类的析构函数设为虚函数,则派生类的析构函数也会自动变成虚函数。)

//
//  VF2.cpp
//  C++
//
//  Created by Cheng Sun on 2017/6/21.
//  Copyright © 2017年 Cheng Sun. All rights reserved.
//

#include <stdio.h>
#include <iostream>

using namespace std;

class Base
{
public:
    virtual void Func1()
    {
        cout << "Base::Func1()..." << endl;
    }
    virtual void Func2()
    {
        cout << "Base::Func2()..." << endl;
    }
    void Func3()
    {
        cout << "Base::Func3()..." << endl;
    }
    Base()
    {
        cout << "Base()..." << endl;
    }
    virtual ~Base()
    {
        cout << "~Base()..." << endl;
    }
};

class Derived : public Base
{
public:
    /*virtual*/ void Func1()
    {
        cout << "Derived::Func1()..." << endl;
    }
    /*virtual*/ void Func2()
    {
        cout << "Derived::Func2()..." << endl;
    }
    void Func3()
    {
        cout << "Derived::Func3()..." << endl;
    }
    Derived()
    {
         cout << "Derived()..." << endl;
    }
    ~Derived()
    {
        cout << "~Derived()..." << endl;
    }
};

int main()
{
    Base* p;
    p = new Derived; // 创建派生类对象的时候,会首先创建它的基类对象
    
    p->Func1();
    
    delete p;
    
    return 0;
}
程序运行结果:

Base()...
Derived()...
Derived::Func1()...
~Derived()...
~Base()...
Program ended with exit code: 0

如果上面代码中的基类没有将析构函数设为虚函数,那么delete p只会执行~Base(),而不会执行~Derived()。

总的来说,什么时候需要用到虚析构函数呢?如果一个类要作为多态基类,那么就要将析构函数定义为虚函数。

反之,如果一个类不会被任何派生类继承,则没有必要将它的析构函数定义为虚函数。

虚表指针和虚函数表

当一个类包含一个或一个以上的虚函数时,编译器就会为这个类产生一个4个字节(编译器工作在32位的框架下)的虚表指针。

虚函数是通过虚表来实现的。讨论没有覆盖的虚函数的情况没有太多意义,我们讨论一下有覆盖的虚函数的工作机制。当一个派生类继承基类时,若是派生类中出现了基类中同名的虚函数,即发生了所谓的覆盖,那么在派生类的虚函数表中,覆盖的虚函数被放到了原本父类虚函数的位置,没有被覆盖的函数,位置依旧。

虚表指针存放在类对象内部的头四个字节,三大区域都有可能。

虚函数表存放在全局数据区(静态区),因为对每个类,所有该类的对象共用虚表。

 

为方便下面部分代码的理解,这里稍稍介绍一下定义一个函数指针的方法:

返回类型 (*指针名) (函数参数列表)

例如: void (*Fun)(void) 意思是定一个指向返回值为void,函数参数也为void的函数的指针。

 

探讨包含虚函数的基类和派生类的内存模型:

//
//  VF3.cpp
//  C++
//
//  Created by Cheng Sun on 2017/6/21.
//  Copyright © 2017年 Cheng Sun. All rights reserved.
//

#include <stdio.h>
#include <iostream>

using namespace std;

class Base // 4 Bytes (pointer to virtual table) + 4 Bytes (int) = 8 Bytes
{
public:
    virtual void Func1()
    {
        cout << "Base::Func1()..." << endl;
    }
    
    virtual void Func2()
    {
        cout << "Base::Func2()..." << endl;
    }
    
    virtual void Func3()
    {
        cout << "Base::Func3()..." << endl;
    }
    
    int data1_;
};

class Derived : public Base // 4 Bytes (pointer to virtual table) + 4 Bytes (int, data1_) + 4 Bytes (int, data2_) = 12 Bytes
{
public:
    void Func2()
    {
        cout << "Derived::Func2()..." << endl;
    }
    
    void Func3()
    {
        cout << "Derived::Func3()..." << endl;
    }
    
    int data2_;
};

typedef void (*FUNC) (void); //定义一个函数指针,指向一个函数,这函数的类型是 void Fun (void)

int main()
{
    cout << "size of Base = " << sizeof(Base) << endl;
    cout << "size of Derived = " << sizeof(Derived) << endl;
    
    Base b;
    Derived d;
    
    long** p = (long**)&b; //定义一个p, 指向虚表指针的指针
    FUNC fun;
    
    cout << "基类的虚函数表如下: " << endl;
    fun = (FUNC)p[0][0];
    fun(); //函数指针 == 函数地址 == 函数名 ?
    
    fun = (FUNC)p[0][1];
    fun();
    
    fun = (FUNC)p[0][2];
    fun();
    
    cout << "派生类的虚函数表如下: " << endl;
    p = (long**)&d;
    fun = (FUNC)p[0][0];
    fun();
    
    fun = (FUNC)p[0][1];
    fun();
    
    fun = (FUNC)p[0][2];
    fun();
    
    cout << "----------------" << endl;
    Base* pp = &d;
    pp->Func2(); // 基类指针指向派生对象,通过基类指针(或引用)调用派生类虚函数,触发动态绑定
    d.Func2();   // 直接调用,不会触发动态绑定,属于静态绑定
    
    return 0;
}
程序运行结果:
size of Base = 8
size of Derived = 12
基类的虚函数表如下: 
Base::Func1()...
Base::Func2()...
Base::Func3()...
派生类的虚函数表如下: 
Base::Func1()...
Derived::Func2()...
Derived::Func3()...
----------------
Derived::Func2()...
Derived::Func2()...
Program ended with exit code: 0


多重继承中,包含虚函数的基类和派生类的内存模型:

因为是多重继承,不同于单一继承,所以派生类的虚表指针也不止一个,所以代码中可以访问p[1][n],p[2][n],而如果是单一继承,则只有p[0][n]。

验证代码如下:

//
//  VF3.cpp
//  C++
//
//  Created by Cheng Sun on 2017/6/21.
//  Copyright © 2017年 Cheng Sun. All rights reserved.
//

#include <stdio.h>
#include <iostream>

using namespace std;

class Base1 // 4 Bytes (pointer to virtual table) + 4 Bytes (int) = 8 Bytes
{
public:
    virtual void Func1()
    {
        cout << "Base1::Func1()..." << endl;
    }
    
    virtual void Func2()
    {
        cout << "Base1::Func2()..." << endl;
    }
    
    virtual void Func3()
    {
        cout << "Base1::Func3()..." << endl;
    }
    
    int data1_ = 1;
};

class Base2 // 4 Bytes (pointer to virtual table) + 4 Bytes (int) = 8 Bytes
{
public:
    virtual void Func1()
    {
        cout << "Base2::Func1()..." << endl;
    }
    
    virtual void Func2()
    {
        cout << "Base2::Func2()..." << endl;
    }
    
    virtual void Func3()
    {
        cout << "Base2::Func3()..." << endl;
    }
    
    int data2_ = 2;
};

class Base3 // 4 Bytes (pointer to virtual table) + 4 Bytes (int) = 8 Bytes
{
public:
    virtual void Func1()
    {
        cout << "Base3::Func1()..." << endl;
    }
    
    virtual void Func2()
    {
        cout << "Base3::Func2()..." << endl;
    }
    
    virtual void Func3()
    {
        cout << "Base3::Func3()..." << endl;
    }
    
    int data3_ = 3;
};

class Derived : public Base1, public Base2, public Base3
// 4 Bytes (pointer to virtual table) * 3 + 4 Bytes (int, data1_) * 3 + 4 Bytes (int, data4_) = 28 Bytes
{
public: // four virtual funcitons in derived class
    void Func2()
    {
        cout << "Derived::Func2()..." << endl;
    }
    
    void Func3()
    {
        cout << "Derived::Func3()..." << endl;
    }
    
    virtual void  Func4()
    {
        cout << "Derived::Func4()... 此函数是派生类特有的哦,只存在派生类覆盖第一个基类的虚表里" << endl;
    }
    int data4_ = 4;
};

typedef void (*FUNC) (void); //定义一个函数指针,指向一个函数,这函数的类型是 void Fun (void)

int main()
{
    cout << "size of Base = " << sizeof(Base1) << endl;
    cout << "size of Derived = " << sizeof(Derived) << endl;
    
    
    long** p;
    FUNC fun;
    int* da;
    
    //既然当一个类包含虚函数时,系统就会为它自动生成一个虚函数表
    //那么一个类就一个表?如果对于继承了好几个基类的派生类呢?
    //如果不是,那么继承了多个基类的派生类有多少个虚表指针?
    cout << "-----------------" << endl;
    cout << "第一个基类的虚函数表如下: " << endl;
    Base1 b1;
    p = (long**)&b1;
    
    fun = (FUNC)p[0][0];
    fun();
    
    fun = (FUNC)p[0][1];
    fun();
    
    fun = (FUNC)p[0][2];
    fun();
    cout << "-----------------" << endl;
    
    da = (int*)p[1];
    cout << "data1_ = " << da << endl;
    
    cout << "-----------------" << endl;
    cout << "第二个基类的虚函数表如下: " << endl;
    Base2 b2;
    p = (long**)&b2;
    
    fun = (FUNC)p[0][0];
    fun();
    
    fun = (FUNC)p[0][1];
    fun();
    
    fun = (FUNC)p[0][2];
    fun();
    
    cout << "-----------------" << endl;
    
    da = (int*)p[1];
    cout << "data2_ = " << da << endl;
    
    cout << "-----------------" << endl;
    cout << "第三个基类的虚函数表如下: " << endl;
    Base3 b3;
    p = (long**)&b3;
    
    fun = (FUNC)p[0][0];
    fun();
    
    fun = (FUNC)p[0][1];
    fun();
    
    fun = (FUNC)p[0][2];
    fun();
    cout << "-----------------" << endl;
    da = (int*)p[1];
    cout << "data3_ = " << da << endl;
    
    cout << "-----------------" << endl;
    
    cout << "派生类的虚函数表如下: " << endl;
    Derived d;
    p = (long**)&d; //定义一个p, 指向虚表指针的指针
    
    fun = (FUNC)p[0][0];
    fun(); //函数指针 == 函数地址 == 函数名 ?
    
    fun = (FUNC)p[0][1];
    fun();
    
    fun = (FUNC)p[0][2];
    fun();
    
    fun = (FUNC)p[0][3];
    fun();
    
    cout << "-----------------" << endl;
    
    // 为什么跳过p[1][n]呢,因为p[1][0]存放的是 data1_,虽然数据成员并没有存在虚表里面,但是派生类因为继承的关系,依然继承了基类里面的数据成员,所以通过指针访问虚表指针进而调用虚函数时,要跳过派生类中继承的基类的数据成员的内存存储空间(位置)
    // 验证一下:
    da = (int*)p[1];
    cout << "data1_ = " << da << " 成员数据并不存放于虚表中" << endl;
    cout << "-----------------" << endl;
    fun = (FUNC)p[2][0];
    fun();
    
    fun = (FUNC)p[2][1];
    fun();
    
    fun = (FUNC)p[2][2];
    fun();
    
    cout << "-----------------" << endl;
    da = (int*)p[3];
    cout << "data2_ = " << da << " 成员数据并不存放于虚表中" << endl;
    //fun = (FUNC)p[2][3];
    //fun();   //报错,说明继承了多个基类的派生类,它里面独有的虚函数,只存在第一个(按声明顺序)继承的基类的虚表中!!!
    
    // 为什么跳过p[1][n]呢,因为p[1][0]存放的是 data2_
    cout << "-----------------" << endl;
    fun = (FUNC)p[4][0];
    fun();
    
    fun = (FUNC)p[4][1];
    fun();
    
    fun = (FUNC)p[4][2];
    fun();
    
    cout << "-----------------" << endl;
    da = (int*)p[5];
    cout << "data3_ = " << da << " 成员数据并不存放于虚表中" << endl;
    cout << "-----------------" << endl;
    da = (int*)p[6];
    cout << "data4_ = " << da << " 成员数据并不存放于虚表中" << endl;
    //fun = (FUNC)p[4][3];
    //fun();  //报错,同理。
    
    return 0;
}

程序运行结果:

size of Base = 8
size of Derived = 28
-----------------
第一个基类的虚函数表如下: 
Base1::Func1()...
Base1::Func2()...
Base1::Func3()...
-----------------
data1_ = 0x1
-----------------
第二个基类的虚函数表如下: 
Base2::Func1()...
Base2::Func2()...
Base2::Func3()...
-----------------
data2_ = 0x2
-----------------
第三个基类的虚函数表如下: 
Base3::Func1()...
Base3::Func2()...
Base3::Func3()...
-----------------
data3_ = 0x3
-----------------
派生类的虚函数表如下: 
Base1::Func1()...
Derived::Func2()...
Derived::Func3()...
Derived::Func4()... 此函数是派生类特有的哦,只存在派生类覆盖第一个基类的虚表里
-----------------
data1_ = 0x1 成员数据并不存放于虚表中
-----------------
Base2::Func1()...
Derived::Func2()...
Derived::Func3()...
-----------------
data2_ = 0x2 成员数据并不存放于虚表中
-----------------
Base3::Func1()...
Derived::Func2()...
Derived::Func3()...
-----------------
data3_ = 0x3 成员数据并不存放于虚表中
-----------------
data4_ = 0x4 成员数据并不存放于虚表中
Program ended with exit code: 0








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值