设为首页 收藏本站
查看: 78|回复: 0

[经验分享] 【大厂C++面试突击手册】Day12:类成员存储机制/虚函数表...

[复制链接]
累计签到:50 天
连续签到:1 天
发表于 2025-3-19 14:23:57 | 显示全部楼层 |阅读模式
本帖最后由 jinchanchanwaji 于 2025-3-19 14:25 编辑

CSIG 腾讯云客户端
const常量和static变量在类里面的区别

  • 存储和访问:
    • const常量是类的一个成员,它的值在编译时就已确定,并且在类的每个对象中都有一份独立的拷贝。每个对象都不能修改它的const成员的值。
    • static变量属于类本身,而不是属于类的某个对象。它在程序的生命周期内只有一份存储,被所有对象共享。static变量需要在类外进行定义和初始化。

  • 初始化:
    • const常量必须在声明的时候或者通过构造函数的初始化列表进行初始化。
    • static变量可以在类外初始化,且只能初始化一次。

  • 作用和用途:
    • const常量通常用于定义类中的不可变数据,例如物理常数、配置值等。
    • static变量常用于所有对象间共享数据,或者计数类实例数量、实现单例模式等。

  • 使用场景举例:
    • 假设有一个Circle类,其中有一个const成员PI(圆周率),它表示一个常量值3.14159,每个Circle对象访问的PI都是一样的,但它属于每个对象的一部分。
    • 如果在Circle类中定义一个static int count;变量用以记录创建的Circle对象数量,那么所有的Circle对象都将共享这个count变量,而且它不属于任何一个Circle对象,而是属于Circle类。

  • 语法:
    • 示例const常量使用:在类定义内class MyClass { const int myConst = 5; };
    • 示例static变量定义和初始化:在类内声明static int myStaticVar;,在类外初始化int MyClass::myStaticVar = 0;

static修饰类函数访问成员的限制
当函数被声明为static时,它可以直接通过类来调用,而无需创建类的对象实例。这就意味着static成员函数只能访问类的static成员(变量和函数),它们不能访问类的非static成员。这是因为非static成员属于类的具体实例,而static成员函数在没有任何对象实例的情况下也能被调用。
  • 不能访问非static成员:
    static成员函数不能直接访问非static成员变量和非static成员函数,因为这些成员要求有一个具体的对象实例才能被访问。
  • 不能使用this指针:
    static成员函数不接受this指针,因为this指针指向类的某个具体实例,而static成员函数与具体实例无关。
  • 可以访问static成员:
    static成员函数可以访问类的其他static成员变量和static成员函数,因为这些成员不依赖于类的实例。
  • 可以被正常函数调用:
    一个类的static成员函数可以被该类的非static成员函数调用,也可以被其他类和非类(全局)函数调用,因为它们的访问不受限于对象实例。
  • 使用场景:
    static成员函数通常用于执行不依赖于对象状态的操作,例如工具函数,或者类级别的行为(例如,工厂模式中的工厂方法)。

内联和普通函数的区别
内联函数会在编译时将函数体嵌入到每个调用点,以减少函数调用的开销;而普通函数则通过常规调用机制执行,涉及跳转和返回操作。内联函数可减少调用成本,但可能增加代码体积;普通函数调用清晰但开销相对较大。内联是编译器的建议,具体内联与否取决于编译器决策。
计算结构体大小需要主要什么
  • 成员大小:每个成员的数据类型决定了其大小。
  • 对齐/Padding:为了保证内存访问的效率,编译器会根据硬件平台的要求自动添加填充字节(padding)以对齐成员。

  • 对齐规则:
    • 结构体的总大小通常是最宽基本类型成员的整数倍。
    • 每个成员的偏移量(从结构体开始的位置)是该成员类型大小的整数倍。
  • 继承:如果结构体是从另一个结构体继承而来,基类的大小也需要考虑在内。
  • 位字段:位字段成员可能会使大小计算更加复杂,因为它们可以使得成员在不足一个字节的尺寸内进行打包。
  • 虚函数:如果结构体中有虚函数,则需要考虑虚函数表(vtable)的指针大小。
  • 编译器选项:编译器的某些选项(如pack)可以改变默认的对齐方式。
  • 平台和编译器:不同的编译器或目标平台可能有不同的对齐需求。

