【C++】虚函数相关常见问题

news2025/5/22 18:47:13

【C++】虚函数相关常见问题

文章目录

  • 【C++】虚函数相关常见问题
    • 1.说说为什么要虚析构?
    • 2. C++默认的析构函数为什么不是虚函数?
    • 3. 构造函数能不能是虚函数
    • 4. 说说什么是虚继承,解决什么问题,如何实现?
    • 5. 说说什么是虚函数
    • 6.说说虚函数的实现原理
    • 7.说说纯虚函数
    • 8.说说纯虚函数能实例化吗?为什么?派生类要实现吗?为什么?
    • 9.说说C++中虚函数与纯虚函数的区别
    • 10.请问构造函数中能不能调用虚方法
    • 11.如何理解抽象类?
    • 12.除了虚函数,还有什么方式能实现多态?
    • 13.说说什么是虚基类,可否被实例化?
    • 14. C++中那些函数不能被声明为虚函数?
    • 15. 虚函数表里存放的内容是什么时候写进去的?

1.说说为什么要虚析构?

虚析构:将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。如果基类的析构函数不是虚函数,在特定情况下会导致派生来无法被析构。

  1. 用派生类类型指针绑定派生类实例,析构的时候,不管基类析构函数是不是虚函数,都会正常析构

  2. 用基类类型指针绑定派生类实例,析构的时候,如果基类析构函数不是虚函数,则只会析构基类,不会析构派生类对象,从而造成内存泄漏。为什么会出现这种现象呢,个人认为析构 的时候如果没有虚函数的动态绑定功能,就只根据指针的类型来进行的,而不是根据指针绑 定的对象来进行,所以只是调用了基类的析构函数;如果基类的析构函数是虚函数,则析构 的时候就要根据指针绑定的对象来调用对应的析构函数了。

2. C++默认的析构函数为什么不是虚函数?

是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。 而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。

3. 构造函数能不能是虚函数

不能虚构造

  1. 从存储空间角度:虚函数对应一个vtale,这个表的地址是存储在对象的内存空间的。如果将构造函数设置为虚函数,就需要到vtable 中调用,可是对象还没有实例化,没有内存空间分配,如何调用。(悖论)

  2. 从使用角度:虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。

  3. 从实现上看,vbtl 在构造函数调用后才建立,因而构造函数不可能成为虚函数。从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有太大的必要成为虚函数。

4. 说说什么是虚继承,解决什么问题,如何实现?

虚继承:虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。

解决存在的两个问题:其一,浪费存储空间;第二,存在二义性问题,通常可以将派生类对象的地址 赋值给基类对象,实现的具体方式是,将基类指针指向继承类(继承类有基类的拷贝)中的基类对象的 地址,但是多重继承可能存在一个基类的多份拷贝,这就出现了二义性。虚继承可以解决多种继承前面提到的两个问题。

#include <iostream>
using namespace std;


class A {
public:
	int _a;
};

class B :virtual public A
{
public:
	int _b;
};

class C :virtual public A
{
public:
	int _c;
};

class D :public B, public C
{
public:
	int _d;
};

//菱形继承和菱形虚继承的对象模型


int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	cout << sizeof(D) << endl;
	
	return 0;
}

分别从菱形继承和虚继承来分析:

在这里插入图片描述

菱形继承中A在B,C,D,中各有一份,虚继承中,A共享。 上面的虚继承表实际上是一个指针数组。B、C实际上是虚基表指针,指向虚基表。 虚基表:存放相对偏移量,用来找虚基类

5. 说说什么是虚函数

虚函数的作用:C++中的虚函数的作用主要是实现了多态的机制。

多态:关于多态,简而言之就是用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种 形态”,这是一种泛型技术。

如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。非虚函数总是在编译时根据调用该函数的对象,引用或指针的类型而确定。如果调用虚函数,则直到运行时才能确定调用哪个函数,运行的虚函数是引用所绑定或指针所指向的对象所属类型定义的版本。

虚函数必须是基类的非静态成员函数。虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。以实现统一的接口,不同定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。

class Person {
public:
	//虚函数
	virtual void GetName() {
		cout << "PersonName:xiaosi" << endl;
	};
};

class Student :public Person {
public:
	void GetName() {
		cout << "StudentName:xiaosi" << endl;
	};
};

int main() 
{
	//指针
	Person* person = new Student();
	//基类调用子类的函数
	person->GetName();//StudentName:xiaosi
	return 0;
}

6.说说虚函数的实现原理

虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

7.说说纯虚函数

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”, virtual void GetName() = 0

在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类, 但动物本身生成对象明显不合常理。为了解决上述问题,将函数定义为纯虚函数,则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

