360 C++开发一面

1. 简单介绍一下你自己和你做过的项目

回答要点:

  • 教育背景和专业方向
  • 核心技术栈和擅长领域
  • 1-2个代表性项目,突出技术亮点
  • 对应聘岗位的理解和兴趣
  • 控制在2-3分钟

参考模板:"您好,我是XXX,XX大学计算机专业硕士应届生。主要研究方向是后端开发和分布式系统,熟练掌握C++和Python,对数据结构算法有深入理解。

我主要参与过两个项目:一个是高性能网络服务框架,采用Reactor模式实现,能够支持万级并发;另一个是分布式存储系统,使用Redis集群做缓存,MySQL做持久化存储。在项目中积累了网络编程、多线程开发、数据库优化等经验。

我对360的安全技术很感兴趣,希望能够加入团队学习成长。"

2. 你了解C++的封装、继承、多态吗?多态有几种实现方式?

答案:

三大特性:

  • 封装:将数据和方法封装在类内部,通过访问控制隐藏实现细节
  • 继承:派生类继承基类的属性和方法,实现代码复用和扩展
  • 多态:相同的接口调用产生不同的行为,提高代码灵活性

多态的实现方式:

编译时多态(静态绑定):

  • 函数重载:同名函数参数列表不同
  • 运算符重载:自定义运算符行为
  • 模板:编译期根据类型生成代码

运行时多态(动态绑定):

  • 虚函数机制:通过虚函数表实现
  • 基类指针或引用指向派生类对象
  • 运行时根据对象实际类型调用对应函数

两者对比:

  • 编译时多态效率高,可以内联优化,但灵活性较低
  • 运行时多态灵活性高,但有虚函数调用开销

3. 什么是菱形继承问题?如何用虚继承解决?

答案:

菱形继承问题:当类D同时继承类B和类C,而B和C都继承自类A时,形成菱形继承结构。

带来的问题:

  1. 二义性问题:D访问A的成员时,编译器不知道走B还是C的路径
  2. 数据冗余:A的成员变量在D中存在两份副本

虚继承解决方案:让B和C虚继承A,保证A在D中只有一份实例。

实现原理:

  • 虚继承使用虚基类指针(vbptr)
  • 虚基类指针指向虚基类表
  • 虚基类表记录虚基类相对于当前类的偏移
  • 最终派生类负责虚基类的初始化

注意事项:

  • 虚继承有额外的内存和性能开销
  • 尽量避免复杂的继承层次
  • 优先使用组合而非继承

4. 线程和进程有什么本质区别?各自的应用场景是什么?

答案:

核心区别:

  • 资源所有权:进程拥有独立的资源(内存、文件句柄等),线程共享所属进程的资源
  • 调度单位:进程是资源分配单位,线程是CPU调度单位
  • 地址空间:进程有独立地址空间,线程共享进程地址空间
  • 创建开销:进程创建需要分配资源,开销大;线程创建开销小
  • 通信方式:进程间通信需要IPC机制,线程间通信直接访问共享内存
  • 崩溃影响:进程崩溃不影响其他进程,线程崩溃可能导致整个进程崩溃

应用场景:

选择进程:

  • 需要强隔离性和安全性(浏览器多进程架构)
  • 不同功能模块相对独立
  • 需要充分利用多核CPU且任务独立性强

选择线程:

  • 需要频繁的数据共享和通信
  • 任务粒度小,需要快速创建和销毁
  • 对性能要求高,希望减少上下文切换开销

5. 什么情况下会发生死锁?如何避免?

答案:

死锁的四个必要条件:

  1. 互斥使用:资源同一时刻只能被一个进程使用
  2. 占有并等待:进程持有资源的同时等待获取其他资源
  3. 不可强占:资源只能由持有者主动释放,不能被强制剥夺
  4. 循环等待:存在进程-资源的循环等待链

预防死锁(破坏必要条件):

  • 破坏互斥:将资源设计为可共享(并非总是可行)
  • 破坏占有等待:进程一次性申请所有需要的资源
  • 破坏不可强占:申请资源失败时释放已持有的资源
  • 破坏循环等待:对资源进行编号,按序申请资源

实际开发中的避免方法:

  • 统一加锁顺序:所有线程按相同顺序获取锁
  • 使用超时机制:try_lock_for设置超时时间
  • 使用std::lock:同时锁定多个互斥量
  • 减少锁的持有时间:尽快释放锁
  • 避免嵌套锁:尽量只持有一个锁

