【C++八股题整理】虚函数

news2024/9/21 16:24:52

C++八股题整理 - 虚函数

  • 虚函数
    • 虚函数的定义?
    • C++11引入的override和final关键字的作用?
    • 虚函数的实现原理?虚函数表(vbtl)和虚函数表指针(vptr)
    • 虚函数表、虚函数表指针的生成时期及存储位置?
    • 含有虚函数的类的对象的大小?
    • 构造函数和析构函数可以是虚函数吗?
    • 构造函数和析构函数中能否调用虚函数?
    • 哪些函数不能是虚函数?
    • 虚函数和纯虚函数的区别?
    • 虚函数和模板的区别?

虚函数

虚函数的定义?

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

#include<iostream>  
using namespace std;  
  
class A {  
public:  
    void foo() {  printf("1\n"); }  
    virtual void fun() {  printf("2\n");  }  // 虚函数
};  
class B : public A {  
public:  
    void foo() {  printf("3\n"); }  // 派生类的函数屏蔽了与其同名的基类函数
    void fun() {  printf("4\n"); }  // 重写虚函数
};  
int main(void) {  
    A a;  
    B b;  
    A *p = &a;  
    p->foo();  // 1
    p->fun();  // 2
    p = &b;  
    p->foo();  // 取决于指针类型,输出1
    p->fun();  // 取决于对象类型,输出4,体现了多态
    return 0;  
}

派生类B重写了A中的虚函数foo(),B中重写后的foo()同样是一个虚函数(不需要virtual显式标注)。如果B被继承,可以在子类中继续重写。

C++11引入的override和final关键字的作用?

  • override:保证在派生类中声明的重载函数,与基类的虚函数有相同的签名
    • 函数签名不一致:不加override,会视为派生类中新定义的函数;加了override,会报错
    virtual void fun() override;
    
  • final:阻止类的进一步派生 和 虚函数的进一步重写
    • 一个虚函数被定义为final,则派生类中不能再重写它
    virtual void fun() final;
    

虚函数的实现原理?虚函数表(vbtl)和虚函数表指针(vptr)

  • 类 的 虚函数表(vbtl)
    • 当一个类中包含虚函数时,编译器会为该类生成虚函数表,表中保存着该类包含的虚函数的地址。“包含”的意思是继承的+自己新定义的
    • 如果在该类中重写了父类的虚函数A,那就在虚函数表中将A对应的地方,替换成重写后的虚函数的地址
    • 类自己新定义的虚函数,也要将其追加某一张虚函数表上
    • 一个包含虚函数的类,至少有1张虚函数表,即使该类不重写任何虚函数
    • 一个类继承了n个有虚函数的基类,就有n张虚函数表
  • 对象 的 虚函数表指针(vptr)
    • 当一个类中包含虚函数时,该类的对象将会拥有虚函数表指针(vptr)指向该类的虚函数表。虚函数表指针也称虚指针、虚表指针
    • 类有n张虚函数表,类的对象就有n个虚指针,每个指针指向1张虚函数表
      在这里插入图片描述
  • 虚函数的实现原理
    在程序运行时,找到动态绑定到基类指针上的对象,然后根据该对象的虚函数表指针找到对应的虚函数表,从而确定调用哪个版本的虚函数。
    在这里插入图片描述

虚函数表、虚函数表指针的生成时期及存储位置?

  • 虚函数表:在编译时生成,存储在只读数据段
  • 虚函数表指针:在对象创建时生成,位置在对象的头部,根据对象创建方式存储在堆或栈上

含有虚函数的类的对象的大小?

前置知识:C++类对象大小的计算(一)常规类大小计算
含有虚函数的类的对象的大小 = 虚函数表指针(vptr)个数 x 指针大小 + 内存对齐后,对象拥有的非静态成员变量的大小

32位系统下,指针大小为4;64位系统下,指针大小为8。
在64位系统下考虑如下代码:

class Base1 {
public:
    int a;			// size: 4, 内存对齐后为 8
    static int b;	// 静态成员属于类,不计入大小
    virtual void func1() {}
};

