自学内容网 自学内容网

C++面试速通宝典——23

420. 一个类有一个int和一个char有多大?

假设不考虑虚函数或虚继承,该类的大小通常由以下情况确定:

  1. int类型通常占用4个字节
  2. char类型占用1个字节

由于内存对齐,编译器可能在int和char之间或者char后面添加填充字节,以保证结构体的总大小是最大基本类型成员大小的整数倍。在大多数平台上,这个最大基本类型成员大小通常是4个字节。

因此,即使char占用1个字节,编译器可能会添加额外的3个字节作为填充,确保int在内存中对齐,使得类的总大小为8个字节。

421. 一个指针多大?不同系统如32位,64位上的区别

  1. 在32位系统上,一个指针通常是4个字节(32位)
  2. 在64位系统上,指针通常是8个字节(64位)

这是因为指针需要能够存储内存地址,并且在32位系统上,内存地址是32位的;相应的在64位系统上内存地址是64位的

422. 介绍一下读写锁

‌‌‌‌  读写锁(也称共享——独占锁或者共享——修改锁)是一种同步机制,旨在解决多线程程序中的读者-写者问题。读写锁允许并发的读取操作,但写入操作是互斥的。这种锁是对读者和写者操作的优化,因为读取操作通常不会修改数据,所以多个线程可以安全的同时读取。

读写锁提供了两种锁的机制:

  1. 共享锁(或读锁):当获得共享锁时,其他线程可以获取共享锁进行读操作,但任何试图获得写锁(独占锁)的线程都会被阻塞,直到所有的共享锁都被释放。
  2. 独占锁(或写锁):当获得独占锁时,其他线程不能读取也不能写入。所有试图获取共享锁或独占锁的线程都会被阻塞,直到写锁释放。

‌‌‌‌  在c++中,可以使用标准库中的std::shared_mutex。自C++17起,之前版本可用boost库中的实现。std::shared_mutex允许多个线程持有读锁,但在任意时刻最多只允许一个线程持有写锁。

423. extern C介绍一下

‌‌‌‌  extern C是一种特殊的链接指令,用于告诉C++编译器某个给定的代码块应当以C语言的方式来进行编译和链接。这是因为C++和C语言在函数名的编码(也称为名称修饰或名称矫正)方面有所不同。C++支持函数重载,因此编译器会将函数名编码以包含关于函数参数类型的信息,这样编译器就可以区分有相同名称但参数类型不同的函数。而C语言不支持函数重载,因此不对函数名进行这样的编码。

‌‌‌‌  使用extern C的主要目的是允许C++代码调用C语言代码库,或者允许C语言代码调用C++代码中以C语言方式编写的部分。他确保了在链接时函数的名称按照C语言的方式解析,从而可以正确地链接到C语言代码。

433. 互斥量和条件变量介绍一下

‌‌‌‌  互斥量和条件变量是同步原语,他们在多线程编程中用来控制对共享资源的访问和线程之间的协调

互斥量
‌‌‌‌  一个互斥量是用来保护共享资源的,他确保在任意时间点上,只有一个线程可以访问该资源。当一个线程锁定了一个互斥量,其他任何试图锁定该互斥量的线程都将被阻塞,直到互斥量被解锁。C++标准库中提供了std:mutex类来处理互斥量。

条件变量
‌‌‌‌  条件变量用于线程之间的同步,它允许线程挂起执行,并等待某个条件成为真。条件变量通常与互斥量一起使用,以保证对共享资源的安全访问。但条件尚未达到时,线程会释放互斥量并进入休眠状态;当其他线程改变了条件并发出通知时,等待条件的线程将被唤醒,重新获取互斥量并检查条件。C++标准库中提供了std::condition_variable类。

434. 动态库和静态库的区别

‌‌‌‌  动态库在程序运行时动态地加载和链接,不会增加最终可执行文件的大小,并且允许不同程序共享同一份库代码,便于更新。

‌‌‌‌  静态库在编译时链接到程序中,他们成为最终可执行文件的一部分,使得每个程序都有自己的库代码副本,这会增加可执行文件的大小,且更新库需要重新编译程序。

435. 知道什么是动态加载么?

‌‌‌‌  动态加载是指在程序运行时(而非启动时)按需加载和链接库(通常是动态链接库,如DLLs或SO文件)中的代码和资源的过程。这种机制允许程序在执行过程中根据需要加载额外的功能或数据,而不是在程序启动时就加载所有可能用到的资源。

在C++中,动态加载通常通过以下方式实现:

  1. 使用操作系统提供的API:如WINDOWS上的LoadLibrary和GetProcAddress函数,或Linux上的dlopen和dlsym函数。这些函数允许你在运行时加载动态库,并获取库中函数或变量的地址。
  2. 使用高级封装:一些跨平台库(如Boost.DLL)提供了对动态加载功能的封装,使得开发人员可以使用更简洁、更易于理解的接口拉进行动态加载。

