C++虚函数表的原理与使用解析

C++虚函数表的原理与使用解析

简介

在C++的类继承中,为了实现多态,我们常常会使用虚函数。虚函数与虚函数表有着密切的关系,在本文中,我们将深入探讨C++虚函数表的原理和使用。

虚函数的概念

虚函数是在C++中用于实现多态的重要机制。通过在基类中声明虚函数,在子类中覆盖该虚函数,可以在运行时根据对象的实际类型来调用相应的函数实现,从而实现多态。

虚函数的声明方式如下:

class Base {
public:
    virtual void func(); 
};

其中,关键字virtual表示这是一个虚函数。在子类中覆盖该虚函数时,需要使用override关键字:

class Sub : public Base {
public:
    void func() override; 
};

虚函数表的原理

虚函数表(Virtual Table,简称VTable)是C++实现多态机制的重要机制之一。在C++中,每一个带有虚函数的类都会有一个对应的虚函数表,虚函数表里存放着该类所有虚函数的地址。

当调用一个对象的虚函数时,编译器会查询该对象所属的类的虚函数表,找到对应的函数地址并调用之。

在C++中,每一个带有虚函数的类都会被分配一个虚函数表,在其中存储该类所有虚函数的信息。虚函数表一般包括两个元素:一个指向虚函数表本身的指针(vptr),以及该类所有虚函数的地址(vfunc)。vptr的值是在对象创建时被初始化的,指向该对象的虚函数表。

虚函数表的使用

下面,我们来看一个使用虚函数表的例子。假设我们要实现一个图形类,可以绘制不同类型的图形。我们可以定义一个基类Shape,声明一个虚函数draw(),用于绘制图形。同时,派生出不同类型的子类Circle、Square等,重写draw()函数。

class Shape {
public:
    virtual void draw() = 0;
};

class Circle : public Shape {
public:
    void draw() override {
        cout << "绘制一个圆形" << endl;
    }
};

class Square : public Shape {
public:
    void draw() override {
        cout << "绘制一个正方形" << endl;
    }
};

可以看到,在基类Shape中声明了一个纯虚函数draw(),并在派生类Circle和Square中实现了该函数。现在,我们可以创建不同类型的图形对象,并使用它们的draw()函数进行绘制。

int main() {
    Circle c;
    Square s;

    Shape *pShape = &c;
    pShape->draw();

    pShape = &s;
    pShape->draw();

    return 0;
}

在这个例子中,首先我们定义了一个Circle对象和一个Square对象,然后用Base类指针pShape依次指向两个对象,并调用它们的draw()函数。程序将会输出:

绘制一个圆形
绘制一个正方形

这是因为,在draw()函数被调用时,编译器会根据pShape指针所指对象的类型,找到相应的虚函数地址并调用之,从而实现多态。

示例2

来看一个稍微复杂一些的例子。假设我们有一个基类Animal,派生出类Dog和类Cat。除了虚函数func()外,派生类还定义了其他类型的函数,如Dog::bark()和Cat::meow()。我们可以通过调用派生类的非虚函数来验证虚函数表的使用:

#include <iostream>

using namespace std;

class Animal {
public:
    virtual void func() {
        cout << "Animal func()" << endl;
    }
    void sleep() {
        cout << "Animal sleep()" << endl;
    }
};

class Dog : public Animal {
public:
    void func() override {
        cout << "Dog func()" << endl;
    }
    void bark() {
        cout << "Dog bark()" << endl;
    }
};

class Cat : public Animal {
public:
    void func() override {
        cout << "Cat func()" << endl;
    }
    void meow() {
        cout << "Cat meow()" << endl;
    }
};

int main() {
    Animal *pAnimal = new Dog;
    pAnimal->func();
    pAnimal->sleep();
    //pAnimal->bark();//编译出错
    delete pAnimal;

    pAnimal = new Cat;
    pAnimal->func();
    pAnimal->sleep();
    //pAnimal->meow();//编译出错
    delete pAnimal;

    return 0;
}

在这个例子中,我们首先定义了一个Animal类,并在其中声明了虚函数func()和普通函数sleep()。接着,我们派生出了Dog类和Cat类,并分别重写了func()函数并增加了各自的其他函数。

在main()函数中,我们创建了一个指向Dog对象的Animal指针pAnimal,并调用了两个函数:func()sleep()。输出结果为:

Dog func()
Animal sleep()

可以看到,虽然pAnimal指向的是Dog对象,但是调用func()函数时并没有调用基类的实现,而是调用了Dog类中的实现,这就是虚函数多态的实现。

