虚函数的内部实现原理深入剖析

本文深入解析C++中虚函数的实现原理及其与多态的关系,详细介绍了虚函数表(vftable)和虚函数指针(vfptr)的概念,以及它们如何在运行时确定函数地址,实现动态多态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

虚函数实现原理

今天学C++多态时,对虚函数实现原理有些疑惑。

然后爬了很多文,看了很多教程。

经过一系列研究,以下是我的理解:

多态是C++面向对象三大特性之一

多态分为两类

  • 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
  • 动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

注意:请按照编号顺序查看代码说明

#include<iostream>
#include<string>
using namespace std;

//动物类
class Animal 
{
public:
	virtual void speak() 
	{
        // 1.原理:
        
        //没写virtual时 这是一个非静态的成员函数
        //它不属于类上面,是分开存储的
        //现在这个类是空类,空类的大小是1字节
        
        //但如果加了virtual后,类大小为4字节,变成虚函数
        //这个类的结构发生了变化,因为里面会添加一个4字节大小的指针
        //叫vfptr(virtual function pointer 虚函数(表)指针)

        //这个指针会指向一个虚函数表
        //vftable(virtual function table 虚函数表)

        
        //虚函数指针  &对象名
		//虚函数表  *(&对象名) - 第一个虚函数的地址
		//虚函数表 **(&对象名) - 第一个虚函数
        
        
        //所有的虚函数都是通过 虚函数指针 在虚函数表中调用的
        //虚函数表会记录这个类中所有的虚函数的地址,
        //可以理解为 它是一个 只存放这个类的虚函数的地址表

        //不管你在这个类中写多少个虚函数,都不会影响这个实例的大小
        //依旧是4字节,因为虚函数也是分开存储的

        //小知识:在C++的标准说明书中说到
        //编译器必须要保证 虚函数表的指针 存在于对象的实例中 
        //最前面的位置
        //也就是那个对象最开始的地址
        //(这是为了保证正确取到虚函数的偏移量)

        cout << "动物在说话" << endl;
	}
};


//猫类
class Cat : public Animal
{
public:
    
    // 2.
    
	// 重写函数:函数返回值类型,函数名,形参列表 要完全相同 才叫重写
	// 这里写不写 virtual都可以 
	// 如果父类的是虚函数的话 重写时 不写的virtual话 也会默认是virtual
    
	void speak() {cout << "小猫在说话" << endl}
};

//狗类
class Dog : public Animal
{
public:
	void speak() {cout << "小狗在说话" << endl;}
};


// 5.
// 动态多态满足条件:
// 1.有继承关系
// 2.子类要重写父类的虚函数
//(重写:函数返回值类型,函数名,形参列表 要完全相同 才叫重写)

// 动态多态使用:
// 父类的指针或者引用 执行子类对象


// 4.

void doSpeak(Animal& animal) // Animal & animal = cat;  
 
//C++中允许父子之间的类型转换,不需要做强制类型转换
{

	//引用的本质是起别名,引用里有一个常量指针指向cat
	//引用本质:Animal* const animal = &cat;

	//所以当用animal这个引用调用speak函数时,实际上是:
	//引用通过Cat的首地址找到虚函数表,从虚函数表通过指针偏移量获取speak函数的地址,从而调用speak函数
	//因为speak的作用域再Cat下面,所以会调用Cat下的speak

	animal.speak(); 
    // 因为我们这个函数的地址是早绑定,所以输出的是“动物在说话”
}



void test01()
{
	Cat cat;
    
	// 3.
    
	//已知创建子类时会创建一个父类
	//父类中虚函数表内部记录了虚函数的地址:&Animal::speak
	//然后继承给子类

	//子类就拥有了所有父类属性,此时子类中虚函数表的地址依然还是 &Animal::speak
	
	//但是!!

	//我们重写了子类函数(此时重写的函数默认是vitural,所以vitural写不写都可以)
	//这时虚函数指针 指向的虚函数表 因为重写 就覆盖&Animal::speak 变成 &Cat::speak 这个地址

	doSpeak(cat);
	Dog dog;
	doSpeak(dog);
}

void test02() {cout << "sizeof Animal = " << sizeof(Animal) << endl;}

int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

如果文章有误,欢迎指正: 2892870137@qq.com
欢迎拜访我的主博客:https://h-james.github.io/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值