备战秋招_C++软开_面试真题

2025 秋招 C++软开面试真题整理
以下内容均为自己搜集整理,仅供参考
持续更新中~~~

文章目录


1. TCP粘包问题

TCP粘包问题是在TCP通信中常见的一个问题,它指的是发送方多次写入数据,而接收方从缓冲区中读取数据时,导致多个数据包“粘”在一起,难以正确解析和处理。

以下是一些解决TCP粘包问题的方法:

  • 使用定长包‌
    原理:发送端将每个消息的长度固定,接收端根据固定长度来分割接收到的数据,从而确保每个接收到的数据包含完整的消息。
    实现:需要封装两个函数,readn和writen,它们的作用分别是读取和写入固定长度的数据。
    缺点:无论每次发送的有效数据是多大,都得按照定长的数据长度进行发送,这可能会增加网络的负担。

  • ‌在消息头部添加长度字段‌
    原理:发送端在每个消息前加入一个固定长度的消息头部,这个头部包含消息的长度信息。接收端先读取消息头部,获取数据的长度,然后再根据这个长度来读取对应长度的数据。
    优点:这种方法可以高效地、精准地解析数据。
    缺点:需要处理字节序和长度校验。

  • 使用特殊的消息分隔符‌
    原理:在发送的消息中加入特定的消息分隔符(如换行符或者自定义的标记),接收端根据这个分隔符来分割接收到的数据,从而识别出完整的消息。
    优点:这种方法比较灵活,可以兼容变长数据。
    缺点:需要处理分隔符的转义,可能会降低效率。如果分隔符在数据中出现,还可能导致解析错误。

  • 使用带消息边界标记的协议‌
    可以使用标准的应用层协议(比如HTTP、HTTPS)来封装要传输的不定长的数据包。这些协议通常已经处理了粘包和拆包的问题。HTTP协议通过设置回车符、换行符作为HTTP header的边界,通过Content-Length字段作为HTTP body的边界,这两种方式都是为了解决“粘包”问题

  • ‌关闭Nagle算法‌
    Nagle算法是为了减少网络上小数据包的数量而设计的。它会将多个小数据包合并成一个大的数据包再发送。在某些情况下,关闭Nagle算法可以减少粘包问题的发生,但可能会增加网络的开销。

  • 使用缓冲区机制‌
    在接收端设置一个缓冲区来累积数据,等待足够的数据后再进行处理。这种方法可以避免数据不完整造成的解析错误,但也需要合理设置缓冲区的大小,以避免过多的内存占用。

在实际应用中,可以根据具体的需求和网络环境来选择合适的方法。同时,也可以组合使用多种方法来更有效地解决粘包问题。

2. 计算机的基本结构

运算器 控制器 存储器 输入设备 输出设备

3. C++开发面经

3.1 线程有没有独立内存?

线程没有自己完全独立的内存空间,但拥有独立的栈空间。具体区别如下:

  1. 共享的内存区域(线程间共享)
    • 堆内存:所有线程共享同一个堆,因此动态分配的对象(如 new 出来的对象)可以被所有线程访问。
    • 全局变量:包括静态变量、全局变量等,所有线程共享。
    • 代码段:程序的二进制指令也是共享的。
  2. 线程私有的内存区域
    • 栈内存:每个线程有独立的调用栈,用于存储局部变量、函数参数、返回地址等。栈的大小通常是固定的(如 Linux 默认 8MB),但可以通过系统调用调整(如 pthread_attr_setstacksize)。
    • 寄存器上下文:线程切换时,CPU 寄存器的值(如程序计数器 PC、栈指针 SP)会被保存到线程的私有控制块(如 TCB, Thread Control Block)。
    • 线程局部存储(TLS):通过 __thread 或 pthread_key_create 等机制,可以定义线程私有的全局变量。
  3. 为什么需要共享内存?
    线程的设计初衷是为了高效并发,共享内存可以避免进程间通信(IPC)的开销(如管道、消息队列)。但这也引入了同步问题(如竞态条件、死锁),需要借助锁(mutex)、原子操作等机制。
  4. 对比进程内存模型
    • 进程有完全独立的虚拟地址空间(代码段、堆、栈、全局变量等均独立),需通过 IPC 通信。
    • 线程是轻量级的,共享进程的地址空间,仅栈和少量上下文私有。
    总结
    • 线程内存模型:共享堆+全局变量,私有栈+TLS。
    • 核心优势:高效共享数据,但需同步机制(如锁)。
    • 调试提示:若发现变量“诡异变化”,可能是未同步的共享内存导致

3.2 resize函数是做什么的?

在 C++ 标准库里,resize 并不是某个全局函数,而是容器(std::vector、std::string、std::deque 等)提供的一个成员函数。它的核心作用只有一句话:改变容器当前保存的元素个数(size)。
与reserve的区别:
在这里插入图片描述
在这里插入图片描述
一句话总结:
resize 就是“把容器变长或剪短”,顺带帮你填值或析构元素。
补充:capacity() 是 C++ 顺序容器(如 std::vector、std::string、std::deque 等)的一个成员函数,返回:当前该容器已经分配(reserve)出来的、尚未使用的内存空间最多能容纳多少个元素,表示容器在不重新分配内存的情况下,最多还能再push_back多少个元素。

3.3 vector和list的结构是什么?

一句话先答:
vector 是「一段连续内存的动态数组」,list 是「一堆节点用双向指针串起来的双向链表」。
1、内存布局
Vector是连续的,像C数组,一块整的内存块;list是非连续的。每个节点单独new/malloc
2、节点结构
Vector没有额外节点,元素本身顺序排布在首元素到尾元素之间;
List每个元素包一层双链表节点
3、迭代器实现
vector::iterator:
本质就是 裸指针 T*(或封装了一层类的指针,仍支持 +n、随机访问)。
list::iterator:
是 自定义类,内部保存一个 __list_node*,重载了 ++、–、-> 等运算符,只能双向移动,不能随机跳跃。
4、操作复杂度对比