将函数定义为纯虚函数能够说明,该函数为后代类型提供了可以覆盖的接口,但是这个类中的函数绝不会调用。声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。必须在继承类中重新声明函数(不要后面的=0) 否则该派生类也不能实例化,而且它们在抽象类中往往没有定义。

定义纯虚函数的目的:使派生类仅仅只是继承函数的接口。

纯虚函数的意义:让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。

// 抽象类
class Person {
public:
	//纯虚函数

	virtual void GetName() = 0;
};

class Student :public Person {
public:
	Student() {
	};
	void GetName() 
	{
		cout << "StudentName:xiaosi" << endl;
	};
};

int main() 
{
	Student student;
	return 0;
}

8.说说纯虚函数能实例化吗?为什么?派生类要实现吗?为什么?

纯虚函数不可以实例化,但是可以用其派生类实例化

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

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

int main()
{
	Base* b = new Derived();
	b->func();
	return 0;
}

纯虚函数为什么不能实例化:

虚函数的原理采用 vtable。类中含有纯虚函数时,其vtable 不完全,有个空位。 即纯虚函数在类的vftable表中对应的表项被赋值为0。也就是指向一个不存在的函数。由于编译器绝对不允许有调用一个不存在的函数的可能,所以该类不能生成对象。在它的派生类中,除非重写此函数,否则也不能生成对象。”

所以纯虚函数不能实例化。

派生类要实现方法吗?

纯虚函数是在基类中声明的虚函数,它要求任何派生类都要定义自己的实现方法,以实现多态性。

为什么?

定义纯虚函数是为了实现一个接口,用来规范派生类的行为,也即规范继承这个类的程序员必须实现这个函数。派生类仅仅只是继承函数的接口。纯虚函数的意义在于,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但基类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。

9.说说C++中虚函数与纯虚函数的区别

  1. 虚函数和纯虚函数可以定义在同一个类中,含有纯虚函数的类被称为抽象类,而只含有虚函数的类不能被称为抽象类。

  2. 虚函数可以被直接使用,也可以被子类重载以后,以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类有声明而没有定义。

  3. 虚函数和纯虚函数都可以在子类中被重载,以多态的形式被调用。

  4. 虚函数和纯虚函数通常存在于抽象基类之中,被继承的子类重载,目的是提供一个统一的接口。

  5. 虚函数的定义形式: virtual{} ;纯虚函数的定义形式: virtual { } = 0 ;在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时要求前期绑定,然而虚函数却是动态绑定,而且被两者修饰的函数生命周期也不一样。

10.请问构造函数中能不能调用虚方法

不要在构造函数中调用虚方法,从语法上讲,调用完全没有问题,但是从效果上看,往往不能达到需要的目的。 派生类对象构造期间进入基类的构造函数时,对象类型变成了基类类型,而不是派生类类型。 同样,进入基类析构函数时,对象也是基类类型。 所以,虚函数始终仅仅调用基类的虚函数(如果是基类调用虚函数),不能达到多态的效果,所以放在构造函数中是没有意义的,而且往往不能达到本来想要的效果。

11.如何理解抽象类?

  1. 抽象类的定义如下: 纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现 方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”,有虚函数的类就叫做抽象类。

  2. 抽象类有如下几个特点:

    1)抽象类只能用作其他类的基类,不能建立抽象类对象。

    2)抽象类不能用作参数类型、函数返回类型或显式转换的类型。

    3)可以定义指向抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性。

12.除了虚函数,还有什么方式能实现多态?

多态是以封装和继承为基础的。在C++中多态分为静态多态(早绑定)和动态多态(晚绑定)两种,其中动态多态是通过虚函数实现,静态多态通过函数重载实现,代码如下:

class A
{
public:
	void do(int a);
	void do(int a, int b);
};

13.说说什么是虚基类,可否被实例化?

在被继承的类前面加上virtual关键字,这时被继承的类称为虚基类,代码如下:

class A
class B1 :public virtual A;
class B2 :public virtual A;
class D :public B1, public B2;

虚继承的类可以被实例化,举例如下:

class Animal {/* ... */ };

class Tiger : virtual public Animal { 
public:
	void getWeight()
	{
		cout << "getWeight()" << endl;
	}
};

class Lion : virtual public Animal { /* ... */ };

int main()
{
	Tiger lg;
	/*既然我们已经在Tiger和Lion类的定义中声明了"virtual"关键字,于是下面的代码编译OK*/
	lg.getWeight();
	return 0;
}

14. C++中那些函数不能被声明为虚函数?

