虚函数(日志20)

一、虚函数(Virtual Function)

1.1 定义和作用

虚函数是在基类中使用关键字 virtual 声明的成员函数,它允许派生类对其进行重写(Override),实现运行时多态。当通过基类指针或引用调用虚函数时,实际调用的是对象类型对应的派生类中的函数,这个过程称为动态绑定(Dynamic Binding)或晚绑定(Late Binding)。

1.2 实现原理

虚函数的实现原理基于虚函数表(Virtual Table,简称VTable)。每个使用虚函数的类都有一个虚函数表,该表是一个函数指针数组,存储了指向类的虚函数的指针。类的每个实例都包含一个指向其虚函数表的指针(vptr),通过这个指针可以找到并调用正确的虚函数实现。

当派生类覆盖(重写)基类的虚函数时,派生类的虚函数表中相应位置的函数指针会被更新为指向派生类中的函数。如果派生类没有重写虚函数,则派生类的虚函数表中会保留指向基类虚函数的指针。

1.3示例

#include <iostream>
using namespace std;

class Base {
public:
    virtual void show() {
        cout << "Base class show" << endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        cout << "Derived class show" << endl;
    }
};

int main() {
    Base* b = new Derived();
    b->show(); // 输出:Derived class show
    delete b;
    return 0;
}

1.4 虚函数的重写

虚函数的重写(Override)是面向对象编程中实现多态性的一种方式。虚函数允许派生类根据需要改变或扩展基类中的行为。这里,我们将详细探讨虚函数的重写,包括它的定义、规则以及一些注意事项。

定义

虚函数重写指的是派生类中提供一个函数版本,该版本与基类中具有相同名称、相同返回类型和相同参数列表的虚函数相匹配。通过这种方式,派生类可以提供自己特定的实现,替换或扩展基类的行为。

规则

函数签名必须匹配:要重写基类中的虚函数,派生类中的函数必须具有相同的名称、返回类型和参数列表。

基类函数必须是虚函数:只有虚函数可以被重写。如果基类中的函数不是虚函数,派生类中相同签名的函数会隐藏(而非重写)基类中的函数。

访问权限可以不同:虚函数在派生类中的访问级别(public、protected、private)可以与基类中的不同,但这会影响到函数的访问性。

使用 override 关键字(C++11及以上):虽然不是强制的,但建议在派生类中重写虚函数时使用 override 关键字,这有助于编译器检查函数签名是否正确匹配,避免潜在的错误。

注意事项

析构函数应该是虚的:如果一个类有可能被继承,并且通过基类指针来删除派生类对象,那么基类的析构函数应该是虚的。这确保了通过基类指针删除派生类对象时,能够正确地调用派生类的析构函数。

构造函数不能是虚函数:在C++中,构造函数不能被声明为虚函数。因为构造函数是用来创建对象的,而虚函数的调用需要通过对象的虚函数表,这在对象构造阶段还未完全建立。

使用 final 关键字防止进一步重写:在某些情况下,你可能希望禁止进一步重写某个虚函数。C++11引入了final关键字,可以用来阻止派生类重写特定的虚函数。

#include <iostream>

class Base {
public:
    virtual void print() const {
        std::cout << "Base class print function" << std::endl;
    }
    virtual ~Base() {} // 虚析构函数
};

class Derived : public Base {
public:
    void print() const override { // 使用override确保正确重写
        std::cout << "Derived class print function" << std::endl;
    }
};

int main() {
    Base* b = new Derived();
    b->print(); // 输出:Derived class print function
    delete b; // 正确调用派生类析构函数
    return 0;
}

在上述示例中,Derived 类重写了 Base 类中的 print 函数,并且基类的析构函数被声明为虚函数,确保了通过基类指针删除派生类对象时能够正确调用派生类的析构函数。

通过理解和正确应用虚函数的重写,可以充分利用C++的多态性,设计出灵活且易于维护的面向对象程序。

1.5 基类和派生类的虚函数表

当涉及到继承时,虚函数表(vtable)的处理方式会稍微复杂一些,但关键点在于每个类都有自己的虚函数表,而不是只有一个。这意味着,如果有派生类继承自基类,并且这些类中包含虚函数,那么每个类将拥有各自独立的虚函数表。下面我们来详细解释这个过程。

基类:在基类中,编译器会为其创建一个虚函数表,这个表包含了基类中所有虚函数的地址。如果派生类没有覆盖(重写)这些虚函数,派生类对象的虚函数表会复制基类虚函数表中相应的条目。

派生类:当派生类覆盖(重写)基类中的虚函数时,派生类的虚函数表中对应位置的函数指针会被更新为指向派生类中的函数实现。如果派生类引入了新的虚函数,这些新的虚函数也会被加入到派生类的虚函数表中。

多重继承:在多重继承的情况下,每个基类都会有自己的虚函数表。派生类对象会包含多个虚函数表指针,每个指针指向对应基类的虚函数表。如果派生类覆盖了某个基类的虚函数,那么相关基类虚函数表中的条目会被更新为指向派生类中的实现。

全部评论

相关推荐

不愿透露姓名的神秘牛友
12-25 19:08
京东科技 软开 26*20 硕士985
点赞 评论 收藏
分享
美团&nbsp;(数据开发)-一面1.算法:归并链表2.编程语言:可变类型与不可变类型,tuple设计出来的作用是什么,深拷贝浅拷贝,闭包,继承与多态,面向对象与面向过程,Python内存管理机制3.算法:归并排序,选择排序,冒泡排序,快速排序的过程,时间,空间复杂度,快排能只用o1的空间复杂度吗,二叉树,二叉查找树,二叉线索树,b树,查找复杂度,树的遍历,栈和队列,如何简单实现4.数据开发:spark&nbsp;rdd是什么,spark任务执行的底层逻辑,join的底层逻辑,&nbsp;数据仓库一般架构是什么样的5.操作系统:并发和并行,协程是什么,用在什么场合6.计网:五层模型,tcp/udp,三次握手,四次握手,能否两次握手,四次握手中间连接中断如何处理美团&nbsp;(数据开发)-二面挖项目(不深)1.&nbsp;数仓有哪几层,建立表的时候有什么需要注意的问题吗,举个例子,有哪些需要规范化的地方,这样规范化有什么样的好处2.&nbsp;MySQL和NoSQL,讲一讲区别,优劣势3.&nbsp;讲一种你熟悉的机器学习算法,(答:attention),讲一讲它的原理,举个例子说明一下,你怎样评价一个模型的表现4.&nbsp;random&nbsp;forest和gbdt原理讲一下,优劣势是什么5.&nbsp;Git冲突怎么办,你如何撤回Git操作6.&nbsp;聊一聊你对大语言模型的理解,它现在在哪些应用上落地,国内外技术上有什么样的差距7.&nbsp;聊一聊你在工作中如何提出一个方案,并选择确定一个方案的8.&nbsp;在项目中你是担任什么样的角色,你是如何跟进一个项目的进度的
查看14道真题和解析
点赞 评论 收藏
分享
kiramekuyuki:想要你的时候怎么都match,我做cv的有过风控的面试,还拿到过大模型的岗位,华子甚至让我去做路由算法。不想要你的时候一个做智驾cv的岗位也能挂掉。
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务