C++中的虚函数机制

目录

前言:虚函数机制为C++引入了一些动态特性,使得C++编程更加安全,拓展性更强,可维护性更好

1.什么是虚函数

2.2个重要的动态特性

1.函数的动态绑定(多态实现,动态获取对象类型,从而进行后续的行为)

2.运行时候类型识别

3.函数的绑定含义和静态绑定

什么是函数的绑定:一个函数的调用和函数的实现关联起来的过程;

4.函数动态绑定

为什么需要动态绑定,一般A类型指针指向A类型对象,B类型指针指向B类型对象,但在多态情况下,需要A类型指针指向B类型对象,这时候就需要函数的动态绑定

4.虚函数实现动态绑定的原理

4.1虚函数表的生成与共享

4.2虚函数表的初始化与销毁

4.3多重继承和虚函数表

5.C++运行时候的类型识别(RTTI)

1.RTTI应用场景

2.typeid

3.dynamic_cast

4.RTTI和虚函数


前言:虚函数机制为C++引入了一些动态特性,使得C++编程更加安全,拓展性更强,可维护性更好

1.什么是虚函数

虚函数:C++中使用virtual声明的函数,其背后会有复杂的机制,通过这套机制为C++引入了一些动态特性

2.2个重要的动态特性

1.函数的动态绑定(多态实现,动态获取对象类型,从而进行后续的行为)

2.运行时候类型识别

3.函数的绑定含义和静态绑定

什么是函数的绑定:一个函数的调用和函数的实现关联起来的过程;

C++默认的静态绑定,在查找和关联的过程中是在编译期间完成的,普通函数,重载函数和成员函数都是静态绑定;


#include <iostream>
using namespace std;



//普通函数
void dosomthing(){


}

//重载函数
void func(int){}
void func(double){}


//成员函数
struct _myClass
{

    void dosomthing(){}
};


void test(){

    //都是静态绑定,能够提高程序的性能
    dosomthing();
    func(100);

    _myClass my;
    my.dosomthing();

}

4.函数动态绑定

为什么需要动态绑定,一般A类型指针指向A类型对象,B类型指针指向B类型对象,但在多态情况下,需要A类型指针指向B类型对象,这时候就需要函数的动态绑定

#include <iostream>
using namespace std;


struct _animal
{
    void speak() {
        cout << "Animal speak" << endl;
    }
};

struct _dog : _animal
{
    void speak() {
        cout << "Dog speak" << endl;
    }
};

struct _cat : _animal
{
    void speak() {
        cout << "Cat speak" << endl;
    }
};

void test()
{
    _animal* animal = nullptr;
    animal = new _dog;
    animal->speak();
    animal = new _cat;
    animal->speak();
}

int main()
{
    test();
    return 0;
}


运行结果

为什么会这样

原因是这时候还是静态绑定,编译的时候指针类型会指向什么类型的对象

那怎么解决这个问题,这时候就需要函数的动态绑定;

怎么解决,函数的绑定的行为不在编译期间完成,在运行的时候检查指针类型的运行空间进行函数的动态绑定即可,这时候就需要用到虚函数机制;

1.编译期间,编译器检查到类有虚函数的时候,就不会简单的根据对象类型进行函数绑定

2.运行阶段,会根据实际的对像类型进行函数的绑定,然后调用;

struct _animal
{
    virtual void speak() {
        cout << "Animal speak" << endl;
    }
};

子类可以加上virtual,也可以不加;

4.虚函数实现动态绑定的原理

当类中有虚函数的时候,编译器会在对象的第一个数据成员的位置生成一个vfptr指针(构造函数中进行初始化),这个指针指向虚函数的地址的一个数组,这个数组就是虚函数表;

子类如果没有重写基类的虚函数的时候,子类继承基类的虚函数表,vfptr指针指向基类的函数;

子类如果重写了基类的虚函数的时候,虚函数表就会重新被覆盖,vfptr指针会指向子类的虚函数表中对应的函数;调用对应的函数的时候,会调用虚函数指针去找到虚函数表,在虚函数表中找到要执行函数的地址然后执行;

注意的地方:函数的静态绑定的效率比动态绑定的效率高;

4.1虚函数表的生成与共享

编译器会为每一个包含虚函数的类生成虚函数表,该类型的所有对象共享一个虚函数表,这个表包含了该类还有基类的所有虚函数的地址;