操作vectorlist
随机访问 v[i]O(1)O(n)
任意位置插入/删除O(n)(需搬移元素)O(1)(已知迭代器)

5.手撕:去除链表重复节点
①已排序去重②未排序去重③配合哈希表

场景最简套路
已排序一次扫描,相邻值相等就删
无序保留第一次哈希表+一次扫描
无序全部删哑结点+双重扫描

4. C++开发面经

4.1 指针和引用的区别

①引用是变量的别名;指针是存地址
②引用必须初始化,一旦初始化就一直绑定一个对象,不能改变绑定;指针可以无需初始化,可以改变
③不存在指向空值的引用,但存在指向空值的指针
④引用用&声明,指针用*声明
⑤引用通常用于函数参数传递,操作符重载以及重建别名;指针用于动态内存分配、数组操作以及函数参数传递。

4.2 Vector扩容,什么情况下会出现迭代器失效,怎么防止

所有对于vector的操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。

vector在堆中分配了一段连续的内存空间来存放元素,当插入元素导致容量不足时会:
1.重新分配一块更大的内存(通常按 1.5 或 2 倍增长)。
2. 把旧元素 拷贝/移动 到新内存。
3. 释放旧内存。
此时,所有指向旧内存的迭代器、指针、引用都会变成悬空(dangling),访问它们就是 UB(未定义行为)。
哪些操作会触发扩容 & 迭代器失效?
在这里插入图片描述
防止/缓解迭代器失效的 4 种办法
4. 提前 reserve(最常用)
5. 用索引代替迭代器
6. 每次操作后重新获取迭代器(逻辑简单时)
7. 使用 std::deque / std::list(数据结构本身无扩容问题),需频繁插入且迭代器长期持有,考虑换容器。

小结口诀:“vector 扩容,旧地全抛;提前 reserve,索引来保。”

4.3 函数参数传递有哪几种,用在什么时候

①值传递T:将实参完整拷贝一份给形参,适用于小对象
②按引用传递:
1.普通引用T& 零拷贝,可修改原对象,适用于需要修改原对象
2.const引用const T& 只读别名,禁止修改,避免拷贝,适用于只读大对象
③按指针传递T*:将地址传进去,可空,可重指,需手动解引用,适用于可能为空
④更多:右值引用T&&:接收可移动对象,适用于实现移动语义/完美转发;Std::unique_ptr :专属所有权转移,适用于移动资源;Std:shared_ptr:共享所有权,适用于异步回调,跨组件共享对象,共享生命周期。

4.4 怎么实现多态,虚函数表

多态满足条件为:①有继承关系②子类重写父类中的虚函数;
多态使用条件为:父类指针或引用指向子类对象;
虚函数通过虚函数表实现,编译器为含有虚函数的类生成一个虚表(vtable),类对象中包含一个虚指针(vptr)指向该表,即虚表。通过虚表实现运行时的动态绑定,从而支持多态。

虚函数的实现机制如下:
第一,虚函数表(vtable):每个类有一个虚表,存储该类的虚函数地址列表。如果某个类有虚函数,编译器就会自动生成这张表。
第二,虚指针(vptr): 每个含虚函数的类对象都有一个隐藏的成员,叫虚指针,指向当前类的虚表,这个指针用于运行时确定函数的真实调用目标。
第三,调用过程: 当通过基类指针或引用调用一个虚函数时,程序会:先找到对象里的 vptr;然后根据 vptr 找到对应类的 vtable;再从 vtable 中取出正确的函数地址并调用它。
第四,动态绑定(多态): 这种通过虚表间接调用函数的方式,就是运行时多态。它让程序在运行时决定调用哪个类的函数,而不是编译时决定。

引申问题:

  1. vtable 和 vptr 的内存布局是怎样的?
    答:vptr 通常是放在对象内存布局的 第一个位置,这样通过对象首地址就能快速访问虚函数表。多继承时,一个对象可能有 多个 vptr(每个继承链一个),对应多个 vtable。
  2. 虚函数和普通函数在调用开销上的不同?
    答:虚函数和普通函数在调用机制上的最大区别在于绑定时机和调用方式;普通函数是在编译时就完成绑定,调用时直接跳转到函数地址,效率较高;而虚函数是在运行时通过对象中的虚指针(vptr)查找对应的虚函数表(vtable),再从表中找到目标函数地址进行调用,这种多了一次间接寻址的过程导致其调用开销相对较大。不过,正因为虚函数采用动态绑定,才支持运行时多态,而普通函数不具备这种能力。简单来说,虚函数的灵活性是以一点性能损耗为代价换来的。

4.5 虚析构函数及其作用

当通过基类指针删除派生类对象时,基类析构函数必须是虚函数,否则不会调用派生类析构函数,可能导致资源泄露。
来源:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,解决方式:将父类中的析构函数改为虚析构或者纯虚析构。
虚析构和纯虚析构共性:可以解决父类指针释放子类对象;都需要有具体的函数实现
虚析构和纯虚析构区别:如果是纯虚析构,该类属于抽象类,无法实例化对象。

4.6 怎么防止内存泄露

