面灵AI→

C++ 面经合集:正孚软件、大梦龙途、辰瑞炽娱三场面试

轮次
多轮面试合集
时间
2026-09
来源
牛客网

《面试题目》

  1. 请做一下自我介绍,并介绍工作或实习经历。
  2. 讲一下工作做了什么、负责哪些模块、模块里有哪些功能,拆解一下功能的实现原理,遇到过哪些核心问题?
  3. 讲一下你自己的项目。
  4. vector 和 list 有什么区别?
  5. 多线程访问 vector 和 list 会有什么问题?
  6. 为什么多线程读没问题?读写同时进行呢?
  7. 线程池有没有用过?
  8. 多线程日志系统怎么设计,怎么体现多线程?
  9. 平时使用什么开发工具?
  10. 有没有在本地用 gcc 编译过工程,比如生成 .so 文件?
  11. C++ 指针和引用的区别是什么?
  12. 使用空指针会发生什么?
  13. 使用指针还有什么需要注意的?
  14. shared_ptr 在什么场景下使用,需要注意什么?
  15. shared_ptr 是怎么实现的?
  16. RAII 是什么,说一下。
  17. 对虚函数是怎么理解的?
  18. 虚函数是怎么实现的?
  19. Linux 用到哪些指令?
  20. GDB 怎么用?
  21. 编译 C++ 用什么级别的优化?
  22. TCP 和 UDP 的区别?
  23. TCP 怎么保证可靠性?
  24. 你是怎么用 epoll 实现 IOManager 的?
  25. 为什么项目用协程,而不是传统的异步回调?
  26. 协程的调度是怎么实现的,用什么策略做协程调度的管理?
  27. 讲一下你上一份工作的主要内容。
  28. 了解的排序算法有哪些?
  29. 讲一下快排是怎么实现的,时间复杂度是多少,什么情况下效率最差、什么时候效率最好?时间复杂度是怎么算出来的?
  30. 手写代码:实现单向链表的插入、删除和反转,完成后讲一下你的思路。
  31. 数组和链表的原理、使用场景与性能优缺点分别是什么?
  32. 实现一个队列,包含初始化、入队、出队。
  33. 实现一个栈。
  34. 给定一个由 1-9 组成的数组,判断它能否由顺子和刻子组成,写出思路或伪代码。
  35. 如果线下笔试的算法题是一个要交付的实际任务,出现各种情况你会怎么处理?
  36. 让你自己讲一下简历,说说有什么亮点?

《参考解析》

多线程访问 vector 和 list:标准容器不提供任何线程安全保证,能并发的前提是”没有写者”。vector 是连续内存,扩容会重新申请一块并搬移元素,此时读线程手里的指针、引用、迭代器全部失效,可能直接读到已释放的旧缓冲;list 是双向链表,插入删除只改相邻节点的指针,但节点本身不是原子的,遍历线程撞上 erase 就会拿到悬空指针,而且 size()、迭代器自增这类操作也不是原子的。所以”多个线程同时只读”安全,“读写同时”或”多写”必须自己同步:读多写少用读写锁,写多就分段/分片各管一块,或者换成明确内存序的无锁结构;锁的粒度要覆盖整个遍历过程,只在单次 push_back 上加锁没有意义。

shared_ptr 的实现与注意点:shared_ptr 由”对象指针 + 控制块指针”两部分组成,控制块里放强引用计数、弱引用计数、deleter 和 allocator。拷贝构造让强计数原子加一,析构原子减一,减到 0 就销毁被管理对象,弱计数同时减到 0 才真正释放控制块——这也是 weak_ptr 能安全判断对象是否还活着的原因。make_shared 会把对象和控制块放在同一次分配里,少一次内存申请、缓存更友好,代价是只要还有 weak_ptr,整块内存就回收不了。常见追问:引用计数原子不代表被管理对象线程安全;循环引用要用 weak_ptr 打断;同一个裸指针不能构造两个 shared_ptr,否则会双重释放;需要在成员函数里返回自身时继承 enable_shared_from_this;析构时机不确定,别把重要清理逻辑塞进析构函数。

虚函数是怎么实现的:编译器给每个多态类生成一张虚表,按声明顺序排好虚函数地址(表里还带 RTTI 指针和 offset-to-top 等运行时信息),对象头里存一个 vptr 指向它。基类指针或引用调用虚函数时,走 vptr 找到槽位再间接跳转,这就是动态绑定;子类覆写只是把对应槽位换成自己的地址,多继承下对象会有多个 vptr,跨基类指针调用还需要 this 调整。两个高频细节:构造和析构期间 vptr 指向当前正在构造/析构的类,所以在构造函数里调虚函数不会分发到子类;纯虚函数槽位放的是 __cxa_pure_virtual 占位,误调到会直接终止程序。

TCP 怎么保证可靠性:靠序号、确认、重传、校验、去重排序和滑动窗口这一整套配合。发送方给每个字节编号,接收方用累积确认告诉对方”这段我都要了”;丢包由两条路发现——超时重传(RTO 由平滑 RTT 加方差估计,失败后指数退避,代价是慢)和快速重传(连收三个重复 ACK 就认为中间缺了一段,不等超时立刻重发)。三个重复 ACK 是经典 Reno 的做法,但对窗口尾部的丢包无能为力,只能等 RTO;SACK 让接收方在选项里明确报出已收到的离散区间,发送方就能只补真正缺失的那段,避免一个丢包拖累整个窗口。此外校验和负责检错(不纠错),滑动窗口负责流量控制,重复报文靠序号去重,应用层仍需自己做幂等。

快排的复杂度与最坏情况:平均 O(n log n)、最坏 O(n²)。最坏出现在每次划分都极不平衡时,典型触发条件是”已排序或逆序数组 + 固定取第一个元素做基准”,大量重复元素也会让朴素双指针划分退化。递归深度平均 O(log n)、最坏 O(n),所以工程实现一般做三件事:随机化或三数取中选基准,小区间切换插入排序,递归改成显式栈或尾递归优化把空间压到 O(log n);重复元素多就用三路划分(小于/等于/大于)。另外快排不稳定,需要稳定排序时用归并,或把下标作为比较的第二关键字。

手写单链表的插入、删除与反转:这题考察的是指针操作的边界意识,面试官通常盯着几个点看:空链表、只有一个节点、在头节点插入/删除时有没有更新头指针(传 Node** 或用带哨兵的头节点能少一类 bug)、删除时有没有先保存 next 再 free、内存有没有泄漏。反转用三指针迭代(prev / cur / next)一趟走完,O(n) 时间 O(1) 空间,循环里的顺序是”先存 next → 改 cur->next 指向 prev → prev 和 cur 一起前移”,最后把头指针指向 prev;递归写法代码短但栈深 O(n),链表长时会爆栈。写之前先跟面试官确认是否允许哨兵节点、是否需要处理环,比闷头写完再被追问要好。