面灵AI→

国庆前的一些小厂面试题

时间
2026-10
来源
牛客网

《面试题目》

  1. 纯虚函数有什么作用?什么时候需要纯虚析构函数?
  2. C++ 中各类变量分别存放在哪个内存分区?
  3. 深拷贝和浅拷贝有什么区别?什么时候需要自己写拷贝构造函数?
  4. 缓存一致性协议、Store Buffer 和内存序分别解决什么问题?
  5. 项目里的分表用了哪个字段当分表键,是怎么考虑的?
  6. 队列出现积压时,怎么解决和优化?
  7. 栈和堆的访问速度一样吗?它们哪里不一样?
  8. 栈的内存是什么时候分配的?调用一个函数时会使用一个栈帧,需要每次都单独分配一次内存吗?
  9. 指针里存的值是虚拟地址还是物理地址?
  10. 寄存器、内存和磁盘的访问速度为什么差别这么大?
  11. 手撕:十进制转八进制。
  12. 手撕:实现一个 JSON 解析器。

《参考解析》

  1. 纯虚函数与虚析构:纯虚函数是把接口声明出来、实现留给派生类,含纯虚函数的类不能实例化。它和虚析构是两个独立问题:只要打算通过基类指针删除派生类对象,基类析构就必须是虚的,否则派生类部分不会被析构掉。纯虚析构要写成声明为 = 0 再单独给出函数体,因为派生类析构一定会调用基类析构,没有函数体会链接失败。
  2. 变量的内存分区:栈放局部变量和函数参数,由编译器自动分配回收;堆放 new / malloc 出来的对象,由程序自己管理;数据段放已初始化的全局变量与静态变量,BSS 放未初始化或零初始化的那些;常量区放字符串字面量和常量,代码段放机器码。面试常追问的点是「字符串字面量在常量区、而指向它的指针变量在栈上」,以及静态局部变量虽然作用域在函数内,存储却和全局变量在同一片区域、生命周期到进程结束。
  3. 深浅拷贝:默认拷贝构造逐成员复制,成员里有裸指针时两个对象会指向同一块内存,一方析构后另一方就成了悬垂指针,这时必须自己写拷贝构造做深拷贝。工程上更常见的做法是不自己写,而是用智能指针表达所有权或直接 = delete 禁用拷贝,让语义显式化,代价是对象不再能值传递。
  4. 缓存一致性与内存序:多核下每个核有私有 cache 和 store buffer,MESI 这类一致性协议保证同一个地址最终可见,但写先进 store buffer 会让别的核暂时看不到,于是需要屏障或 acquire/release 语义来约束重排序。取舍很清楚:顺序一致模型最好推理但限制编译器与 CPU 优化,弱序模型(ARM、RISC-V)性能好,代价是必须给每个原子操作配正确的内存序,写错就是极难复现的偶发问题。
  5. 分表键的选择:判据是三条——查询条件里高频出现、取值离散度足够高、数据能均匀落到各分片。订单表按 user_id 分能覆盖「查我的订单」,但按订单号查就要走基因法把用户维度编进单号,或者另建一张映射表;按时间区间分利于归档,却容易把新数据全压在一个分片上形成热点。扩容方式也要一起想:取模哈希扩容要搬数据,一致性哈希或者提前分好较多的逻辑分片能缓解。
  6. 栈帧是怎么分配的:栈在线程创建时就预留了一段地址空间,函数调用只是把栈指针往下移动预留出栈帧,并不是每次都向内核申请内存,只有访问到还没映射的页才会触发缺页。所以「分配」本身几乎免费,真正的开销在于页的换入换出和 cache 行为,递归太深会爆栈也是这个原因。
  7. 指针里存的是虚拟地址:带 MMU 的机器上进程看到的都是虚拟地址,地址翻译由 MMU 在访存时完成,物理地址只存在页表项里;裸机或关掉 MMU 的场景两者才等价。追问一般会落到 TLB 和多级页表,用来解释「每次访存都要翻译」这件事为什么没有想象中那么贵。
  8. 实现一个 JSON 解析器(手撕):标准写法是递归下降——先做词法切分,把字符串、数字、true / false / null、括号、逗号和冒号分出来,再用 parseValue 按首字符分派到 object / array / string / number 分支,object 与 array 内部递归下去。要额外处理转义序列与 Unicode 代理对、数字精度、以及非法输入要能报出位置;深嵌套的大文件会把递归爆栈,可以改成显式栈的迭代写法。