防止内存泄露的核心思想是:谁申请、谁释放;用对象管理资源(RAII)。

  1. 优先使用RAII(对象管理资源)
    原则:将资源(内存、文件句柄、锁等)的生命周期绑定到对象,由对象的构造函数获取资源,析构函数释放资源。如智能指针,标准容器。
  2. 避免裸指针裸操作
    禁止new/delete 直接暴露:
    若必须手动管理,封装在类内部(如自定义内存池)。
    工厂函数返回智能指针;
  3. 工具与编译期检查
    静态分析:启用编译器警告;Clang-Tidy:检查未释放的内存
    动态监测:Address Sanitizer (ASan):运行时检测内存泄露;Valgrind;
  4. 代码规范与最佳实践
    Rule of Zero/Three/Five:
    若类管理资源,必须正确实现析构函数、拷贝/移动构造和赋值运算符(或使用智能指针避免手写);异常安全:用智能指针或 try-catch 确保异常时资源释放
    总结:
    在这里插入图片描述

4.7 智能指针有哪几种,shared_ptr是否线程安全

3种:

智能指针所有权模型典型用途
std::unique_ptr<T>独占(不可拷贝,只可移动)工厂函数返回值、RAII 资源
std::shared_ptr<T>共享(引用计数)多个所有者共享同一对象
std::weak_ptr<T>非拥有(观测 shared_ptr打破循环引用、缓存

shared_ptr的线程安全性:
结论:控制块(引用计数)是线程安全的,对象本身不是。
(shared_ptr的引用计数是线程安全的,但被管理的对象仍需手动同步)

操作场景线程安全?说明
多个线程 拷贝/析构 同一个 shared_ptr引用计数的增减是原子操作(C++11 起)。
多个线程 同时读写 被管理的 对象和普通裸指针一样,需自行加锁或使用 std::atomic 等机制。
多个线程 读写同一个 shared_ptr 实例同一实例 的读写需加锁(如 std::mutex)。

4.8 深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作 编译器提供
深拷贝:在堆区重新申请空间,进行拷贝操作

总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题,且要在析构函数中手动释放堆内存

4.9 手撕:字符串数组中第k大的数

4.10 补充:介绍C++的三大特性

简要回答:
C++的三大特性就是:封装、继承、多态。
一句话概括就是:
封装:把数据和操作打包在一起,外人不能随便动,通过public,private,protected保护数据。
继承:可以“复制+扩展”父类的内容,写代码更省力。子类通过继承使用父类公开的数据,属性和方法,包括实现继承,接口继承和可视继承。
多态:用“同一种方式”操作不同的对象,扩展性强。
详细回答:
1.封装(Encapsulation)
封装是指把数据和操作数据的方法包装在类里,通过访问权限(public、private、protected)来控制对外的暴露。好处是提高安全性,把实现细节藏起来,别人只能用你提供的接口,而不能直接改内部数据。
2. 继承(Inheritance)
继承让一个类(子类)拥有另一个类(父类)的属性和行为,可以在原有功能的基础上做改进和拓展。有了继承,代码可以复用,比如定义一个Animal类,再派生出Dog、Cat等具体动物类,不用重复写代码。
3. 多态(Polymorphism)
多态指的是“同一种接口,不同的行为”。 主要有两种:编译时多态(函数重载、运算符重载)运行时多态(虚函数+继承)比如写了一个 speak() 接口,猫、狗调用它表现都不一样,这就是多态的威力。其中编译时多态又叫静态多态,指的是在编译阶段就确定了函数调用的绑定关系。典型方式有两个:函数重载和运算符重载运行时多态又叫动态多态,指的是在程序运行时根据对象的实际类型来决定调用哪个函数。原理是通过重写父类虚函数,生成虚函数表和虚函数指针,通过父类引用指向不同的子类对象选取不同的虚函数表进而实现多态。
面试官可能追问:
1.编译时多态可以代替运行时多态吗?
答: 不完全可以。编译时多态(如模板、函数重载)是在编译期确定函数调用,适用于参数类型、数量已知的情况。而运行时多态(虚函数+继承)则是运行时根据对象真实类型做动态绑定,适用于“需要统一接口处理不同对象”的情况。当对象的真实类型在编译时就能确定,并且想避免虚函数开销,可以用模板+策略模式模拟运行时多态效果。
2. 虚函数调用的底层机制是怎样的?vtable 和 vptr 是怎么工作的?
答:虚函数背后有三样东西:虚函数表,虚指针,动态绑定过程。
虚函数表(vtable): 每个含虚函数的类在编译时都会生成一个vtable,里面存的是该类的虚函数地址列表。
虚指针(vptr): 每个对象内部都含一个隐藏的指针(vptr),指向它所属类的vtable。
动态绑定过程: 如:当用Animal* a = new Dog()时: 创建Dog对象时,会设置a->vptr指向Dog类的vtable。当调用a->speak(),程序会通过vptr找到Dog类的vtable,调用里边的speak()地址。拓展:vtable 是类级别的(多个对象共享),vptr是对象级别的
3. 能不用虚函数实现运行时多态吗?
答:可以,但要用其他技巧,比如函数指针、策略模式、std::function

4.11 补充:什么是智能指针,C++ 中有哪几种智能指针?

  1. 简要回答:
    智能指针 是一种自动管理动态内存的工具类,用于防止内存泄漏。
    c++提供了三种常用的指针 unique,share,weak。
    unique独占所有权,指针指向的对象只能有这一个指针;不能拷贝,只能移动,一个资源只能被一个unique管理。
    shared共享所有权,指针可以有多个,每释放一个指针变量,指针计数减少一个,到零时释放被指对象,常用来计数;多个指针可以共享一个资源,使用计数器控制资源释放。
    weak指针是一种弱指针,不拥有资源,防止循环引用,如果对象在指针还在时被释放,也不会报错,不受影响;用于观察共享指针的管理资源,不增加引用计数,防止循环引用。
  2. 详细回答:
    提到智能指针就必须知道RAII的编程思想,RAII是C++语言的一种管理资源、避免泄漏的惯用法。
    智能指针是用来自动管理动态内存的工具,通过封装原生指针在适当时机释放内存。
    unique_ptr:独占智能指针,独占对象所有权,同一时间只能有一个指针指向一个对象,适合独占资源的场景;禁止拷贝构造和拷贝赋值,支持移动语义
    share_ptr:一个共享所有权的智能指针,允许对象之间进行复制或者赋值,展示出来的就是值语义。
    使用引用计数的观点,当对象之间进行复制或者赋值的时候,引用计数会加+1,当最后一个对象销毁的时候,引用计数减为0,此时会回收托管的空间。
    weak_ptr:常用于解决share_ptr循环引用的问题,weak_ptr类的对象可以指向shared_ptr,并且不会改变shared_ptr的引用计数。一旦最后一个shared_ptr被销毁时,对象就会被释放。
    智能指针则深刻的体现了这种思想。在现代 C++ 编程中,标准库包含智能指针,该指针用于确保程序不存在内存和资源泄漏且是异常安全的。
    智能指针本质就是一个类模板,它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。
  3. 补充:(摘自卡码笔记)
    在这里插入图片描述
    三个指针同时运行的例子:
#include <iostream>
#include <memory>
using namespace std;

class Animal {
public:
    Animal(string name) : name_(name) {
        cout << "Animal " << name_ << " created.\n";
    }
    ~Animal() {
        cout << "Animal " << name_ << " destroyed.\n";
    }
    void speak() {
        cout << "Hi, I'm " << name_ << endl;
    }

private:
    string name_;
};

int main() {
    // 1. 使用 unique_ptr 管理一只独占的小狗
    unique_ptr<Animal> dog = make_unique<Animal>("Dog"); 
    dog->speak();

    // 2. 使用 shared_ptr 管理一只共享的猫
    shared_ptr<Animal> cat1 = make_shared<Animal>("Cat");
    {
        shared_ptr<Animal> cat2 = cat1;  // 共享一份资源
        cout << "Cat use_count = " << cat1.use_count() << endl; // 应该是 2
    } // cat2 离开作用域,引用计数 -1
    cout << "Cat use_count = " << cat1.use_count() << endl; // 应该是 1

    // 3. 使用 weak_ptr 观察 shared_ptr 管理的猫
    weak_ptr<Animal> weakCat = cat1;
    if (auto catShared = weakCat.lock()) {
        catShared->speak();  // 还活着,可以访问
    }

    // cat1 离开作用域后,猫对象自动销毁
    return0;
}
//==================================//
// 输出结果
Animal Dog created.
Hi, I'm Dog
Animal Cat created.
Cat use_count = 2
Cat use_count = 1
Hi, I'm Cat
Animal Dog destroyed.
Animal Cat destroyed.
  1. 什么是RAII机制? RAII 全称: Resource Acquisition Is Initialization(资源获取即初始化)
    即:当你用一个智能指针去创建对象时,它立刻就会接管资源(如内存); 当智能指针生命周期结束(如离开作用域、函数返回等),它会自动调用析构函数,把资源安全释放掉,不需要你手动 delete。
    通俗的讲就是,RAII就是“我拿到资源我就负责到底,我走了就顺手把它销毁”;它就像“靠谱保姆”,资源交给他,它就不会让别人乱碰了,也不忘记打扫干净。
  2. 记忆口诀
    unique 独家专属,不能复制
    shared 合作共赢,数清关系
    weak 偷偷观察,不管后事
    RAII 是关键,内存不手动

5. C++客户端开发面经

5.1 智能指针如果不使用,引用技术,还能使用其他方式来实现吗?

在 C++ 中,智能指针的核心目标是自动管理资源生命周期,避免内存泄漏。引用计数(如 std::shared_ptr)只是其中一种实现方式,但并非唯一。如果不使用引用计数,仍有其他策略可以实现智能指针:

  1. 所有权唯一:实现方式:std::unique_ptr
    通过独占所有权管理资源,禁止拷贝,仅允许移动(std::move)。无需引用计数,资源在析构时直接释放。
  2. 作用域绑定:实现方式:基于栈对象的析构函数自动释放资源(RAII)。例如 std::lock_guard、std::fstream 等,无需额外计数。
  3. 标记-清除:实现方式:通过垃圾回收器(GC)追踪所有可达对象,定期清理不可达对象。
    无需引用计数,但需要全局的根集(Root Set)和暂停程序扫描。
    库支持:std::experimental::observer_ptr(仅观察,不管理)
    第三方库如 Boehm GC(保守式 GC)。
  4. 区域内存清理:实现方式:在预分配的内存区域(Arena)中批量分配对象,销毁时一次性释放整个区域。无需逐个对象计数,适合短生命周期对象。
  5. 引用链接:实现方式:维护一个双向链表记录所有引用同一资源的指针。当最后一个指针被销毁时,链表为空,释放资源。
    避免引用计数的原子操作开销,但链表操作可能影响性能。
  6. 所有权树:实现方式:通过父子关系管理资源,父对象销毁时递归释放子对象。例如 Qt 的 QObject 树。
    在这里插入图片描述
    结论:引用计数只是智能指针的一种实现策略。std::unique_ptr 和 RAII 是 C++ 中最轻量、零开销的替代方案,而 垃圾回收区域内存管理 则适用于更复杂的场景。选择哪种方式取决于性能需求、资源模型和代码复杂度。

5.2 对比一下Java和C++的垃圾回收机制

5.3 为什么redis比较快

5.4 Realist高版本是不是多线程?为什么要改为多线程

5.5 IO多路复用有哪些方式?poll和epoll有什么区别?

常见的有select,poll,epoll
select的数据结构是位图,poll将位图换成了数组,解决了数量上限1024的问题,但他俩都是O(n)的轮询。
poll为轮询方式,每次都要遍历全部的fd,时间复杂度为O(n),而epoll为事件驱动,内核把就绪fd放进就绪链表,直接返回,时间复杂度为O(1)。

机制是否事件驱动就绪通知方式时间复杂度
poll❌ 轮询每次遍历全部 fdO(n)
epoll✅ 事件驱动内核把就绪 fd 放进就绪链表,直接返回O(1)

因此,只有 epoll才称得上真正的事件驱动;poll 只是“用户态轮询 + 内核线性扫描”的改良版 select。

维度pollepoll
数据结构简单数组,每次把整个数组拷进/拷出内核内核内部红黑树 + 就绪链表,只传增量
时间复杂度O(n):每次都要线性扫描全部 fdO(1):只返回真正就绪的 fd
fd 数量上限RLIMIT_NOFILE,可几万同左,但常把上限设到几十万也没问题
内核-用户拷贝每次调用都要完整拷贝第一次 epoll_ctl 注册后,后续几乎零拷贝
触发模式只有 水平触发 LT支持 LT + ET(边缘触发)
跨平台POSIX,几乎所有 Unix 都有Linux 独有
编程复杂度简单,直接替换 select稍高,需要 epoll_create/ctl/wait 三部曲
高并发场景万级并发就吃力十万、百万级并发仍游刃有余

5.6 如果redis内存爆炸了怎么办?

5.7 介绍一下,平常都用过C++中哪些数据结构

序列,关联,无需,容器适配器,字符串/视图,智能指针

  1. 序列式容器(元素顺序即插入顺序)
    std::vector,std::array,std::deque,std::list / std::forward_list,std::string
  2. 有序关联容器(基于红黑树)
    std::map / std::set,std::multimap / std::multiset,
  3. 无序关联容器(基于哈希表)
    std::unordered_map / unordered_set,unordered_multimap / unordered_multiset
  4. 容器适配器(底层复用前面容器)
    std::stack,std::queue,std::priority_queue
  5. 字符串视图
    std::string_view(C++17)std::span(C++20)
  6. 智能指针(资源管理容器)
    std::unique_ptr,std::shared_ptr / weak_ptr

5.8 处理哈希冲突,除了用红黑树和链表,还能有其他什么处理方式?开放定址法

哈希冲突:不同的输入可能产生相同的哈希值
冲突的处理机制:链表法,开放定址法
开放定制法:所有键值都留在 同一个数组 里,冲突时按规则找下一个空槽。

5.9 死锁的产生有哪些条件?

  1. 互斥条件:资源是独占的。
  2. 占有且等待:线程持有至少一个资源,同时又在等待获取其他线程占有的资源。
  3. 不可抢占条件:资源不能被强制剥夺,只能由占有它的线程主动释放。
  4. 循环等待条件:存在一个线程-资源的循环链,链中的每个线程都在等待下一个线程占有的资源。

如何破坏死锁?只要破坏 任意一个条件即可:
破坏互斥:使用无锁(Lock-Free)数据结构(如std::atomic)。
破坏占有且等待:一次性申请所有资源(如 std::scoped_lock 同时锁多个互斥量)。
破坏不可抢占:使用 std::timed_mutex 超时放弃锁。
破坏循环等待:按固定顺序加锁(如全局锁层级表)。

5.10 TCP协议保证不出错的原因是什么?

协议本身内置了一整套“检错+重传+顺序+去重+流量/拥塞控制”机制

  1. 检错:发现“错了”
    16 位校验和(Checksum)
    覆盖 TCP 首部 + 数据 + 伪首部(源/目的 IP 等)。
    接收端计算校验和失败 → 直接丢弃该报文段,不发 ACK → 发送端超时重传。
  2. 重传:把“错的/丢的”补回来
    超时重传(RTO) 快速重传(Fast Retransmit)SACK / DSACK
  3. 顺序:让“乱序”变“有序”
    序列号(Sequence Number)
    每个字节都有编号,接收端根据序列号重新排序,再交给应用层。乱序到达的数据暂存在接收缓冲区,空缺填补后才连续向上交付。
  4. 去重:解决“重复”
    接收端滑动窗口 + 序列号检查
    已确认过的序列号再次到达会被丢弃,防止因重传造成重复数据。
  5. 流量/拥塞控制:减少“出错概率”
    流量控制(滑动窗口)防止接收端缓存溢出导致丢包。
    拥塞控制(慢启动、拥塞避免、快速恢复)
    防止网络过载引起大规模丢包/重传,降低“看起来出错”的几率。

5.11 HTTP协议常见的请求头有哪些?

5.12 算法:找链表倒数第n个节点

6. C++侧开面经

6.1 Hashset和hashmap的区别

7. C++后端开发面经

7.1 手撕:给一个数组candies,数组中的每个元素candies[i]代表一堆糖果,把这些糖果分给k个小朋友,每堆糖果可以分成几堆,但不能再和其他堆的糖果组合在一起,请每个小朋友可以获得的最多糖果,每个小朋友只能获得一堆糖果

例子1:candies=[5,8,6],k=3,每个小孩最多可以获得5个糖果,8拆成5+3,6拆成5+1
例子2:candies = [2,7],k=11,每个小孩最多可以获得0个糖果,糖果总数小于人树
例子3:candies=[5,8,6],k=4 每个小朋友最多可以获得4个糖果,5拆成4+1,8拆成4+4,6拆成4+2。

8. C++开发面经

8.1 说说重载函数,重载函数的return可以不同吗?

8.2 重载和重写的区别?

8.3 构造函数与析构函数可以为虚函数吗?同时讲讲虚函数原理

8.4 线程与进程的区别

8.5 了解线程池吗

8.6 C++11的新特性

8.7 GDB调试的一些命令

8.8 如何在GDB中查看崩溃点的变量的状态

8.9 Git中如何回退到之前的commit

8.10 Git中如何创建分支与合并

8.11 平时如何让进行git上的项目控制,分支开几个

8.12 手撕冒泡排序

8.13 vector和list的区别

8.14 map和unordered_map的区别

8.15 vector中resize和reserve的区别

9. C++开发面经

9.1 TCP为什么四次挥手,三次行不行

9.2 Mysql的索引是什么数据结构?为什么不用红黑树或者B树?

9.3 MySQL有哪些日志?什么作用

9.4 算法:k个一组翻转链表,要求只翻转id为奇数的组,如1 3 5组翻转,2 4 6组不动

10. C++开发面经

10.1 Mmap和malloc区别,什么时候使用map什么时候使用malloc?

10.2 讲一下观察者模式,有对象需要立即响应,有些不需要,怎么设计?多个对象需要立即响应呢?

10.3 互斥锁怎么写?

10.4 读写锁,当一个程序读占用,后续写操作可不可以运行?如何后面再加读操作呢?

10.5 map和unorderedmap区别,什么时候使用红黑树,什么时候使用哈希表

10.6 tcp有哪些算法?怎么优化

10.7 死锁条件,如何避免,如何破坏死锁

11. C++侧开面经

11.1 数据库增删改查 表连接join怎么使用

11.2 linux命名 进程 查看当前磁盘,怎么看当前进程 的id

11.3 手撕 怎么读取一个json文件的信息

12. C++开发面经

12.1 mysql使用注意事项

12.2 什么是迭代器?C++中有哪些类型的迭代器

12.3 查询平均成绩大于等于 85 的所有学生的学号姓名和平均成绩

12.4 什么是最左前缀原则?

12.5 64位系统中,各种常用内置数据类型占用的字节数?

12.6 什么是ABA问题?如何解决

12.7 C++中的SFINAE是什么?举例说明

12.8 查询每门课程的平均成绩,结果按平均成绩降序排列,平均成绩相同时,按课程编号升序排列x

12.9 谈谈你对装饰器的理解?

12.10 什么是死锁?如何避免

12.11 构造函数初始化列表

12.12 有了进程为什么还要线程

12.13 浏览器输入URL发生了什么?

12.14 深拷贝与浅拷贝的区别?

12.15 static和const分别怎么用,类里面static和const可以同时修饰成员函数吗?

12.16 TCP如何保证可靠?

12.17 std::unique ptr和std::shared ptr有什么区别

12.18 python程序退出的时候,是否释放了全部内存?

12.19 C++构造函数几种,分别有什么作用?

C++构造函数主要有以下几种:

  1. 默认构造函数:无参或所有参数都有默认值,用于创建对象时的默认初始化。
  2. 参数化构造函数:带参数,用于创建对象时进行特定初始化。
  3. 拷贝构造函数:接受同类型对象的引用,用于通过已有对象初始化新对象。
  4. 移动构造函数(C++11):接受同类型对象的右值引用,用于资源的高效转移。
  5. 委托构造函数(C++11):一个构造函数可以调用同类中的其他构造函数。
  6. 继承构造函数(C++11):派生类可以直接使用基类的构造函数。

详细回答:
默认构造函数
如果没有显式定义任何构造函数,编译器会自动生成一个默认构造函数。如果定义了其他构造函数,编译器不会自动生成默认构造函数,除非显式声明 = default。
参数化构造函数
允许在创建对象时传入参数,直接初始化成员变量。可以重载多个版本以适应不同的初始化需求。
拷贝构造函数
形式为ClassName(const ClassName& other),用于深拷贝或浅拷贝。如果未定义,编译器会生成一个默认的拷贝构造函数(按成员浅拷贝)。
移动构造函数
形式为 ClassName(ClassName&& other),用于“窃取”临时对象(右值)的资源,避免不必要的拷贝。典型应用场景:STL容器、智能指针等需要高效资源管理的场景。
委托构造函数
允许一个构造函数调用同类中的其他构造函数,避免代码重复。示例:ClassName() : ClassName(0, 0) { }。
继承构造函数
派生类通过using Base::Base直接继承基类的构造函数(C++11特性)。注意:继承的构造函数不会初始化派生类新增的成员。
面试官可能追问:
为什么需要移动构造函数?
答:避免深拷贝临时对象的资源,提升性能(如 std::vector 的 push_back 使用移动语义)。
拷贝构造函数参数为什么必须是 const 引用?
答:避免无限递归调用拷贝构造,同时支持常量对象的拷贝。
什么情况下编译器不会生成默认构造函数?
答:当用户定义了其他构造函数时,除非显式声明 = default。

13. C++开发面经

  1. 对比分析python中列表、元组、集合和字典这四种数据结构的特点及其应用场景
  2. 函数重载的实现机制是什么?在编译过程中是如何处理的?
  3. C++四种强制类型转换
  4. explicit关键字
  5. lamda表达式的值捕获和引用捕获的区别
  6. 类的静态成员变量和静态成员函数有哪些特性?他们与普通成员的区别是什么?
  7. 请解释C++中虚函数的实现实现原理及其在多态中的应用
  8. C++智能指针
  9. GCC编译器的常用优化参数有哪些?各有什么作用?
  10. 如何使用gdb进行程序调试 ?请说明常用调试命令
  11. Linux中定时任务的配置方法有哪些?
  12. 当软件安装出现依赖缺失时,有哪些解决方法?

14. C++开发面经

  1. 最长回文字串,一样长返回第一个出现的
  2. 合并两个有序数组
  3. 两个降序链表合并为一个新的升序
  4. 字符串相加

15. C++开发面经

  1. 程序局部性原理
    程序局部性原理表现为:时间局部性和空间局部性
    时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某块数据被访问,则不久之后该数据可能再次被访问。
    空间局部性是指一旦程序访问了某个存储单元,则不久之后,其附近的存储单元也将被访问。

15.1 C++如何实现一个单例模式?

  1. 简要回答
    单例模式(Singleton Pattern)确保某个类在程序运行期间只能有一个实例,并提供一个全局访问点。
    常见的 C++ 实现方式:
    饿汉式:程序启动时立即创建
    懒汉式:第一次使用时才创建,线程不安全
    双检锁(懒汉式 + 线程安全):加锁或使用 std::call_once
    C++11 局部静态变量(Magic Static):推荐使用这样,因为这种情景下线程是安全的
  2. 详细回答
    单例模式通过以下机制确保类的唯一性:
    权限控制:将构造函数、拷贝构造函数和赋值运算符设为私有或删除,防止外部实例化和拷贝
    静态管家:类内部维护一个静态成员变量来保存唯一实例
    全局访问点:提供公共静态方法(通常命名为getInstance)作为获取实例的唯一入口
    每种单例模式的特点
    饿汉式(Eager Initialization)
    特点:程序启动时就创建实例优点:实现简单,线程安全
    缺点:可能造成资源浪费(实例未被使用时也占用内存)
class Singleton {
private:
    static Singleton* instance;
    Singleton() {}
public:
    static Singleton* getInstance() {
        return instance;
    }
};
// 程序启动时初始化
Singleton* Singleton::instance = new Singleton();

懒汉式(Lazy Initialization)
特点:首次请求时才创建实例
优点:资源利用率高
缺点:基础实现非线程安全

class Singleton {
private:
    static Singleton* instance;
    Singleton() {}

public:
    static Singleton* getInstance() {
        if (!instance)
            instance = new Singleton();
        return instance;
    }
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;

双检锁(Double-Checked Locking)
特点:懒加载+线程安全
原理:两次检查实例是否存在,中间加锁

class Singleton {
private:
    static Singleton* instance;
    staticstd::mutex mtx;
    Singleton() {}
public:
    static Singleton* getInstance() {
        if(instance == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            if(instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance;
    }
};

局部静态变量(Magic Static, C++11推荐)
特点:利用C++11的静态局部变量线程安全特性
优点:代码简洁,自动线程安全,自动销毁

class Singleton {
private:
    Singleton() {}
public:
    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }
};
  1. 代码示例
    现代C++推荐实现:局部静态变量,这是用的最多的,下面给出一个这种情况下的例子
#include <iostream>

class Singleton {
public:
    // 删除拷贝构造和赋值操作
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    
    // 全局访问点
    static Singleton& getInstance() {
        static Singleton instance; // 线程安全初始化(C++11)
        return instance;
    }
    
    // 示例方法
    void log(const std::string& message) {
        std::cout << "[LOG] " << message << std::endl;
    }

private:
    // 私有构造函数
    Singleton() { std::cout << "Singleton created\n"; }
    ~Singleton() { std::cout << "Singleton destroyed\n"; }
};

int main() {
    Singleton::getInstance().log("Hello World");
    Singleton::getInstance().log("Another message");
    return0;
}
  1. 知识拓展
    单例模式的优缺点
    优点:
    严格控制实例数量,避免资源浪费,全局访问点简化资源访问;避免频繁创建销毁对象,减少全局变量污染
    缺点:
    违反单一职责原则(同时管理自身和业务),且单元测试困难(全局状态难以隔离);可能隐藏类间依赖关系,多线程环境需要特殊处理

知识图解:
在这里插入图片描述
使用场景
比如要实现下列功能时
日志管理器(Logger)
数据库连接池
配置读取器(配置文件只读一次)
驱动设备控制器(如串口、打印机)
游戏中的全局控制器(音效、UI管理)

15.2 STL容器了解哪些?

  1. 简要回答:
    C++ STL 中主要有三类容器:
    顺序容器(Sequence Containers):如 vector, list, deque, array, forward_list
    关联容器(Associative Containers):如 set, map, multiset, multimap
    无序容器(Unordered Containers)哈希容器:如 unordered_map, unordered_set, unordered_multimap, unordered_multiset
    此外,STL 还包括配套的 迭代器、算法、仿函数、适配器等组件。
  2. 详细回答:
    STL容器可分为三大类:
    序列容器: 维护元素的线性序列
    vector:动态数组,支持快速随机访问
    deque:双端队列,两端高效插入/删除
    list:双向链表,任意位置高效插入/删除
    forward_list:单向链表,更节省空间
    array:固定大小数组,更安全的原生数组替代
    关联容器:基于键值对的有序存储
    set/multiset:有序唯一/非唯一键集合
    map/multimap:有序键值对集合,键唯一/非唯一
    无序关联容器:基于哈希表的存储unordered_set/unordered_multiset
    unordered_map/unordered_multimap
    每种容器在时间复杂度、内存布局和适用场景上有所不同。

STL的容器的特点如下:
顺序容器:

容器特点
vector动态数组,随机访问快,尾部插入快
list双向链表,任意位置插入/删除快
deque双端队列,头尾都能快速插入/删除
array固定大小数组(C++11)
forward_list单向链表(C++11)

关联容器(有序,基于红黑树):

容器特点
set唯一元素自动排序
multiset元素允许重复
map键值对,键唯一
multimap键值对,键可重复

无序容器(基于哈希表,C++11):

容器特点
unordered_set无序唯一集合
unordered_map无序键值对
unordered_multiset无序可重复集合
unordered_multimap无序可重复键值对

代码示例:

#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <unordered_map>
#include <list>
usingnamespacestd;

int main() {
    vector<int> vec = {1, 2, 3};
    vec.push_back(4);  // 动态数组

    list<int> lst = {10, 20, 30};
    lst.push_front(5); // 双向链表

    set<int> s = {5, 2, 3, 2}; // 自动去重排序
    map<string, int> m = {{"Tom", 90}, {"Jerry", 85}}; // 字典

    unordered_map<string, int> um;
    um["apple"] = 3;
    um["banana"] = 5; // 哈希表

    return0;
}

知识拓展:
在这里插入图片描述
扩展:
STL容器的底层结构
vector, deque → 动态数组
list, forward_list → 链表
set, map → 红黑树
unordered_* → 哈希表
性能差异(插入、删除、查找):
vector 随机访问快,但插入慢(中间插入需移动元素)
list 插入删除快,但不支持随机访问
map/set 查找、插入都是 O(logN),但有序
unordered_map 查找、插入是 O(1) 平均复杂度
使用场景:
vector:需要随机访问、动态扩容的情况(如图像处理、线性缓存)
list:频繁插入删除数据的场景(如编辑器撤回栈)
map/unordered_map:查找、计数、字典、配置解析(如记录访问次数)
set:需要去重、唯一性判断(如用户 ID 集合)
deque:实现滑动窗口算法、双端队列缓存(如 LRU)

15.3 进程间有哪些通信方式?共享内存为什么最快?

简要回答:

  1. 信号量机制(Semaphore):进程通过P / V操作控制信号量,可以有效地解决同步和互斥问题;信号量机制适合需要同步的场景,如避免竞争条件和死锁。
  2. 共享存储机制(Shared Memory):多个进程能够直接读写同一块内存空间,这种机制被称为共享内存,共享内存是实现进程间通信的一种高效方法。共享存储机制适合需要高效数据传输的场景。
  3. 消息传递机制(Message Passing):消息传递机制需要操作系统内核的支持,通过消息队列 或 信箱传递数据,消息以数据块的形式传输。消息传递机制支持不同类型的数据,适用于无关进程之间的通信。
  4. 管道通信机制(Pipeline):管道通信机制适合进程间的简单数据传输,其又可细分为两类——①匿名管道:半双工通信,适用于父子进程或兄弟进程;②命名管道:具有文件名,适用于无关进程。
  5. 套接字机制(Socket):Socket(套接字)是一种网络通信机制,可以在不同主机上的进程之间进行通信;Socket机制适合分布式系统和网络通信。

详细回答:

  1. 信号量机制(Semaphore):信号量是一种同步通信机制,用于控制多个进程对共享资源的访问;信号量支持两种原子操作:P()(等待)和V()(信号)。
  2. 共享存储机制(Shared Memory):共享存储机制又可细分为两种方式——①共享数据结构的通信方式:进程间共享同一个数据结构,编程人员需要考虑进程间的同步互斥问题,传输效率低;②共享存储区的通信方式:在计算机系统中,操作系统会分配特定的共享内存区域供多个进程同时访问。这种机制允许不同进程高效地进行数据交换。当某个进程需要访问共享内存时,首先需要完成虚拟地址空间到共享内存区域的映射操作,然后向操作系统提交访问请求。
  3. 消息传递机制(Message Passing):进程之间可以调用由操作系统实现的通信命令来传递消息;实现消息传递即分别实现SendReceive两个原语,用于消息的发送和接收。消息传递机制又可细分为两类——①直接通信方式:发送进程直接将消息发送给接收进程,将信息挂载到接收进程的消息队列上,接收进程从自己的消息队列中获取信息;②间接通信方式:操作系统额外创建一片空间用于存储各种消息,类似一个信箱,发送进程和接收进程依靠这个中间实体实现信息的发送和接收,这种方式也被称为 “信箱通信模式”,在计算机网络中被广泛运用。
  4. 管道通信机制(Pipeline):管道机制又可细分为两种类型——
    匿名管道:即我们平时所说的“管道”通信,它使用一种共享文件来实现进程间通信,这种共享文件被称为“管道” 或 “pipe文件”,可以连接一个读进程和一个写进程。写进程以字节流的形式,将数据送入管道,读进程通过管道将数据读出。匿名管道作为一种半双工通信机制,其数据传输具有单向性特征,主要应用于具有亲缘关系的进程间通信,包括父子进程或兄弟进程之间的交互。在UNIX/Linux操作系统中,开发者可以通过调用pipe()函数来建立匿名管道。
    命名管道(Named Pipe/FIFO):在UNIX/Linux操作系统中,存在一种具备特定文件名的特殊通信管道,该管道能够实现不同进程间的数据交互。通过调用mkfifo()这一系统函数,可以创建此类被命名的管道。
  5. 套接字机制(Socket):Socket是一种网络通信机制,支持不同主机上的进程之间进行通信。网络通信功能可通过Socket实现,特别适合分布式架构的应用场景。从通信模式来看,既可采用基于连接的TCP协议,也可选择无连接的UDP协议进行数据传输。

更多总结可参见:
https://mp.weixin.qq.com/s/ZVzVK84kwtL1UgZ7y7exyQ

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值