436. 动态加载的优势:

  1. 减少初始启动时间:因为不是所有的库都在开始时被加载,所以可以减少程序启动的时间。
  2. 节省内存资源:只加载程序需要的资源,不使用的功能不占用内存。
  3. 灵活的模块管理:方便地添加、更新或删除功能模块,而不需要重新编译整个程序。
  4. 插件架构支持:动态加载使得实现插件或扩展机制成为可能,用户可以根据需要向程序添加功能。

437. TCP分包、粘包介绍一下

分包

  1. 定义:分包是指当一个大的数据包传输时,由于数据包的大小超过了网络中的最大传输单元(MTU),需要被分割成更小的数据帧才能通过网络传输的现象。
  2. 处理:发送方将大的数据包按照MTU进行分割发送,接受方负责重新组装这些数据帧以重构原始数据包。

粘包

  1. 定义:粘包是指发送方连续发送了多个数据包时,由于TCP是面向流的协议,接收方可能会一次性接收到这几个数据包合并后的数据流。结果是,多个数据包就像被“粘”在一起一样,接收方无法区分他们原来的界限。
  2. 产生原因:TCP协议本身于传输可靠性而设计,不保证数据包的界限。另外,TCP的Nagle算法、接收方的接收缓冲区和路径上各网络设备的处理策略都可能导致粘包现象。

438. 发10个100字节的包,接收方是怎么收的

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

439. 虚函数表存在什么内存区域上?如果一个子类没有重写虚函数,会和基类公用一个虚函数表么?

‌‌‌‌  在C++中,虚函数表是实现运行时多态性的一种机制,每个包含虚函数的类都会有一个对应的虚函数表。该表通常存储在程序的只读数据段,他是编译时期生成的,且在程序运行时不会被修改。

‌‌‌‌  虚函数表是一个存储函数指针数组的表,当调用一个类的虚函数时,程序会通过这个表来动态确定应该执行那个函数的代码。每个类的对象在内存中通常都有一个虚函数表指针,这个指针指向该类对应的虚函数表。

对于继承体系中的基类和子类,情况如下:

  1. 如果子类没有重写基类的虚函数,子类的虚函数将包含对应基类虚函数地址的指针。在这种情况下,可以认为子类对象所使用的虚函数表条目在逻辑上是继承自基类的,子类对象通过自己的虚函数表调用未被重写的虚函数时,实际上执行的是基类的函数实现。
  2. 如果子类重写了基类的虚函数,子类对象的虚函数表中相应的函数指针将被更新为指向子类中新实现的虚函数的地址

440. 虚指针为什么开头初始化

确保对象的多态行为能够正确工作.

下面是虚指针初始化的原因和重要性:

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

441. 引用能不能实现多态

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

442. shared_ptr的引用计数原理

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

443. 如果插入多个数据,用哈希表好还是红黑树好

‌‌‌‌  高效的随机访问和不关心数据顺序选哈希表数据需要保持有序或者需要高效的进行范围查询和有序遍历选红黑树

‌‌‌‌  哈希表的优点是平均情况下有O(1)的时间复杂度进行插入、查找和删除操作,适用于强调快速访问元素的场景。不过,哈希表的性能和大程度上取决于哈希函数的质量,不良的哈希函数会导致冲突和性能下降。此外,哈希表不支持高效的顺序操作和范围查询。

‌‌‌‌  红黑树是一种自平衡的二叉搜索树,提供了最坏情况下0(logn)的时间复杂度进行插入、查找和删除操作。他优势在于可以保持元素排序,支持高效的顺序访问和范围查询。

444. 红黑树和avl的区别,插入多个数据,选择avl还是红黑树

主要区别在于平衡条件和平衡后的树的高度。

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

445. 哈希表什么时候扩容

‌‌‌‌  哈希表通常在其负载因子达到某个阈值时进行扩容。负载因子是当前存储的元素数量和哈希表容量的比值。当插入操作使得当前负载因子超过预设阈值时,哈希表会进行扩容,以保持操作的效率。

446. 什么时候调用移动构造函数

移动构造函数被调用在以下情况:

  1. 当一个对象以右值引用的形式被初始化时
  2. 在标注库容器中插入或移除元素,导致对象被移动。
  3. 使用std::move显示的将一个对象转换为右值。
  4. 当函数返回一个局部对象时,且编译器选择了返回值优化(RVO)但没有发生的情况。

447. 介绍Reactor

‌‌‌‌  Reactor是一种设计模式,用于处理并发的I/O事件,通常用于网络服务中。该模式将I/O事件的等待和事件分派集中到一个事件循环中,被称为reactor。当I/O事件发生时,reactor负责通知相应的事件处理器进行处理。