6. 介绍一下你的项目,技术架构是怎样的?

回答要点:

  • 项目背景和解决的问题
  • 整体技术架构和模块划分
  • 使用的技术栈和中间件
  • 自己负责的核心模块
  • 遇到的技术挑战和解决方案
  • 项目成果和性能指标

示例回答:"我做的是一个高并发Web服务项目,主要提供RESTful API服务。

技术架构采用分层设计:

  • 接入层:Nginx做负载均衡和反向代理
  • 业务层:C++实现的应用服务器,使用epoll处理网络IO
  • 缓存层:Redis集群缓存热点数据
  • 存储层:MySQL主从架构做持久化

我主要负责业务层的开发,实现了基于Reactor模式的网络框架,使用线程池处理业务逻辑。遇到的主要挑战是高并发下的性能瓶颈,通过引入对象池、优化锁粒度、使用无锁队列等方式,将QPS从8000提升到20000。"

7. 项目中Redis用来解决什么问题?用了哪些数据类型?

答案:

Redis的主要用途:

  1. 缓存层:缓存数据库查询结果,减轻DB压力
  2. Session存储:分布式环境下的会话管理
  3. 计数统计:实时统计在线用户数、访问量
  4. 排行榜:利用有序集合实现实时排名
  5. 消息队列:使用列表实现简单的生产消费模型

使用的数据类型:

  • String:缓存用户token,设置过期时间实现自动失效
  • Hash:存储对象信息,可以单独更新某个字段
  • List:实现简单消息队列,LPUSH生产,BRPOP消费
  • Set:存储用户关注列表,支持交集、并集运算
  • Sorted Set:实现积分排行榜,按分数自动排序

为什么选择Redis:

  • 性能高:基于内存,读写速度快
  • 数据结构丰富:满足多种业务场景
  • 支持持久化:数据不易丢失
  • 支持主从复制:高可用

8. TCP和UDP有什么区别?头部分别占多少字节?

答案:

主要区别:

  • 连接性:TCP需要建立连接(三次握手),UDP无连接直接发送
  • 可靠性:TCP保证可靠传输(确认重传机制),UDP不保证
  • 顺序性:TCP保证数据按序到达,UDP不保证
  • 效率:UDP效率高延迟低,TCP有连接和确认开销
  • 流量控制:TCP有滑动窗口机制,UDP没有
  • 拥塞控制:TCP有拥塞控制算法,UDP没有

头部大小:

  • TCP头部:最小20字节(不包含选项字段) 包含源端口、目的端口、序列号、确认号、窗口大小等
  • UDP头部:固定8字节 只包含源端口、目的端口、长度、校验和

应用场景:

  • TCP:HTTP、HTTPS、FTP、邮件传输等需要可靠传输的场景
  • UDP:视频直播、语音通话、DNS查询等对实时性要求高的场景

9. TCP三次握手的详细过程是什么?握手前双方是什么状态?

答案:

三次握手详细过程:

第一次握手:

  • 客户端发送SYN报文(SYN=1,seq=x)
  • 客户端进入SYN_SENT状态

第二次握手:

  • 服务端收到SYN,回复SYN+ACK报文(SYN=1,ACK=1,seq=y,ack=x+1)
  • 服务端进入SYN_RCVD状态

第三次握手:

  • 客户端收到SYN+ACK,发送ACK报文(ACK=1,seq=x+1,ack=y+1)
  • 客户端进入ESTABLISHED状态
  • 服务端收到ACK后也进入ESTABLISHED状态

初始状态:

  • 客户端:CLOSED状态
  • 服务端:LISTEN状态(监听端口等待连接)

为什么需要三次握手:

  1. 确认双方的收发能力都正常
  2. 防止已失效的连接请求突然传到服务端
  3. 协商初始序列号,保证数据传输的可靠性

两次握手的问题:如果只有两次握手,旧的重复连接请求可能导致服务端建立无效连接,浪费资源。

10. 网络层常用的协议有哪些?分别有什么作用?

答案:

主要网络层协议:

1. IP协议(Internet Protocol)

  • 负责数据包的寻址和路由
  • 提供无连接、不可靠的数据报传输
  • IPv4使用32位地址,IPv6使用128位地址

2. ICMP协议(Internet Control Message Protocol)

  • 用于网络诊断和错误报告
  • ping命令基于ICMP的Echo请求和应答

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C++八股文全集 文章被收录于专栏

本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。

全部评论

相关推荐

评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务