一个类有一个int和一个char有多大
假设不考虑虚函数或虚继承,该类的大小通常由以下情况确定:
  • int 类型通常占用 4 个字节。
  • char 类型占用 1 个字节。
由于内存对齐,编译器可能会在intchar之间或者char后面添加填充字节,以保证结构体的总大小是最大基本类型成员大小的整数倍。在大多数平台上,这个最大基本类型成员大小通常是4个字节。
因此,即使char占用1个字节,编译器可能会添加额外的3个字节作为填充,确保int在内存中对齐,使得类的总大小为 8 个字节。
一个指针多大?不同系统如32位,64位上的区别
  • 在32位系统上,一个指针通常是4个字节(32位)。
  • 而在64位系统上,指针通常是8个字节(64位)。
这是因为指针需要能够存储内存地址,并且在32位系统上,内存地址是32位的;相应地,在64位系统上,内存地址是64位的。

介绍一下读写锁
读写锁(也称作共享-独占锁或者共享-修改锁)是一种同步机制,旨在解决多线程程序中的读者-写者问题。读写锁允许并发的读取操作,但写入操作是互斥的。这种锁是对读者和写者操作的优化,因为读取操作通常不会修改数据,所以多个线程可以安全地同时读取。
读写锁提供了两种锁的机制:
  • 共享锁(或读锁):当获得共享锁时,其他线程可以获取共享锁进行读操作,但任何试图获得写锁(独占锁)的线程都会被阻塞,直到所有的共享锁都被释放。
  • 独占锁(或写锁):当获得独占锁时,其他线程不能读取也不能写入。所有试图获取共享锁或独占锁的线程都会被阻塞,直到写锁被释放。
在C++中,可以使用标准库中的std::shared_mutex自C++17起,之前版本可用boost库中的实现。std::shared_mutex允许多个线程持有读锁,但在任意时刻最多只允许一个线程持有写锁。

extern C 介绍一下
extern "C"是一种特殊的链接指令,用于告诉C++编译器某个给定的代码块应当以C语言的方式来进行编译和链接。这是因为C++和C语言在函数名的编码(也称为名称修饰或名称矫正)方面有所不同。C++支持函数重载,因此编译器会将函数名编码以包含关于函数参数类型的信息,这样编译器就可以区分有相同名称但参数类型不同的函数。而C语言不支持函数重载,因此不对函数名进行这样的编码。
使用extern "C"的主要目的是允许C++代码调用C语言代码库,或者允许C语言代码调用C++代码中以C语言方式编写的部分。它确保了在链接时函数的名称按照C语言的方式解析,从而可以正确地链接到C语言代码。

互斥量和条件变量介绍一下
互斥量和条件变量是同步原语,它们在多线程编程中用来控制对共享资源的访问和线程之间的协调。
互斥量:
一个互斥量是用来保护共享资源的,它确保在任意时间点上,只有一个线程可以访问该资源。当一个线程锁定了一个互斥量,其他任何试图锁定该互斥量的线程都将被阻塞,直到互斥量被解锁。C++标准库中提供了std::mutex类来处理互斥量。
条件变量:
条件变量用于线程之间的同步,它允许线程挂起执行,并等待某个条件成为真。条件变量通常与互斥量一起使用,以保证对共享资源的安全访问。当条件尚未达到时,线程会释放互斥量并进入休眠状态;当其他线程改变了条件并发出通知时,等待条件的线程将被唤醒,重新获取互斥量并检查条件。C++标准库中提供了std::condition_variable类。

动态库和静态库的区别
动态库在程序运行时动态地加载和链接,不会增加最终可执行文件的大小,并且允许不同程序共享同一份库代码,便于更新。
静态库在编译时链接到程序中,它们成为最终可执行文件的一部分,使得每个程序都有自己的库代码副本,这会增加可执行文件的大小,且更新库需要重新编译程序。