class Base2 {
public:
    double c;		// size: 8
    virtual void func2() {}
};

class Derived : public Base1, public Base2 {
public:
    char d;			// size: 1, 内存对齐后为8
    virtual void func3() {}
};

Derived类的对象,共拥有a、c、d三个非静态成员变量,内存对齐后的总大小为8+8+8=24;Derived类的对象还拥有2个虚函数表指针,每个指针的大小为8,因此总的大小为24 + 2 x 8 = 40字节。

构造函数和析构函数可以是虚函数吗?

  • 构造函数不能是虚函数
    • vptr是在构造函数中初始化的,如果将构造函数定义为虚函数,那么在调用构造函数前vptr还未生成,因此无法调用到该构造函数
  • 析构函数应该为虚函数
    • 当基类的指针指向子类的对象时,如果基类的析构函数不为虚函数,那么销毁基类指针时,只会调用基类的析构函数,子类的对象无法被析构,造成内存泄漏

构造函数和析构函数中能否调用虚函数?

在构造函数和析构函数中调用虚函数,不会起到想要的结果。比较下面两段代码:

#include <iostream>

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

    void callShow() {
        std::cout << "Base callShow()" << std::endl;
        show();  // 调用虚函数
    }

    virtual ~Base() = default;
};

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

int main() {
    Derived d;
    d.callShow();  // 调用基类的成员函数,但期望调用派生类的虚函数
    return 0;
}
// Base callShow()
// Derived show()

这段代码中,虚函数show正确地表现出了多态性。而在构造函数和析构函数中调用,不能表现多态性。

#include <iostream>

class Base {
public:
    Base() {
        std::cout << "Base constructor" << std::endl;
        show();  // 调用虚函数
    }

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

    virtual ~Base() {
        std::cout << "Base destructor" << std::endl;
        show();  // 再次调用虚函数
    }
};

class Derived : public Base {
public:
    Derived() {
        std::cout << "Derived constructor" << std::endl;
    }

    void show() override {
        std::cout << "Derived show()" << std::endl;
    }

    ~Derived() {
        std::cout << "Derived destructor" << std::endl;
    }
};

int main() {
    Derived d;
    return 0;
}
// Base constructor
// Base show()
// Derived constructor
// Derived destructor
// Base destructor
// Base show()

在构造函数和析构函数中调用show(),show采用的是基类中的实现。这是因为,在调用到基类的构造函数和析构函数时,派生类中的内容尚没有被创建、或者已经被销毁了。

哪些函数不能是虚函数?

  • 构造函数:执行构造函数前虚表指针尚未初始化,无法正确调用构造函数
  • 内联函数:内联函数在编译阶段进行函数体的替换操作,而虚函数意味着在运行期间进行类型确定,所以内联函数不能是虚函数
  • 静态函数:静态函数不属于对象属于类,静态成员函数没有this指针,因此静态函数设置为虚函数没有任何意义
  • 友元函数,友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法
  • 普通函数,普通函数不属于类的成员函数,不具有继承特性,因此普通函数没有虚函数

总结:不能被继承的函数 和 不能被重写的函数 不能是虚函数

虚函数和纯虚函数的区别?

class A {  
	virtual void example() = 0;	// 纯虚函数
}

纯虚函数是虚函数的一种特殊形式,它的语法是在函数声明后加上’=0’。纯虚函数只有声明没有实现,含有纯虚函数的类称为抽象类,不能被实例化。它的派生类如果想被实例化,就必须实现所有的纯虚函数。

  • 虚函数和纯虚函数都是实现多态性的工具。通过将基类的指针或引用指向派生类对象,可以在运行时调用派生类的重写方法。
  • 虚函数提供了一个默认实现,但派生类可以选择重写它。纯虚函数则强制要求派生类必须提供自己的实现。

虚函数和模板的区别?

模版是一种编译时多态性技术,通过在编译时确定类型来生成特定的代码。
虚函数是运行时多态,在运行时根据对象的实际类型来调用相应的方法,从而实现多态性。