Reactor模式通常包括以下几个组件:

  1. 事件处理器(Event Handlers):具体处理I/O操作的对象,每种类型的事件(如读事件、写事件)都有对应的事件处理器。
  2. 同步事件分离器(Synchronous Event Demultiplexer):负责等待事件的发生(例如通过select,poll或epoll系统调用)并通知reactor。
  3. 事件循环(Event Loop):事件循环负责启动事件分离器等待事件,然后分派事件到相应的事件处理器。
  4. 资源句柄(Resource Handles):代表一个开放的I/O流(如文件描述符、套接字)。

448. select,poll,epoll?

‌‌‌‌  select,poll和epoll是Linux下常用的I/O多路复用机制,他们允许程序监视多个文件描述符等待一个或多个文件描述符成为“就绪”状态(例如,数据可读如、写入),从而实现非阻塞I/O操作。这三者在功能上相似,但各自有不同的实现机制和性能表现。

  1. select:是最早的I/O多路复用系统调用之一。它允许程序监视一个文件描述符集合,知道其中一个或多个文件描述符就绪(对于读操作、写操作或异常)。select的主要限制之一是他支持的文件描述符有数量有限制,通常受到FD_SETSIZE的限制,这在高性能、大规模并发的应用程序中可能成为瓶颈。
  2. poll:与select类似,但他不受FD_SETSIZE的限制,理论上可以监视任意数量的文件描述符。poll使用pollfd结构数组来存储待监视的文件描述符及其期待的事件,从而解决了select有所改进,但在性能上,当监视的文件描述符数目非常大时,他仍然面临效率挑战。
  3. epoll:是较新的IO多路复用机制,专为处理大量并发连接而设计。它提供了比select和poll更好的扩展性,能够高效的处理成千上万的并发连接。epoll使用一组函数(epoll_create、epoll_ctl和epoll_wait)来管理事件。其中,epoll_ctl用于添加、修改或删除监视的文件描述符;epoll_wait等待事件的发生。epoll的一个关键优势是它采用事件驱动机制,只处理就绪的文件描述符,从而减少了不必要的检查操作。

449. TCP的沾包

‌‌‌‌  TCP的沾包问题发生在TCP传输层,因为TCP是面向流的协议,他没有内在的消息界限。沾包问题是指多个TCP发送的数据包,在接收时被合并成一个或多个包。该问题通常发生在如下场景:

  1. 应用层数据包较小:当传输的数据块尺寸小于TCP包的标准尺寸时,TCP为了效率,可能把短的消息合并到一个段里。
  2. Nagle算法:为减少网络中小尺寸包的数量,这个算法可能会导致多个小包合并成一个大包发送。
  3. 接收方延迟确认:TCP接收方可能延迟发送确认消息,这样发送方的几个小包可能合并成一个较大的包。

450. 如何解决沾包问题?

  1. 固定长度:分配一个固定大小的缓冲区给每个消息。如果消息不足预定大小,那么可以在消息后面填充空字节。这种方法简单但可能造成带宽的浪费。
  2. 分隔符或结束字符:用特殊字符或字符串来分隔消息,如使用换行符 \n 或者特殊字符序列< EOF > 。 接收方读取数据流直至遇到分隔符,然后处理消息。这种方法适用于文本协议,但要确保这些特殊字符不会在消息数据中出现。
  3. 长度字段:在消息的头部加入长度信息。这个长度信息字段表示随后跟随的消息内容的长度。这种方法是非常灵活且常见的,它允许消息体含有任何数据,包括二进制数据。
  4. 自定义协议:为你的应用设计一个协议规格,这个协议规格定义了消息的格式、大小以及如何发送接收消息。例如,可以结合使用长度字段和某种形式的头部信息,。
  5. 应用层缓冲:在接收端,应用层可以实现自己的缓冲机制。这个缓冲机制会存储所有接收到的数据并根据上述任一策略进行消息边界识别。

451. 线程有什么东西是不共享的?

  1. 线程栈:每个线程都有自己的执行栈,这个栈包含了可以跟踪到任何时刻线程执行状态的所有栈帧。
  2. 程序计数器:每个线程都有一个程序计数器,以便线程可以跟踪下一条要被执行的指令位置。
  3. 线程局部存储(TLS):用于存储每个线程的私有数据。虽然数据可以被同一程序中的其他线程访问,但每个线程都有其专用的存储副本。
  4. 寄存器集:CPU使用的寄存器也是每个线程有自己的拷贝。
  5. 信号掩码:每个线程可以有自己的一套信号掩码,用于屏蔽或允许处理某些信号。
  6. 优先级:线程也可以拥有自己的执行优先级(尽管这是由线程库和操作系统调度策略控制的)。
  7. 线程特定的数据(TSD):线程可以创建并维护唯一数据,其他线程无法访问。

原文地址:https://blog.csdn.net/qq_43504141/article/details/142848634

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!