
目录前言面试官真正在考什么一、手写代码算法基本功1.1 非递归二分查找1.2 从数组中找出第二大的数二、C/C 语言核心2.1 struct 与 class 的区别2.2 C 中 static 的作用2.3 C 中 extern C 的作用2.4 多态的实现机制2.5 C/C 程序的内存布局2.6 宏由数组名求长度三、Linux 系统与 IO3.1 僵尸进程如何产生3.2 如何避免僵尸进程3.3 文本去重sort / uniq / awk / sed3.4 epoll作用、优势与创建参数四、架构题海量用户并发登录要考虑什么4.1 传输协议选择4.2 负载均衡与分片4.3 线程与进程模型五、总结前言面试官真正在考什么腾讯这类后台 / C 岗的面试常见节奏是(1). 先手写一段代码如二分、链表、快排、atoi 等看编码习惯与边界意识(2). 再挖语言与系统细节,如static、虚表、内存布局、epoll、进程模型(3). 最后抬到架构层最常见的如协议选择、负载均衡、线程模型。下面按这个认知顺序重组原题每一题给出推荐答法 追问点 常见坑 。一、手写代码算法基本功1.1 非递归二分查找有序数组上的二分查找是面试热身题。重点不在背模板而在循环条件、区间收缩、溢出与失败返回值。非递归二分查找示意图 1每次比较array[mid]与目标值将搜索区间收缩到一半直至找到或区间为空。// 数组递增有序找到返回下标失败返回 -1 int binarySearch(const int* array, int len, int value) { if (array nullptr || len 0) { return -1; } int low 0; int high len - 1; while (low high) { // 避免 (low high) 溢出 int mid low (high - low) / 2; if (array[mid] value) { return mid; } if (value array[mid]) { low mid 1; // 右半区 } else { high mid - 1; // 左半区 } } return -1; }面试加分点同类常考手写快排、链表反转、atoi / 字符串转整数。1.2 从数组中找出第二大的数次优思路堆排序建大顶堆后做两次取堆顶时间复杂度 \(O(n\log n)\)。能写出堆调整说明功底扎实????不是本题期望答案。面试官通常会追问能否 \(O(n)\)堆相关下标完全二叉树、数组下标从 0 起左孩子2 * i 1右孩子2 * i 2最后一个非叶子节点len / 2 - 1推荐答案一遍扫描维护最大值与次大值核心是以两个变量换掉第二次全表扫描真正做到 \(O(n)\)、额外空间 \(O(1)\)。// 返回第二大的数无法求出时返回 INT_MIN也可约定返回 -1需与面试官对齐 #include climits int getSecondMax(const int* array, int len) { if (array nullptr || len 2) { return INT_MIN; } int maxVal array[0]; int second INT_MIN; bool hasSecond false; for (int i 1; i len; i) { if (array[i] maxVal) { second maxVal; maxVal array[i]; hasSecond true; } else if (array[i] maxVal) { // 严格小于最大值才可能成为「第二大」 if (!hasSecond || array[i] second) { second array[i]; hasSecond true; } } // array[i] maxVal跳过避免「第二大」等于最大值视题目是否允许重复 } return hasSecond ? second : INT_MIN; }和「冒泡 / 选择排两遍」的对比冒泡或简单选择各扫一遍拿到最大、次大本质是 \(O(2n)\)常数更大且不满足「只要 \(O(n)\)」的表述偏好一遍维护 max / second比较次数约 \(n\) 量级才是标准答法。必须主动提的边界(1). len 2(2). 全部元素相等 → 没有严格意义上的第二大(3). 负数、重复值、最大值出现多次。二、C/C 语言核心2.1 struct 与 class 的区别在 C 里二者几乎等价默认访问权限与默认继承权限不同补充两点常被追问(1). 模板类型参数写法是 templatetypename T 或 templateclass T不能写成 templatestruct T(2). 习惯上struct 多表示「纯数据聚合」class 多表示「有不变式与封装的类型」——这是风格约定不是语言强制。2.2 C 中 static 的作用分场景记比背定义更稳(1) 修饰局部变量存储期静态存储期生命周期贯穿整个程序作用域仍只在定义它的函数 / 块内可见只初始化一次。(2) 修饰全局变量 / 函数C 与 C 文件作用域给予内部链接仅当前翻译单元可见避免与其他 .c/.cpp 符号冲突修饰函数同理限制作用域为当前文件。C11 起还有「静态局部变量的线程安全初始化」等细节资深岗可能追问。2.3 C 中 extern C 的作用告诉编译器按 C 的规则做名字修饰name mangling与链接。原因C 支持函数重载符号名会编码参数类型C 不支持重载符号更「直白」。例如 void foo(int, int)C 侧常见类似 _fooC 侧可能变成 _foo_int_int 一类 mangled name具体格式与编译器相关。典型用途#ifdef __cplusplus extern C { #endif void c_api_init(void); #ifdef __cplusplus } #endif用于C 调用 C 库、导出给 C 调用的接口、动态库稳定 ABI。2.4 多态的实现机制一句话动态多态靠虚函数表vtable 虚表指针vptr完成运行时绑定。C 虚函数表与动态绑定图 2对象内嵌vptr指向所属类的虚表通过基类指针调用虚函数时按对象真实类型跳转到对应实现。口述框架建议(1). 类若声明虚函数编译器为该类生成虚表表项是函数地址(2). 每个对象多一个隐藏的 vptr构造时指向正确虚表(3). Base* p new Derived; 调用 p-foo() 时查 p 所指对象的虚表执行 Derived::foo(4). 析构函数通常也应为虚函数否则经基类指针 delete 会只析构基类部分。可延伸多重继承下的多张虚表、虚继承与 vbptr、纯虚函数与抽象类、override / final。2.5 C/C 程序的内存布局进程地址空间用户态常见分层如下地址由低到高程序内存布局示意图 3典型用户态布局——代码与静态数据在低址侧堆向上增长栈向下增长中间为可映射区域。常考追问栈溢出、堆碎片、new 失败策略、智能指针与 RAII、内存泄漏排查。2.6 宏由数组名求长度#define ARRAY_LEN(array) (sizeof(array) / sizeof((array)[0]))原理sizeof(array) 为整段字节数sizeof(array[0]) 为单元素字节数二者相除得元素个数。坑 该宏只对真正的数组名有效。一旦数组退化为指针函数参数传递sizeof(array) 变成指针大小结果错误。C 更推荐template typename T, std::size_t N constexpr std::size_t arrayLen(const T ()[N]) noexcept { return N; }三、Linux 系统与 IO3.1 僵尸进程如何产生在 UNIX / Linux 中子进程已退出但父进程尚未 wait / waitpid 回收其退出状态时该子进程进入僵尸态Z。内核仍保留最小进程表项PID、退出码等等待父进程读取。僵尸进程产生与回收图 4子进程退出后若无人 wait会残留为僵尸父进程回收或由 init 接管后才能彻底释放。3.2 如何避免僵尸进程3.3 文本去重sort / uniq / awk / sed题目删除文本文件中的重复行。# 方法一排序并去重输出有序 sort -u file # 方法二先排序再 uniq可配合 -k/-t 按字段 sort file | uniq # 方法三sort awk相邻去重 sort file | awk { if ($0 ! line) print; line $0 } # 方法四sort sed模式空间两行比较 sort file | sed $!N; /^\(.*\)\n\1$/!P; D说明uniq 只去掉相邻重复所以通常要先 sort若要求「保序去重、不排序」可用 awk !seen[$0] 。sed 版简要拆解N 读入下一行拼进模式空间正则匹配两行相同!P 表示不匹配时打印第一行D 删到换行并重新循环——保证模式空间里始终是「当前待比较的两行」。3.4 epoll作用、优势与创建参数epoll 是 Linux 上对大批量 fd 做 IO 多路复用的机制是 select / poll 的增强版大量连接、少量活跃时CPU 利用率通常明显更好。epoll 与 select 对比图 5select 侧多在用户态/内核间反复拷贝并线性扫描epoll 维护兴趣列表与就绪队列取事件更接近只处理就绪者。与 select 的核心差异创建接口int epoll_create(int size); // Linux 2.6.8 后 size 被忽略但仍需 0 int epoll_create1(int flags); // 推荐flags 可为 0 或 EPOLL_CLOEXEC 等注意创建出的 epfd 本身也占一个 fd用完必须 closeEPOLL_CLOEXECexec 时自动关闭避免 fd 泄漏给新镜像旧资料里 EPOLL_NONBLOCK 用于 epoll_create1 的说法不准确——非阻塞通常设在被监听的业务 fd 上ET 模式下尤其常见。ET 使用口诀配合非阻塞 IO一次事件尽量读写到 EAGAIN否则可能「漏事件」。四、架构题海量用户并发登录要考虑什么题目往往开放若你是架构师如何支撑 QQ 量级用户并发登录不必复述产品内幕按协议 → 接入 → 状态 → 扩展 → 线程模型分层答即可。海量登录并发架构示意图 6客户端经负载均衡进入鉴权网关账号按分片落到后端服务长连接保活与登录短连接可分层设计。4.1 传输协议选择口述时可强调可靠性既可以在传输层TCP也可以在应用层补齐UDP 之上自建可靠语义。4.2 负载均衡与分片短时海量登录冲击的是鉴权与账号存储(1). 接入层L4/L7 负载均衡无状态网关水平扩展(2). 账号分片按 QQ 号哈希或尾号分片 把热点打散到多组服务与库(3). 缓存热点票据、风控规则放 Redis 等减轻 DB(4). 限流与降级验证码、排队、只读降级防止雪崩。4.3 线程与进程模型服务端处理连接现代常见是多进程 / 多线程 IO 多路复用epoll一个线程处理大量连接事件CPU 密集加解密、风控再丢到线程池进程适合做隔离与优雅重启线程适合共享缓存与更轻的上下文切换。一律用线程服务每个登录过于绝对更完整的答法是事件驱动处理连接工作线程池处理业务并说明共享数据需要锁或无锁结构。五、总结以上腾讯题覆盖了后台 C 面试的典型剖面能写对代码、能讲清机制、能画对架构图。复习时不要只背答案——对每一题准备一个追问应答复杂度能否再降有没有边界线上如何观测与降级把这三问答顺现场发挥会稳很多。