知道什么是动态加载吗
动态加载是指在程序运行时(而非启动时)按需加载和链接库(通常是动态链接库,如DLLs或SO文件)中的代码和资源的过程。这种机制允许程序在执行过程中根据需要加载额外的功能或数据,而不是在程序启动时就加载所有可能用到的资源。
在C++中,动态加载通常通过以下方式实现:
  • 使用操作系统提供的API:如Windows上的LoadLibraryGetProcAddress函数,或Linux上的dlopendlsym函数。这些函数允许你在运行时加载动态库,并获取库中函数或变量的地址。
  • 使用高级封装:一些跨平台的库(如Boost.DLL)提供了对动态加载功能的封装,使得开发人员可以使用更简洁、更易于理解的接口来进行动态加载。
动态加载的优势:
  • 减小初始启动时间:因为不是所有的库都在开始时被加载,所以可以减少程序启动的时间。
  • 节省内存资源:只加载程序需要的资源,不使用的功能不占用内存。
  • 灵活的模块管理:方便地添加、更新或删除功能模块,而不需要重新编译整个程序。
  • 插件架构支持:动态加载使得实现插件或扩展机制成为可能,用户可以根据需要向程序添加功能。

TCP 分包,粘包介绍一下
分包
  • 定义:分包是指当一个大的数据包传输时,由于数据包的大小超过了网络中的最大传输单元(MTU),需要被分割成更小的数据帧才能通过网络传输的现象。
  • 处理:发送方将大的数据包按照MTU进行分割发送,接收方负责重新组装这些数据帧以重构原始数据包。
粘包
  • 定义:粘包是指发送方连续发送了多个数据包时,由于TCP是面向流的协议,接收方可能会一次性接收到这几个数据包合并后的数据流。结果是,多个数据包就像被“粘”在一起一样,接收方无法区分它们原来的界限。
  • 产生原因:TCP协议本身于传输可靠性而设计,不保证数据包的界限。另外,TCP的Nagle算法、接收方的接收缓冲区(Receive Buffer)和路径上各网络设备的处理策略都可能导致粘包现象。

发10个100字节的包,接收方是怎么收的
  • 连续接收:如果网络通畅,接收方有可能依次连续接收到10个100字节的数据,每次read调用读取到一个或多个完整的数据包。
  • 合并接收(粘包):TCP协议以字节流的方式传输数据,因此接收方可能一次性接收到一个包含多个发送包内容的大数据块,例如,第一次接收600字节,下一次接收400字节,这实际上就是粘包现象。
  • 分散接收:在网络状况不佳或由于接收方接收窗口的限制,可能会导致接收方只收到一部分数据,例如,第一次read只读取了50字节,需要多次读取才能获取完整的100字节数据包。

顺便吆喝一句,技术大厂,待遇之类的给的还可以,前、后端/测试,多地有空位,感兴趣的可以试试~~

柠檬微趣C++客户端
虚函数表存在什么内存区域上,如果一个子类没有重写虚函数,会和基类公用一个虚函数表吗
在C++中,虚函数表是实现运行时多态性的一种机制,每个包含虚函数的类都会有一个对应的虚函数表。该表通常存储在程序的只读数据段(.rodata section),它是编译时期生成的,且在程序运行时不会被修改。
虚函数表是一个存储函数指针数组的表,当调用一个类的虚函数时,程序会通过这个表来动态确定应该执行哪个函数的代码。每个类的对象在内存中通常都有一个虚函数表指针,这个指针指向该类对应的虚函数表。
对于继承体系中的基类和子类,情况如下:
  • 如果子类没有重写基类的虚函数,子类的虚函数表将包含对应基类虚函数地址的指针。在这种情况下,可以认为子类对象所使用的虚函数表条目在逻辑上是继承自基类的,子类对象通过自己的虚函数表调用未被重写的虚函数时,实际上执行的是基类的函数实现。
  • 如果子类重写了基类的虚函数,子类对象的虚函数表中相应的函数指针将被更新为指向子类中新实现的虚函数的地址。

