原文地址:腾讯校招 C++ 一面:30道题
最近有位粉丝朋友参加了腾讯C++岗位的面试,面试官提出了30道技术问题,涵盖了C++基础、网络编程、多线程、Web服务等多个方面。
我们来看一下腾讯校招的一面面经(30道题),希望能为大家提供参考。
如果你也准备冲击大厂,一定要做足了准备,大厂面试,要是没通过的话还会有记录,后面还想再去面就难咯。
更多大厂面试题: 字节跳动2面:为了性能,你会牺牲数据库三范式吗?
字节C++一面:enum和enum class的区别?
C/C++ 高频八股文面试题1000题(三)
直面米哈游C++面试:头文件“套娃”了咋整?
B站 C++ 一面:互斥锁和自旋锁的区别,使用场景分别是什么?
快手C++二面:static_assert底层原理解析
Q1自我介绍
面试官好,我是XXX,来自XX大学计算机专业大四学生。我有两年多的C++开发经验,熟悉C++11/14新特性,对底层原理有深入研究。在校期间,我主导开发了两个项目:一个基于倒排索引的搜索引擎和一个基于HTML5 Canvas的在线画图板。我热衷于解决技术难题,对性能优化有浓厚兴趣,希望能加入腾讯,与优秀的团队一起成长。
Q2你对虚函数是怎么理解的?
虚函数是C++实现多态的核心机制。当类中声明了虚函数,编译器会为该类生成虚函数表(vtable),其中存放了指向虚函数实现的指针。当通过基类指针或引用调用虚函数时,会根据对象的实际类型,在运行时动态绑定到正确的函数实现。
虚函数的实现原理是:每个包含虚函数的类对象都有一个隐藏的指针(vptr),指向该类的虚函数表。当调用虚函数时,通过vptr找到虚函数表,再找到对应的函数地址进行调用。
关键点:
- 实现运行时多态
- 需要基类指针或引用调用
- 不能是静态成员函数
- 析构函数最好声明为虚函数
Q3详细讲 static 关键字吧
static 在 C++ 中用法多样,核心是改变变量 / 函数的生命周期、作用域或链接属性:
静态局部变量:在函数内部声明的static变量,只初始化一次,生命周期贯穿整个程序运行期间。
void func() { static int count = 0; count++; std::cout << count << std::endl; }静态全局变量:在全局作用域声明的static变量,作用域限制在当前文件内,避免命名冲突。
静态成员变量:类中声明的static成员变量,属于类而非类的实例,所有实例共享。
class MyClass { public: static int count; }; int MyClass::count = 0;静态成员函数:类中声明的static成员函数,不能访问非静态成员,只能访问静态成员。
class MyClass { public: static void printCount() { std::cout << count << std::endl; } };静态类:在匿名命名空间中定义的类,作用域限制在当前文件内。
Q4引用和指针有什么区别?
引用更安全,适合作为函数参数 / 返回值(避免拷贝);指针更灵活,适合动态内存管理、链表等场景。
特性 | 引用 | 指针 |
初始化 | 必须初始化,不能重新绑定 | 可以不初始化,可以重新指向 |
空值 | 不能为NULL | 可以为NULL |
语法 | 无需解引用,直接使用 | 需要使用*和->操作符 |
内存 | 无独立内存地址 | 有自己的内存地址 |
传递 | 值传递的别名 | 地址传递 |
Q5new 和 malloc 有什么区别?
特性 | new | malloc |
类型 | C++操作符 | C标准库函数 |
初始化 | 调用构造函数 | 不初始化内存 |
返回类型 | 指定类型指针 | void*指针 |
错误处理 | 抛出std::bad_alloc异常 | 返回NULL |
内存释放 | delete | free |
Q6说说C++11 的新特性?右值引用的应用场景?
C++11新特性:
- auto类型推导
- lambda表达式
- 右值引用(移动语义)
- 智能指针(unique_ptr, shared_ptr)
- decltype
- 范围for循环
- 线程支持
右值引用应用场景:右值引用实现移动语义,避免不必要的拷贝,典型场景:
1、移动语义:当对象为右值(如临时对象)时,通过移动构造函数 / 移动赋值运算符 “窃取” 其资源(如堆内存),避免深拷贝,提升性能。
string a = "hello"; string b = move(a); // a的资源被转移到b,a变为空2、完美转发:在模板函数中,通过 std::forward 保持参数的左值 / 右值属性,避免不必要的拷贝。
template <typename T> void func(T&& arg) { other_func(std::forward<T>(arg)); // 保持arg的原始属性 }Q7C++是怎么做动态内存管理的?
C++ 动态内存管理核心是手动控制与自动管理结合:
手动管理:new/delete(单个对象)、new []/delete [](数组),需手动配对使用,否则可能内存泄漏或崩溃。
智能指针:推荐优先使用,自动管理内存生命周期:
- unique_ptr:独占所有权,适合管理单个对象,不可拷贝但可移动;
- shared_ptr:共享所有权,通过引用计数自动释放(计数为 0 时调用 delete);
- weak_ptr:不增加引用计数,用于观察 shared_ptr 管理的对象,避免循环引用。
内存池:针对频繁分配 / 释放小块内存的场景(如服务器高频请求),预先分配一大块内存,自行管理内存块分配与回收,减少系统调用和内存碎片。
其他工具:allocator(STL 内存分配器接口)、boost.pool(第三方内存池库)等。
Q8说一下 map 和 hashmap 的区别
map(C++ 标准库中通常为 std::map)和 hashmap(非标准,通常指 std::unordered_map)的核心差异在底层实现与性能特性:
维度 | std::map | std::unordered_map |
底层结构 | 红黑树(平衡二叉搜索树) | 哈希表(数组 + 链表 / 红黑树) |
有序性 | 键值自动排序(默认升序) | 无序 |
查找效率 | O (log n)(树高) | 平均 O (1),最坏 O (n)(哈希冲突严重时) |
插入 / 删除 | O (log n)(树旋转调整) | 平均 O (1),需处理哈希冲突 |
内存占用 | 较低(仅存储键值对和树结构) | 较高(哈希表需要预留空间,避免冲突) |
键类型要求 | 需支持 < 运算符(用于排序) | 需支持哈希函数(hash<Key>)和 == 运算符 |
需要有序遍历或频繁范围查询用 map;追求极致查找 / 插入性能且键可哈希用 unordered_map。
Q9讲一下堆和栈的区别吧
堆(Heap)和栈(Stack)是程序运行时的两种内存区域,差异体现在分配方式、用途、特性:
维度 | 栈(Stack) | 堆(Heap) |
分配方式 | 编译器自动分配 / 释放(函数调用时入栈,返回时出栈) | 程序员手动分配 / 释放(new/malloc,delete/free) |
大小限制 | 通常较小(默认几 MB,可通过编译器调整) | 较大(可达 GB 级,受系统内存限制) |
存储内容 | 函数参数、局部变量、返回地址等 | 动态分配的对象、数组等 |
生长方向 | 通常向下生长(高地址→低地址) | 通常向上生长(低地址→高地址) |
效率 | 高(直接通过栈指针偏移操作) | 低(需查找空闲内存块,可能有碎片) |
安全性 | 自动管理,不易泄漏但易栈溢出 | 需手动管理,易泄漏或 double free |
Q10time_wait 是发生在哪一端?
time_wait 是 TCP 四次挥手过程中的状态,发生在主动关闭连接的一端(通常是客户端)。
- 当主动关闭方发送 FIN 报文,收到被动关闭方的 FIN+ACK 后,会进入 time_wait 状态,等待 2MSL(报文最大生存时间,通常为 1-2 分钟)。
- 作用:确保被动关闭方收到最后的 ACK(避免 ACK 丢失导致被动方重发 FIN);防止已关闭的连接端口被复用后,接收旧连接的残留报文。
Q11如果很多连接都是处于 time_wait 状态,该怎么处理?
1、内核参数调整:
- sysctl -w net.ipv4.tcp_tw_reuse=1
- sysctl -w net.ipv4.tcp_tw_recycle=0# 已弃用
- sysctl -w net.ipv4.tcp_fin_timeout=30
2、优化程序:
- 避免频繁创建/关闭短连接
- 使用长连接(HTTP keep-alive)
- 减少客户端连接数
3、增加端口范围:
sysctl -w net.ipv4.ip_local_port_range="1024 65535"Q12select 和 epoll 的区别?
select 和 epoll 都是 Linux 下的 IO 多路复用机制,用于高效处理多文件描述符(FD)的 IO 事件,核心差异在性能与设计:
维度 | select | epoll |
FD 数量限制 | 有(默认 1024,受限于 FD_SETSIZE) | 无(仅受系统内存限制) |
效率 | O (n)(每次调用需轮询所有 FD) | O (1)(通过回调机制,仅处理就绪 FD) |
数据结构 | 基于位图(fd_set) | 基于红黑树(管理 FD)+ 就绪链表(存储就绪 FD) |
内存拷贝 | 每次调用需将 fd_set 从用户态拷贝到内核态 | 仅初始化时拷贝,后续通过共享内存访问 |
触发方式 | 仅水平触发(LT) | 支持水平触发(LT)和边缘触发(ET) |
总结:高并发场景下 epoll 性能远优于 select,是 Linux 高性能服务器的首选(如 Nginx、Redis)。
Q13讲一下 LT 和 ET 有什么区别?
- LT(Level Triggered):当文件描述符就绪时,epoll_wait会一直返回该事件,直到事件被处理。优点:编程简单(无需一次性处理完所有数据);缺点:可能导致重复通知,效率略低。
- ET(Edge Triggered):只在文件描述符状态变化时触发一次事件。优点:减少通知次数,效率更高; 缺点:编程复杂(需一次性处理完所有数据,否则可能遗漏事件),必须使用非阻塞 IO。
ET 模式需配合非阻塞 IO,适合高并发场景;LT 模式适合对编程复杂度敏感的场景。
Q14讲一下 get 和 post 的区别?
特性 | GET | POST |
数据位置 | URL查询字符串 | 请求体 |
数据长度 | 受URL长度限制(约2048字符) | 无限制 |
安全性 | 不安全(URL可见) | 相对安全(请求体不可见) |
幂等性 | 是 | 否 |
用途 | 获取数据 | 提交数据 |
Q15http2 和 http1 的区别?
二进制协议:HTTP/1.x 是文本协议(易读但解析效率低),HTTP/2 是二进制帧(Header Frame 和 Data Frame),解析更快且错误少。
多路复用:HTTP/1.x 中每个连接只能处理一个请求(需排队,即 “队头阻塞”);HTTP/2 允许一个连接同时处理多个请求(通过帧的 stream ID 区分),大幅提升并发效率。
头部压缩:通过 HPACK 算法压缩请求头(HTTP/1.x 头部重复传输且未压缩),减少带宽消耗。
服务器推送:服务器可主动向客户端推送关联资源(如 HTML 引用的 CSS/JS),无需客户端请求。
流量控制:每个 stream 可独立进行流量控制,避免单个请求占用过多资源。
Q16http 和 https 的区别?
区别:HTTP是明文传输,HTTPS通过SSL/TLS加密传输。
HTTPS优点:
- 数据加密,防止窃听
- 身份验证,确保连接到正确服务器
- 数据完整性,防止篡改
HTTPS缺点:
- 需要SSL/TLS证书,增加成本
- 加密解密增加CPU开销
- 首次连接需要握手,增加延迟
Q17什么时候要使用多线程编程?
IO 密集型任务:如网络请求、文件读写等(IO 操作时线程会阻塞,多线程可让 CPU 处理其他任务)。例如:服务器同时处理多个客户端的请求。
CPU 密集型任务:如大规模计算、数据处理等(多线程可利用多核 CPU 并行计算)。例如:图像渲染、矩阵运算。
响应性要求高的场景:如 GUI 程序(主线程处理界面交互,子线程处理耗时操作,避免界面卡顿)。
任务拆分场景:将复杂任务拆分为多个子任务,并行执行缩短总耗时。
Q18多线程编程里面,你怎么理解互斥锁和自旋锁?
互斥锁(Mutex)和自旋锁(Spinlock)都是用于保护临界区的同步机制,核心差异在阻塞策略:
- 互斥锁(Mutex):当锁被占用时,线程会进入睡眠状态,等待锁释放。适合长时间持有锁的场景。优点:不占用 CPU 资源,适合临界区执行时间较长的场景; 缺点:上下文切换开销大(阻塞→唤醒涉及内核态操作)。
- 自旋锁(Spinlock):当锁被占用时,线程会不断循环检查锁是否可用,不进入睡眠。适合锁持有时间很短的场景。优点:无上下文切换开销,适合临界区执行时间极短的场景; 缺点:自旋时占用 CPU,多核心场景下效率更高(单核心可能导致死锁)。
选择依据:
- 锁持有时间短 → 自旋锁
- 锁持有时间长 → 互斥锁
Q19用 mysql 吗?
是的,在项目中频繁使用 MySQL 作为关系型数据库,主要涉及:
- 基础操作:通过 C++ 的 MySQL Connector 或封装的 ORM 库进行 CRUD(增删改查)操作;
- 性能优化:设计合理的索引(如 B + 树索引)、优化 SQL 语句(避免 select *、冗余连接)、使用查询缓存(已在 8.0 移除,改为应用层缓存);
- 事务与隔离级别:利用 InnoDB 引擎的 ACID 特性,根据场景选择隔离级别(如读已提交避免脏读);
- 高可用:配合主从复制、读写分离提升并发能力,使用连接池(如 SQLiteConnectionPool)减少连接建立开销。
Q20影响web服务性能的因素
- 网络:带宽、延迟、网络拥塞
- 服务器硬件:CPU、内存、磁盘I/O
- 代码效率:算法复杂度、内存管理
- 数据库:查询优化、索引、连接池
- 并发模型:I/O多路复用、线程池
- 缓存:是否使用Redis等缓存
- HTTP/HTTPS:是否使用HTTP/2,SSL/TLS开销
Q21怎么提升服务器的并发性能?
- I/O多路复用:使用epoll、kqueue等
- 线程池:限制并发线程数
- 异步编程:使用libevent、boost.asio
- 缓存:使用Redis缓存热点数据
- 数据库优化:索引优化、连接池
- 负载均衡:多台服务器分担流量
Q22刚提到线程池,内部的线程一般是多少个?
线程池线程数量根据任务类型确定:
- CPU密集型:线程数 = CPU核心数 + 1
- IO密集型:线程数 = CPU核心数 × 2 + 1
例如,4核CPU:
- CPU密集型:5个线程
- IO密集型:9个线程
实际应用中需动态调整,例如:通过监控 CPU 利用率和任务队列长度,动态增删线程(弹性线程池)。常见经验值:8 核 CPU 处理 IO 密集型任务时,线程数可设为 16-40。
Q23倘若你用了线程池,但是高峰时期流量就是太高了,服务器承受不住,你该怎么办?
- 水平扩展:增加服务器数量,通过负载均衡分担流量
- 限流:在入口处设置限流(如令牌桶算法)
- 降级:关闭非核心功能,保证核心功能可用
- 异步队列:将请求放入消息队列(如Kafka、RabbitMQ)
Q24倘若就算这样还是不行,怎么办?
- 代码优化:分析性能瓶颈,优化关键路径
- 缓存:增加缓存层,减少数据库压力
- CDN:使用CDN分发静态资源
- 预热:提前加载热点数据,减少高峰期计算
Q25倘若你就只有一台服务器,没有更多的硬件资源了,怎么办?
- 优化代码:深入分析性能瓶颈
- 减少依赖:移除不必要的功能
- 调整系统参数:优化文件描述符、TCP参数
- 使用高效内存分配:如jemalloc替代malloc
- 数据压缩:减少网络传输量
Q26倘若你的操作无法用原子性,一个线程不断的生产任务,放到队列中,另一个从队列中拿取请求,如何做到无锁编程?
使用CAS(Compare-And-Swap)实现无锁队列:
template <typename T> class LockFreeQueue { private: struct Node { T data; std::atomic<Node*> next; Node(T d) : data(d), next(nullptr) {} }; std::atomic<Node*> head; std::atomic<Node*> tail; public: void push(T data) { Node* new_node = new Node(data); Node* old_tail = tail.load(); while (true) { Node* next = old_tail->next.load(); if (old_tail == tail.load()) { if (next == nullptr) { if (old_tail->next.compare_exchange_strong(next, new_node)) { tail.compare_exchange_strong(old_tail, new_node); return; } } else { tail.compare_exchange_strong(old_tail, next); } } } } bool pop(T& data) { Node* old_head = head.load(); while (true) { Node* next = old_head->next.load(); if (old_head == head.load()) { if (next == nullptr) { return false; } if (head.compare_exchange_strong(old_head, next)) { data = next->data; delete old_head; return true; } } } } };Q27平常有什么兴趣爱好?
编程方面平时喜欢深入研究 C++ 标准和 Linux 内核源码(如 STL 容器实现、epoll 原理),尝试用 C++ 实现一些基础组件(如线程池、内存池)。阅读技术博客(如 CppCon 演讲、Linux 内核月报),偶尔做一些算法题(LeetCode 中等难度)保持思维活跃度。生活方面,喜欢打篮球和跑步,保持身体健康。
Q28最近有看什么技术书籍吗?
最近在读《深入理解计算机系统》(CSAPP),这本书从底层到应用层详细讲解了计算机系统,对理解操作系统、编译器、网络等有帮助。也读了《C++并发编程实战》,对多线程编程有了更深的理解。
Q29你写的两个项目,搜索引擎和在线画图板,是你的课程设计吗?
不是课程设计,是自主开发的项目。搜索引擎主要用于学习倒排索引、分词算法和分布式爬虫;在线画图板则是为了实践实时数据同步(基于 WebSocket)和分布式锁(Redis 实现),两个项目均部署在云服务器上,可通过公网访问,源码已开源到 GitHub。
Q30你的 httpserver 是怎么搭建的?
基于 C++11 实现,核心架构如下:
网络层:
- 用 socket 创建 TCP 监听端口,通过 epoll 实现 IO 多路复用(LT 模式);
- 支持 TCP 长连接,通过心跳包检测连接状态。
协议层:
- 解析 HTTP 请求(method、uri、headers、body),支持 GET/POST 方法;
- 构建 HTTP 响应(状态码、响应头、响应体),支持 gzip 压缩。
并发处理:
- 主线程负责 accept 新连接和 IO 事件分发;
- 工作线程池(线程数 = CPU 核心数 * 2)处理请求逻辑(路由匹配、业务计算)。
路由与业务:
- 用 std::unordered_map 存储路由表(uri→处理函数);
- 支持静态资源(html/css/js)访问和动态接口(如 /search)。
扩展:
- 集成 OpenSSL 实现 HTTPS(配置证书和私钥);
- 加入简单的访问控制(IP 黑名单)和请求限流。
通过压测工具(如 wrk)优化,单服务器可支持每秒数千 QPS(取决于请求类型)。