度小满 客户端开发-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 命令主要是用在开发、调试和排查问题上。比较常用的场景有:

  • grepawksed 做日志过滤、字段提取和统计
  • toppspidstat 看进程 CPU、内存、线程占用
  • ssnetstatlsof 排查端口、连接和文件句柄问题
  • 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
  • 需要有序性和范围操作时考虑 mapset
  • 算法层面常用 sortlower_boundnth_element
  • 使用过程中会注意扩容、rehash、拷贝开销和对象移动成本

实际开发里,容器选型不会只看接口是否方便,也会结合访问模式、数据规模和修改频率来判断。

5. vectordequelist 的底层差异是什么

答案:这三个容器最大的差异在内存布局上:

  • 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++ 常考面试题总结 文章被收录于专栏

本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

全部评论

相关推荐

点赞 评论 收藏
分享
KKorz:是这样的,还会定期默写抽查
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

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