虚指针为什么开头初始化
确保对象的多态行为能够正确工作。
下面是虚指针初始化的原因和重要性:
  • 对象类型识别:虚指针使得运行时可以根据对象实际类型调用正确的虚函数。如果没有虚指针,程序就无法在运行时解析出应该调用哪个类的哪个虚函数。
  • 支持多态:初始化虚指针是多态性能够正常运作的基础。多态允许基类型指针或引用调用实际子类型的方法,而这一切都依赖于虚指针。
  • 构造器工作:在对象生命周期中,构造器必须设置虚指针,以确保任何时候都能调用到正确版本的虚函数。这一过程从基类构造器开始,每个派生类的构造器负责更新虚指针,以指向各自的虚函数表。
  • 析构器逻辑:在析构时,虚指针也起到了关键作用。当析构派生类对象时,虚指针确保了类的析构器按正确顺序调用,以避免内存泄漏或其他资源未正确释放的问题。

引用能不能实现多态
C++的引用可以实现多态。要通过引用实现多态,需要定义基类的虚函数,并在派生类中重写这些虚函数。当通过基类的引用来调用一个虚函数时,会根据引用所绑定的对象的实际类型来决定调用哪个类的函数版本,实现多态行为。

shared_ptr的引用计数实现原理
shared_ptr通过内部的控制块来实现引用计数。控制块包含一个计数器,当我们创建一个shared_ptr时,这个计数器被初始化为1。每当另一个shared_ptr复制或赋值指向同一个对象时,该计数器增加。当shared_ptr被销毁或者重新赋值,计数器减少。如果计数器降到0,意味着没有shared_ptr再指向对象,对象会被自动销毁,并释放相关资源。这个机制确保了共享资源的生命周期被正确管理。

如果插入多个数据,用哈希表还是红黑树好
高效的随机访问和不关心数据顺序选哈希表,数据需要保持有序或者需要高效地进行范围查询和有序遍历选红黑树。
哈希表的优点是平均情况下具有O(1)的时间复杂度进行插入、查找和删除操作,适用于强调快速访问元素的场景。不过,哈希表的性能很大程度上取决于哈希函数的质量,不良的哈希函数会导致冲突和性能下降。此外,哈希表不支持高效的顺序操作和范围查询。
红黑树是一种自平衡的二叉搜索树,提供了最坏情况下O(log n)的时间复杂度进行插入、查找和删除操作。它优势在于可以保持元素排序,支持高效的顺序访问和范围查询。

红黑树和avl的区别,插入多个数据,选择avl还是红黑树?
主要区别在于平衡条件和平衡后的树的高度。
  • 平衡条件:
    • AVL树要求任何节点的两个子树的高度差的绝对值最多为1,这使得AVL树比红黑树更加平衡。
    • 红黑树通过确保从根到叶子的所有路径上黑色节点的数量相同,并且不存在连续的红色节点来进行平衡。
  • 高度和操作复杂度:
    • 由于更严格的平衡条件,AVL树的高度比红黑树更低,因此在查找操作中AVL可能表现更好。
    • 红黑树的插入和删除操作引起的重新平衡操作比AVL树少,因此在频繁进行插入和删除的场景下红黑树可能有更好的性能。
选择:
  • 如果插入多个数据,并且插入操作比查找操作更频繁,选择红黑树可能更合适,因为它的插入和删除操作引起的平衡调整较少。
  • 如果系统更注重查找效率,并且对插入删除操作的效率要求不是很高,可以选择AVL树,因为它更加平衡,查找效率略高。

哈希表什么时候扩容
哈希表通常在其负载因子达到某个阈值时进行扩容。负载因子是当前存储的元素数量与哈希表容量的比值。当插入操作使得当前负载因子超过预设阈值时,哈希表会进行扩容,以保持操作的效率。
什么时候调用移动构造函数
移动构造函数被调用在以下情况:
  • 当一个对象以右值引用的形式被初始化时。
  • 在标准库容器中插入或者移除元素,导致对象被移动。
  • 使用std::move显式地将一个对象转换为右值。
  • 当函数返回一个局部对象时,且编译器选择了返回值优化(RVO)但没有发生的情况。



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-1005700-1-1.html 上篇帖子: 【大厂C++面试突击手册】Day11:动态规划/SQL优化/二叉树层... 下篇帖子: 最新大厂C++面试真题合集,大厂面试百日冲刺 day8
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表