特性模板(Templates)虚函数(Virtual Functions)
决策时间编译时运行时
实现机制编译时生成特定类型代码通过虚函数表动态绑定
类型检查编译时运行时
运行时开销有虚函数表查找开销
使用场景泛型编程,STL容器面向对象编程的多态行为

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2075196.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JS常用事件示例

<!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>JS函数中的事件</title> <…

钡铼技术BL196MQTT远程IO模块工业物联网应用

随着工业物联网&#xff08;IIoT&#xff09;的迅猛发展&#xff0c;工业设备之间的互联互通已成为推动产业升级的关键因素之一。在这个背景下&#xff0c;钡铼技术推出了一款名为BL196MQTT的远程IO模块&#xff0c;该模块专为工业自动化环境中的数据采集与控制而设计&#xff…

搭建深度神经网络(DNN)

利用 numpy 工具&#xff0c;手动搭建一个 DNN 深度神经网络。 定义网络结构 初始化模型参数 循环计算&#xff1a;前向传播/计算当前损失/反向传播/权值更新 1、初始化模型参数 对于一个包含L层的隐藏层深度神经网络&#xff0c;我们在初始化其模型参数的时候需要更灵活一点…

触想强固型工业显示器加速海上油气勘探开发

石油作为现代工业发展的主要能源&#xff0c;已成为国际间政治、经济博弈的重要工具。 一、行业发展背景 过去百年间&#xff0c;人类对陆地油气资源的勘探开发逐渐趋于饱和&#xff0c;而面对持续增长的全球能源需求&#xff0c;海洋勘探已成为当今油气能源角逐的主要“战场”…

Linux文件IO缓存

一、缓冲区大小对 I/O 系统调用性能的影响 总之&#xff0c;如果与文件发生大量的数据传输&#xff0c;通过采用大块空间缓冲数据&#xff0c;以及执行更少的 系统调用&#xff0c;可以极大地提高 I / O 性能 二、stdio 库的缓冲 当操作磁盘文件时&#xff0c;缓冲大块数据以…

合宙LuatOS产品规格书——Air700EAQ

Luat Air700EAQ是合宙的LTE Cat.1bis通信模块&#xff0c;采用移芯EC716E平台&#xff0c;支持LTE 3GPP Rel.13技术。 该模块专为满足小型化、低成本需求而设计&#xff0c;具备超小封装和极致成本优势。 Air700EAQ支持移动双模&#xff0c;内置丰富的网络协议&#xff0c;集…

Qt第二十章 数据库操作

文章目录 Qt操作数据库QSqlDataBaseQSqlQuery执行SQL语句 QSqlRecordQSqlField数据库模型QSqlQueryModelQSqlTableModelQSqlRelationalTableModel 编译MySql驱动msvc版本MySql客户端程序部署 Qt操作数据库 需要在cmakelist加上Sql模块 QSqlDataBase 可以通过静态成员查看支持的…

北京青蓝智慧科技:2024(第九届)世界物联网大会将于11月在京举行

2024年11月&#xff0c;北京将迎来第九届世界物联网大会的盛大启幕。 这一年度盛会由世界物联网大会、中国移动通信联合会、外交理事会携手举办&#xff0c;得到了世界绿色设计组织、世界物联网基金会等机构的大力支持。 大会的宗旨在于推动全球智能联网数字经济的创新进展&a…

Golang | Leetcode Golang题解之第373题查找和最小的K对数字

题目&#xff1a; 题解&#xff1a; func kSmallestPairs(nums1, nums2 []int, k int) (ans [][]int) {m, n : len(nums1), len(nums2)// 二分查找第 k 小的数对和left, right : nums1[0]nums2[0], nums1[m-1]nums2[n-1]1pairSum : left sort.Search(right-left, func(sum in…

Notion 使用详解——基础教程

《Notion 使用详解——基础教程》 一、Notion简介 Notion是一款集笔记、任务、数据库、wiki、知识库等功能于一体的生产力工具&#xff0c;其强大的模块化设计和高度自定义能力&#xff0c;使其成为个人和团队提高工作效率的理想选择。 二、基础操作 1. 创建页面&#xff1a;…

