度小满 客户端开发-C++ 二面
1. 自我介绍
2. 如何理解面向对象编程,有过什么相关实践
答案:理解面向对象编程,核心不只是封装、继承、多态这些概念本身,更重要的是用它来做建模、隔离变化、降低耦合。在实际开发里,比较常见的做法有:
- 用封装隐藏内部实现细节,对外提供稳定接口
- 用抽象基类定义能力边界,不让调用方依赖具体实现
- 用多态替换大量条件分支,方便后续扩展
- 用 RAII 管理资源生命周期,减少泄漏和异常路径问题
实际做过的一类场景,是把不同来源的数据处理逻辑抽象成统一接口,对上层只暴露标准调用方式,底层根据实际类型走不同实现。这样后续接新的处理策略时,只需要新增类,不需要改原有调度逻辑。
代码:
#include <iostream>
#include <memory>
using namespace std;
class Handler {
public:
virtual ~Handler() = default;
virtual void process() = 0;
};
class ImageHandler : public Handler {
public:
void process() override {
cout << "process image" << endl;
}
};
class TextHandler : public Handler {
public:
void process() override {
cout << "process text" << endl;
}
};
void run(unique_ptr<Handler> h) {
h->process();
}
3. 使用 Linux 命令做过什么实践
答案:平时 Linux 命令主要是用在开发、调试和排查问题上。比较常用的场景有:
- 用
grep、awk、sed做日志过滤、字段提取和统计 - 用
top、ps、pidstat看进程 CPU、内存、线程占用 - 用
ss、netstat、lsof排查端口、连接和文件句柄问题 - 用
gdb、core 文件定位崩溃现场 - 用
strace看系统调用路径 - 用
perf查热点函数和性能瓶颈 - 用 shell 脚本做构建、部署和巡检
排查问题时我一般会先看日志和基础监控,再结合系统命令确认是 CPU、内存、IO、网络还是锁竞争方向的问题,最后再回到代码层定位。
代码:
# 查看进程连接 ss -antp | grep 8080 # 查看打开的文件句柄 lsof -p <pid> # 查看线程级 CPU 占用 top -H -p <pid> # 采样热点 perf top -p <pid>
4. 了解 STL 到什么程度,有过什么实践
答案:STL 平时用得比较多,不只是常见容器和算法,也会关注底层结构、复杂度、内存布局、迭代器失效和适用场景。平时使用时主要会考虑这些:
- 顺序存储场景优先考虑
vector - 需要快速哈希查找时考虑
unordered_map - 需要有序性和范围操作时考虑
map、set - 算法层面常用
sort、lower_bound、nth_element - 使用过程中会注意扩容、rehash、拷贝开销和对象移动成本
实际开发里,容器选型不会只看接口是否方便,也会结合访问模式、数据规模和修改频率来判断。
5. vector、deque、list 的底层差异是什么
答案:这三个容器最大的差异在内存布局上:
vector是连续内存,随机访问快,遍历性能好,扩容时可能整体搬迁deque是分段连续结构,头尾插入删除更灵活list是双向链表,节点离散分布,插入删除方便,但访存局部性差
如果从真实工程使用来看,vector 的使用频率通常是最高的,主要原因是:
- 连续内存对 cache 更友好
- 遍历速度通常更好
- 额外指针开销更小
- 大多数业务场景读多改少
所以即使某些操作在理论复杂度上 list 更优,实际运行效果也不一定更好,还是要结合访问模式来看。
6. unordered_map 的底层原理是什么?什么时候会退化?
答案:unordered_map 底层是哈希表,常见实现包括:
- 桶数组
- 哈希函数
- 冲突处理结构
- 装载因子和 rehash 机制
正常情况下,查找、插入、删除的平均复杂度是 O(1)。但在下面这些场景里会出现性能退化:
- 哈希函数分布不好,冲突严重
- key 分布不均匀,很多元素落到同一个桶
- 装载因子太高
- 频繁扩容和 rehash
比较常见的优化方式有:
- 提前
reserve - 控制
max_load_factor - 对复杂 key 设计更合适的哈希函数
- 在性能敏感路径避免频繁插入触发 rehash
代码:
#include <unordered_map>
#include <string>
using namespace std;
int main() {
unordered_map<string, int> mp;
mp.reserve(10000);
mp.max_load_factor(0.7f);
mp["jd"] = 1;
}
7. std::sort 为什么通常比手写快排更稳
答案:std::sort 一般不是单一快排实现,而是基于内省排序的思路,通常会结合:
- 快速排序
- 堆排序
- 插入排序
它的处理方式一般是:
- 大区间用快排,平均性能好
- 递归层数过深时切换堆排,避免最坏情况退化
- 小区间用插入排序,减少常数开销
所以它相比简单手写快排更稳,主要体现在:
- 最坏情况有兜底
- 小区间做了专门优化
- 标准库实现通常经过充分调优
如果要稳定排序,通常会用 stable_sort,底层更偏归并思想。
代码:
#include <algorithm>
#include <vector>
using namespace std;
int main() {
vector<int> nums = {5, 2, 4, 1, 3};
sort(nums.begin(), nums.end());
}
8. 项目拷打
9. 设计一个消息队列,要求不能插入已经存在的消息,并且按插入顺序获取消息
答案:这个需求有两个核心点:
- 去重
- 保序
只用队列可以保序,但没法高效判重;只用哈希表可以判重,但不能维护顺序。比较直接的实现方式是:
- 用队列维护插入顺序
- 用哈希集合维护消息是否已经存在
插入时先查集合,不存在才入队;取消息时从队头取,并且同步从集合里删除。
这种做法适合只需要从头取消息的场景。
代码:
#include <queue>
#include <unordered_set>
#include <string>
using namespace std;
class UniqueQueue {
public:
bool push(const string& msg) {
if (exists_.count(msg)) return false;
q_.push(msg);
exists_.insert(msg);
return true;
}
bool pop(string& out) {
if (q_.empty()) return false;
out = q_.front
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.