常见的不能声明为虚函数的有:普通函数(非成员函数),静态成员函数,内联成员函数,构造函数,友元函数。

  1. 为什么C++不支持普通函数为虚函数?

    普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此 编译器会在编译时绑定函数。

  2. 为什么C++不支持构造函数为虚函数?

    这个原因很简单,主要是从语义上考虑,所以不支持。因为构造函数本来就是为了明确初始化对象 成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。 另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函 数来完成你想完成的动作。(这不就是典型的悖论) 构造函数用来创建一个新的对象,而虚函数的运行是建立在对象的基础上,在构造函数执行时,对象尚 未形成,所以不能将构造函数定义为虚函数

  3. 为什么C++不支持内联成员函数为虚函数?

    其实很简单,那内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在 继承后对象能够准确的执行自己的动作,这是不可能统一的。(再说了,inline函数在编译时被展 开,虚函数在运行时才能动态的绑定函数) 内联函数是在编译时期展开,而虚函数的特性是运行时才动态联编,所以两者矛盾,不能定义内联函数 为虚函数

  4. 为什么C++不支持静态成员函数为虚函数?

    这也很简单,静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,他也没 有要动态绑定的必要性。 静态成员函数属于一个类而非某一对象,没有this指针,它无法进行对象的判别

  5. 为什么C++不支持友元函数为虚函数?

    因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。

15. 虚函数表里存放的内容是什么时候写进去的?

  1. 虚函数表是一个存储虚函数地址的数组,以NULL结尾。虚表(vftable)在编译阶段生成,对象内存空间开辟以后,写入对象中的 vfptr,然后调用构造函数。即:虚表在构造函数之前写入虚函数?

  2. 除了在构造函数之前写入之外,我们还需要考虑到虚表的二次写入机制,通过此机制让每个对象的虚表指针都能准确的指向到自己类的虚表,为实现动多态提供支持。

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

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

相关文章

VESC操作入门——PPM输入控制和ADC输入控制

目录 一、PPM输入控制1.1、硬件准备1.2、PPM信号1.3、校准电机1.4、输入设置 二、ADC输入控制2.1、硬件准备2.2、更改固件2.3、电压信号2.4、校准电机2.5、输入设置 三、电动车转把控制3.1、转把说明3.2、转把测试 ODrive、VESC和SimpleFOC 教程链接汇总&#xff1a;请点击 一、…

SpringBoot作日志切面记录

目录 1.WebLogAspect 2.配置log4j2.yml 3.效果 话不多说&#xff0c;直接上代码&#xff1a; 1.WebLogAspect import java.util.Arrays;import javax.servlet.http.HttpServletRequest;import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.Str…

第Y2周:训练自己的数据集

我的环境&#xff1a; 训练自己的数据集 一、准备自己的数据集1. 编写split_train_val.py文件2.生成训练文件索引文件 二、创建训练yaml文件三、开始训练 一、准备自己的数据集 数据集来源&#xff1a;kaggle水果检测 目录结构如下&#xff1a; 1. 编写split_train_val.py…

ClickHouse基本使用总结

查看系统配置 查看系统表 select * from system.clusters; 验证zookeeper #验证zookeeper是否与当前数据库clickhouse进行了正确的配置 SELECT * FROM system.zookeeper WHERE path /clickhouse; 建表 创建本地表 MergeTree&#xff0c;这个引擎本身不具备同步副本的功能&…

Kali Linux 2023.2 发布(Hyper-V 和 PipeWire)

Kali Linux 2023.2 发布&#xff08;Hyper-V 和 PipeWire&#xff09; 请访问原文链接&#xff1a;https://sysin.org/blog/kali-linux-2023/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 更新说明 2023 年 5 月 30 日&…

国产上新!芯驰D9多核Cortex-A55核心板,国产车规级平台

随着信息技术的快速发展&#xff0c;市场对芯片的需求越来越大&#xff0c;中国芯片行业自20世纪80年代开始起步&#xff0c;经过近40年的努力&#xff0c;也进入了一个新的时代&#xff0c;芯片国产化乃未来发展的大势所趋。米尔电子作为行业领先的嵌入式模组厂商&#xff0c;…

【Difussion Model】理解和编程

目录 理论框架text-to-imgaedecodergeneration modelclip的原理 FID指标&#xff1a;评估图像生成的好坏数学原理 理论 框架 不断的进行去噪&#xff0c;并且在这个过程中&#xff0c;step也作为“去噪模型&#xff08;其实就是扩散模型&#xff09;”的输入&#xff1a; de…

iOS应用上架详细图文教程

App Store作为苹果官方的应用商店&#xff0c;审核严格周期长一直让用户头疼不已&#xff0c;很多app都“死”在了审核这一关&#xff0c;那我们就要放弃iOS用户了吗&#xff1f;当然不是&#xff01;本期我们从iOS app上架流程开始梳理&#xff0c;详细了解下iOS app上架的那些…