几个很棒的AI问题和精彩回答

这里有几个很棒的与AI相关的问题和精彩的回答&#xff0c;分享给大家 2024&#xff0c;怎么以10倍的速度设计AI产品&#xff1f; 回答嘉宾&#xff1a;Tidyread作者 根据产品定位&#xff0c;对整体风格进行定调 Tidyread 希望人们能从中建立资讯阅读的秩序感&#xff0c;所以…

阿里云对象存储OSS的前端直传-demo

原由 在项目里有时候会碰到比如上传文件相关的&#xff0c;一般都是后端提供个接口&#xff0c;然后我们上传的时候后端再传到阿里OSS或者其他服务商的对象存储&#xff0c;然后把最终的url拿到存起来或者返回给前端&#xff0c;这种方式其实在上传图片的频率不高的业务场景中…

电商数据接口助力电商数据分析||电商运营每日必看5个底层数据

数据分析充电站——深入探索中小企业数字化转型&#xff0c;专注提供各行业数据分析干货、分析技巧、工具推荐以及各类超实用分析模板&#xff0c;为钻研于数据分析的朋友们加油充电。 电商运营店铺涉及大量数据&#xff0c;包括用户行为、交易记录、库存信息等&#xff0c;如何…

Python测试之测试覆盖率统计

本篇承接上一篇 Python测试框架之—— pytest介绍与示例&#xff0c;在此基础上介绍如何基于pytest进行测试的覆盖率统计。 要在使用 pytest 进行测试时检测代码覆盖率&#xff0c;可以使用 pytest-cov 插件。这个插件是基于 coverage.py&#xff0c;它能帮助你了解哪些代码部…

【PySide6-QML】2. 添加菜单栏

文章目录 前言实现添加菜单栏添加菜单添加子菜单点击动作添加快捷键 前章回顾&#xff1a;【PySide6-QML】1. 创建新项目 前言 本文使用 MenuBar 添加工具菜单栏&#xff0c;Action 添加子菜单&#xff0c;并添加快捷键和动作回调。 实现 添加菜单栏 import QtQuick.Contr…

centos mongodb安装+开机启动

1.mongodb安装 Centos系统中mongodb的安装详解_centos安装mongodb-CSDN博客 步骤1-下载 下载地址&#xff1a;Download MongoDB Community Server | MongoDB 步骤2-安装-修改配置 Centos系统中mongodb的安装详解_centos安装mongodb-CSDN博客 下载包到 /usr/local/ 解压 tar…

新一代RK3576芯片,3588平替吗?

瑞芯微RK3576是一款高性能、低功耗的SoC&#xff08;系统级芯片&#xff09;处理器&#xff0c;适用于基于ARM的PC、边缘计算设备、个人移动互联网设备等多种应用场景。它采用Arm架构的八核心CPU&#xff0c;集成了GPU、MCU、NPU、VPU等多种计算核心&#xff0c;并具有丰富的外…

基于深度学习的交通标志检测识别系统(含UI界面、yolov5、Python代码、数据集)

项目介绍 项目中所用到的算法模型和数据集等信息如下&#xff1a; 算法模型&#xff1a;     yolov5、yolov5 SE注意力机制&#xff0c;两个模型都已训练好&#xff0c;可直接使用。 数据集&#xff1a;     网上下载的数据集&#xff0c;格式都已转好&#xff0c;可…

K8S对接Ceph分部署存储

文章目录 一、Ceph理论知识1、Ceph简介2、Ceph分布式存储的优点3、Ceph核心组件 二、部署Ceph高可用集群1、服务器环境信息2、部署前环境准备工作3、部署Ceph监控服务Monitor4、激活Ceph存储服务OSD 三、K8S对接Ceph存储1、K8S对接Ceph RBD实现数据持久化2、基于Ceph RBD生成PV…

【精选】基于数据可视化的智慧社区内网平台(程序员阿龙出品精品)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…