首页 > 编程语言 >C++ 虚函数与动态绑定

C++ 虚函数与动态绑定

时间:2023-06-13 15:34:26浏览次数:52  
标签:虚表 函数 指向 void 绑定 C++ vfunc1 指针

多态与动态绑定

为了实现 C++ 的多态,C++ 使用了动态绑定技术,该技术的核心是虚函数表(简称虚表)。

类的虚函数表

每个包含了虚函数的类都包含一个虚表,一个子类如果继承了包含虚函数的父类,那么这个类也拥有自己的虚表,例如

class A {
public:
    virtual void vfunc1();
    virtual void vfunc2();
    void func1();
    void func2();
private:
    int data1_, data2_;
};
class B : public A {
public:
    void vfunc1() override;
    void func1();
};
class C : public B {
public:
    void vfunc2() override;
    void func2();
private:
    int data1_, data2_;
};

A 包含虚函数 vfunc1() ,B 继承自 A,A 的虚表如图所示 UMqKwRv6nlpISct

虚表是一个指针数组,其元素是虚函数的指针,数组中的每个元素对应一个虚函数的指针。普通的函数(即非虚函数),其调用并不需要经过虚表,所以虚表的元素并不包括普通函数的函数指针。

虚函数指针的赋值发生在编译器的编译阶段,也就是在编译阶段,虚表就被构建出来了。

虚表指针

虚表是属于类的(有点像静态成员变量),而不属于某个具体的对象,一个类只需要一个虚表即可,同一个类的所有对象都使用同一个虚表。

为了指定对象的虚表,对象内部包含一个虚表的指针,来指向自己所使用的虚表。为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个指针,*__vptr,用来指向虚表。这样,当类的对象在创建时便拥有了这个指针,且这个指针的值会自动被设置为指向类的虚表。

一个子类的父类如果包含虚函数,那么这个子类也拥有自己的虚表,所以这个子类的对象也包含一个虚表指针,用来指向它的虚表。 2IiUQ9NMtksgCpv

类 A 包括两个虚函数,故 A 的虚函数表包含两个指针,分别指向 A::vfunc1()A::vfunc2()

类 B 继承于类 A,故类 B 可以调用类 A 的函数,但由于类 B 重写了 B::vfunc1() 函数,故 B 的虚函数表的两个指针分别指向 B::vfunc1()A::vfunc2()

类 C 继承于类 B,故类 C 可以调用类 B 的函数,但由于类 C 重写了 C::vfunc2() 函数,故C 的虚表的两个指针分别指向B::vfunc1()(指向继承的最近的一个类的函数)和 C::vfunc2()

对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向其继承的最近的一个类的虚函数

动态绑定

C++ 的动态绑定即是通过虚表虚表指针来实现的。

承接上面的代码,假设我们实例化子类 B 的对象bObj,并声明基类 A 的指针 p,然后让 p 指向 bObj,即 A *p = &bObj;,那么,当我们使用 p 来调用 vfunc1() 时,由于 __vptr 也是基类的一部分,因此 p->__vptr 的值实际上是 &bOjb->__ptr,即 p->__vptr 指向了类 B 的虚函数表,因此 p->vfunc1() 实际上调用了 B::vfunc1()

因此,我们只要注意虚表指针的值,即可弄清楚到底调用了哪个类的函数。

我们把虚表调用虚函数的过程称为动态绑定,其表现出来的现象称为运行时多态,传统的函数调用我们称为静态绑定(函数调用在编译阶段就能确定下来了)。

执行函数的动态绑定需要符合以下三个条件:

  • 通过指针调用函数;
  • 指针发生从子类向父类的转换,例如 A *p = &bObj;
  • 调用的是虚函数;

参考资料

标签:虚表,函数,指向,void,绑定,C++,vfunc1,指针
From: https://www.cnblogs.com/zwyyy456/p/17477653.html

相关文章

  • C++中malloc/free与new/delete的区别与联系
    原文:https://blog.csdn.net/u010510020/article/details/76266505 一、用法:  用malloc申请一块长度为length的整数类型的内存,程序如下:   int*p=(int*)malloc(sizeof(int)*length);   我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。 ......
  • MVVM开发模式+双向数据绑定及扩展
    一、MVVM开发模式(1)前端的视图层概念=》由MVC演化(2)M:model【模型层】:渲染页面所以来的数据源(通过ajax从服务端获取的数据)  V:view【视图层】:将数据模型转换成UI展示给用户  VM:【视图模型层】:当监听到DOM变化时,会自动地更新数据源里面所依赖的数据......
  • C++ Today 03
    3.0运算符用于:执行代码的运算运算符类型:算数运算符用于处理s四则运算赋值运算符用于将表达式赋值给变量比较运算符用于表达式的比较,并返回一个真值和假2值逻辑运算符用于根据表达式的值返回真值或假值3......
  • 如何实现一个函数重载的功能
    函数重载将函数接收到的不同参数,进行不同处理。importcreateOverLoadfrom'./funReload.js'constgetUsers=createOverLoad()getUsers.addImpl(()=>{console.log('查询所有用户')})getUsers.addImpl('string',(name)=>{console.log('......
  • C/C++ include 头文件的语句中,双引号和尖括号的区别
    #include指令有两种使用形式#include<stdio.h>文件名放在尖括号中#include“mystuff.h”文件名放在双引号中 双引号"xxx.h",表示编译器先在用户的工作目录下搜索头文件,如果搜索不到则到系统默认目录下去寻找,所以双引号一般用于包含用户自己编写的头文件。如:#include"stu......
  • 【技术积累】JavaSciprt中的函数【一】
    什么是函数?如何声明函数?JavaScript中的函数是一段可重复使用的代码块,它可以接受输入并返回输出。在JavaScript中,函数是一种特殊的对象,因此可以将其存储在变量中,将其作为参数传递给其他函数,并从其他函数中返回。在JavaScript中,声明函数有两种方式:函数声明和函数表达式。1.函数......
  • c++ linux基础学习第一课
    课程目标:1.shell命令解析器shell就是命令解析器,将用户命令翻译成内核能够识别的指令。shell常用的快捷键:tab:补齐命令,补齐文件(包括目录和文件)ctrl+a光标移动到头部,ctrl+e光标移动到尾部2.linux下主要目录:/bin保存着二进制文件、可执行程序和shell命令/sbins是superu......
  • C++ Windows.h max宏与std::max冲突问题解决
    C语言引入的宏支持了一定程度的元编程,但它仅仅是简单的字符串替换,这种“六亲不认”的操作很容易导致一些编译错误。这篇文章介绍了一种场景:项目同时引入了老的C头文件,里面用宏定义了一些宏函数;还引入了C++的头文件,里面用其他方式定义了一些同名函数。具体到问题本身,这个......
  • Linux打包C++应用deb脚本
    目录结构├──CMakeLists.txt├──README.md├──scripts│  └──build_deb.sh├──src│  └──app.cpp└──VERSION打包脚本#!/bin/bashPROJECT_NAME="my-app"PROJECT_PATH=$(cd"$(dirname${BASH_SOURCE[0]})";cd..;pwd)MAINTAINER="jojo......
  • DQL-聚合函数
           ......