项目管理与任务管理:关键的差异和共同点

在商业世界中&#xff0c;了解项目和任务之间的区别对于有效的工作流程管理至关重要。项目和任务经常被混淆&#xff0c;但它们需要不同的技术和方法来有效管理。本文将比较和对比项目和任务&#xff0c;强调它们的主要区别和各自带来的独特挑战。 项目与任务管理的主要区别在…

三分钟了解Spring Boot 的启动流程

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是冰点&#xff0c;从业11年&#xff0c;目前在物流独角兽企业从事技术方面工作&#xff0c;&#x1f342;博主正在努力完成2023计划中&#xff1a;以梦为马&#xff0c;扬帆起航&#xff0c;2023追梦人&#x1f4dd;联系…

MySQL - 分库分表、MyCat配置、分片规则

文章目录 分库分表一、概述1.1 分库分表原因1.2 拆分策略1.2.1 垂直拆分1.2.2 水平拆分 1.3 实现技术 二、安装Mycat2.1 介绍2.2 安装 三、MyCat入门3.1 环境准备3.2 分片配置3.2.1 schema.xml3.2.2 server.xml 3.3 启动测试 四、MyCat配置4.1 schema.xml 配置文件4.1.1 schema…

你知道探针台的功能有哪些吗

探针台的主要用途是为半导体芯片的电参数测试提供一个测试平台&#xff0c;探针台可吸附多种规格芯片&#xff0c;并提供多个可调测试针以及探针座&#xff0c;配合测量仪器可完成集成电路的电压、电流、电阻以及电容电压特性曲线等参数检测。适用于对芯片进行科研分析&#xf…

你还在用 top?这有几个更牛逼的选择

简介 作为一个运维人员&#xff0c;很多时候需要知道服务器的实时情况&#xff0c;比如 Windows 系统的任务管理器&#xff0c;下面我描述的是基于 Linux 系统&#xff0c;你可能听说最流行的就是‘top’【基于终端的系统监视工具】。 top&#xff1a;实时程序可帮助显示所有正…

< axios封装篇 :一文看懂Axios + ElementUi 配置全局遮罩loading >

axios封装篇 &#xff1a;一文看懂axios配置全局遮罩loading &#x1f449; 实现原理&#x1f449; Axios封装> loading配置文件> axios封装文件 往期内容 &#x1f4a8; 今天这篇文章&#xff0c;主要是用于补充上次的 axios封装相关文章&#xff0c;用于补充全局配置接…

拿捏 二叉树前序遍历、中序遍历、后序遍历非递归实现!

二叉树前序遍历非递归实现 在不使用递归的方式遍历二叉树时&#xff0c;我们可以使用一个栈模拟递归的机制。二叉树的前序遍历顺序是&#xff1a;根 → 左子树 → 右子树&#xff0c;我们可以先将二叉树的左路结点入栈&#xff0c;在入栈的同时便对其进行访问&#xff0c;此时就…

安装Docker使用Docker安装部署MySQL和Redis

Docker安装 sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine sudo yum remove -y yum-utils sudo yum install -y yum-utils sudo yum-config-manager --add-re…

【企业化架构部署】Nginx优化与防盗链

文章目录 引言一、Nginx 服务优化1. 隐藏版本号1.1 网页查看 Nginx 版本1.2 命令查看 Nginx 版本1.3 修改配置文件方式1.4 修改源码文件&#xff0c;重新编译安装 2. 修改 Nginx 用户与组2.1 编译安装时指定2.2 修改配置文件方式 3. 配置网页缓存时间4. 日志分割5. 设置连接超时…

(3)NUC980 kenerl编译

解压 用到的配置文件位置&#xff1a; /NUC980-linux-4.4.y-master/arch/arm/configs/nuc980_defconfig 执行&#xff1a; 编译linux内核源码。了解其 配置文件在 arch/arm/configs/nuc980_defconfig (1) make nuc980_defconfig 载入配置文件 (2) make menuconfig --->Devi…

ImageNet Classification with Deep ConvolutionalNeural Networks

AlexNet网络实现&#xff1a;https://blog.csdn.net/weixin_43912621/article/details/127757396 ImageNet Classification with Deep ConvolutionalNeural Networks Abstract We trained a large, deep convolutional neural network to classify the 1.2 million high-res…

面试官问,Vue.js和React.js之间有什么区别

Vue.js和React.js的区别 Vue.js和React.js都是流行的前端框架&#xff0c;它们都采用了组件化的开发方式&#xff0c;可以将大型应用程序分解为多个小组件&#xff0c;从而更加方便地管理和维护应用程序。尽管Vue.js和React.js在许多方面都有相似之处&#xff0c;但它们之间仍…