#include <iostream>
using namespace std;


struct _animal
{
    virtual void speak() {
        cout << "Animal speak" << endl;
    }
};

struct _dog : _animal
{
    void speak() {
        cout << "Dog speak" << endl;
    }
};

struct _cat : _animal
{
    void speak() {
        cout << "Cat speak" << endl;
    }
};

void test()
{
    //虚函数表中一个地址是一级指针,vfptr指向的数组是二级指针,那么vfptr是三级指针
    _animal* animal = nullptr;
    animal = new _animal;
    void** vfptr = *(void***)animal;
    cout << "Animal 虚函数表地址" << vfptr << endl;

    animal = new _dog;
    vfptr = *(void***)animal;
    cout << "Dog 虚函数表地址" << vfptr << endl;
    
    animal = new _cat;
    vfptr = *(void***)animal;
    cout << "Cat 虚函数表地址" << vfptr << endl;

   // vfptr = *(void***)animal;

    //void(*p_func)() = reinterpret_cast<void(*)()>(vfptr[1]);
}

int main()
{
    test();
    return 0;
}


运行结果如下,发现每个类的虚函数地址都是不一样的

4.2虚函数表的初始化与销毁

虚函数表的初始化和销毁由编译器完成,在对象的构造过程中,编译器会确保对象的虚函数指针指向正确的虚函数表,一般程序结束的时候会销毁虚函数表;

4.3多重继承和虚函数表

多重继承的情况下,可能存在多个虚函数表

5.C++运行时候的类型识别(RTTI)

1.RTTI应用场景

C++ 中用typeid和dynamic_cadt时,涉及到运动时类型识别,也就是运行的时候确定变量或者对象的类型

2.typeid

通国typeid运算符可以获取变量的类型,可以在编译或者运行的时候都可以获得变量的类型

int a = 10;
cout << typeid(a).name << endl;

int*p = &a;
cout << typeid(*p).name << endl;

Box*box = new Box;
cout << typeid(*box ).name << endl;



什么情况下需要在运行的时候获取变量的类型?

在多态的情况下 ,typeid才能在运行期间进行变量类型的获取,只要基类包含了虚函数,构造函数和析构函数都行,就启动了虚函数机制

3.dynamic_cast

首先了解指针的类型,决定了指针的操作范围,A类型指针double能操作8字节的范围,B类型int能操作4字节的范围,存在越界的情况

dynamic_cast能够去检验具有继承关系的父子类型的指针,引用的转换是否安全,也分为编译器转换和运行期转换   

子类大小>= 父类大小 也就是子类的指针的操作范围>= 父类的指针的操作范围,所以子类转换为父类是安全的,但反过来就是不安全的,可能存在越界的情况;

编译时的类型转换

#include <iostream>
using nameSpace std;

//这是一种向上的安全的类型转换
class Animal{};
class Dog : public Animal {};

void test()
{
    Dog* dog = new Dog;
    Animal* animal = dynameic_cast<Animal*>(dog);
}

运行时的类型转换

#include <iostream>
using nameSpace std;

//这是一种向上的安全的类型转换
class Animal{
    ~Animal() = default;
};
class Dog : public Animal {};

void test()
{
    Animal* animal= new Animal;
    Dog* dog = new Dog;
    Animal* animal = dynameic_cast<Animal*>(dog);
    //编译器转换失败
    Dog* dog = dynameic_cast<Dog*>(animal);
}

这时候需要多态的方式不会报错

#include <iostream>
using nameSpace std;

//这是一种向上的安全的类型转换
class Animal{
   virtual ~Animal() = default;
};
class Dog : public Animal {};

void test()
{
    Animal* animal= new Animal;
    Dog* dog = new Dog;
    Animal* animal = dynameic_cast<Animal*>(dog);
    //编译器转换失败
    Dog* dog = dynameic_cast<Dog*>(animal);
}

这样就不会报错,通过返回值就可以判断是否转换成功

4.RTTI和虚函数

typeid和dynamic_cast在运行时候类型识别的时候,依赖虚函数机制;

为什么?

C++中,在编译期间对象的类型都是明确的,指针指向不明确的情况下,需要获取类型,干脆把信息合并到虚函数表中,减少复杂度

  • 12
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值