至于为什么不能调用bark()函数,那是因为该函数是派生类Dog的非虚函数,在编译时会被认为是Animal类的函数。因此,如果我们尝试通过Animal指针调用该函数,就会编译出错。同样地,尝试通过Animal指针调用Cat类的函数meow()也会失败。

阅读剩余 69%

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++虚函数表的原理与使用解析 - Python技术站

(0)
上一篇 2023年6月26日
下一篇 2023年6月26日

相关文章

  • Python 实现静态链表案例详解

    Python 实现静态链表案例详解 静态链表的概念 静态链表是一种数据结构,其本质是利用数组来实现链表结构。相比于常规链表,静态链表相对于占用更多的存储空间,但是其在随机访问、插入和删除元素时,性能更高。 静态链表的实现原理 以 Python 实现静态链表为例,静态链表的实现原理如下: 定义一个数组,数组中的每个元素包含两部分内容:数据和下一个元素的下标。 …

    other 2023年6月27日
    00
  • MySQL约束constraint用法详解

    MySQL约束constraint用法详解 MySQL约束(constraint)是一种限制数据库中数据输入的规则,它可以保证数据的准确性和完整性。在MySQL中,常用的约束类型包括主键(primary key)、外键(foreign key)、唯一约束(unique)、非空约束(not null)、默认值约束(default)等。 主键(primary k…

    other 2023年6月25日
    00
  • arcgis布局视图如何调整大小? arcgis改变布局视图方向以及大小的技巧

    ArcGIS布局视图如何调整大小 在ArcGIS中,可以通过以下步骤来调整布局视图的大小: 打开ArcGIS软件并加载你的布局视图。 在布局视图中,选择“布局”选项卡。 在“布局”选项卡中,点击“页面和打印设置”按钮。 在弹出的对话框中,选择“页面设置”选项卡。 在“页面设置”选项卡中,你可以调整布局视图的大小。你可以选择预设的页面大小,也可以手动输入自定义…

    other 2023年9月5日
    00
  • C#操作INI配置文件示例详解

    下面是详细的“C#操作INI配置文件示例详解”攻略。 什么是INI文件? INI文件是一种简单的文本文件,它通常用于存储程序的配置信息。INI文件由若干个节组成,每个节中包含若干个键值对,键值对用等号连接,例如: [Database] Server=127.0.0.1 Port=3306 Username=root Password=123456 C#如何操…

    other 2023年6月25日
    00
  • C++ Array容器的显示和隐式实例化详细介绍

    这里为你详细讲解“C++ Array容器的显示和隐式实例化详细介绍”。 什么是Array容器? C++中的Array容器是一个固定大小的数据结构,可以在声明时指定其大小,且大小不能改变。与C++中的原始数组类似,Array容器也是以0作为索引的。与原始数组不同的是,Array容器提供了一些高级的功能,例如动态分配内存,遍历元素,复制/填充数组等。 显示实例化…

    other 2023年6月26日
    00
  • 点评js异步加载的4种方式

    点评JS异步加载的4种方式 在优化网站性能的过程中,经常需要对JS脚本进行异步加载。点评网作为一个旅游服务平台,需要对JS脚本加载进行优化处理,以确保页面加载速度和用户访问体验。本文将介绍点评网使用的四种JS异步加载方式。 1. 动态创建script节点 动态创建script节点是最常用的JS异步加载方式之一。通过这种方法可以在文档加载期间获取到JS资源,并…

    other 2023年6月25日
    00
  • js使用函数绑定技术改变事件处理程序的作用域

    当我们在JavaScript中编写事件处理程序时,通常会遇到一个问题:在事件处理程序内部,this关键字的值会指向触发事件的元素。然而,有时候我们希望在事件处理程序内部访问其他作用域中的变量或方法。这时,我们可以使用函数绑定技术来改变事件处理程序的作用域。 函数绑定技术可以通过bind()方法来实现。bind()方法会创建一个新的函数,该函数的this值被绑…

    other 2023年8月20日
    00
  • C语言循环控制入门介绍

    C语言循环控制入门介绍 在C语言中,循环控制语句是非常常用的,它可以使相同的代码块多次执行,从而简化程序的编写。C语言有三种循环控制语句:while、do-while和for,本文将为您介绍循环控制的基础知识和语法,以及几个常见的用法。 while循环 while循环控制语句是C语言中最基本的一种循环控制语句,它的基本语法如下: while(conditio…

    other 2023年6月27日
    00
合作推广
合作推广
分享本页
返回顶部