国创中心 C++开发 一面
1. 自我介绍
2. 实习经历
3. 项目拷打
非常非常细 我建议用AI 提前拷打一下
4. rebase 和 merge 的区别是什么,什么时候更适合用 rebase
答案:merge 是把两个分支的历史合并,会保留分叉结构,生成一个合并提交。rebase 是把当前分支的提交“摘下来”,重新接到目标分支最新提交后面,让历史看起来更线性。如果是个人开发分支在合并前整理提交历史,rebase 会更干净;如果是公共分支,尤其是多人已经基于它协作,一般不建议随意 rebase 改写历史。所以是否使用 rebase,核心看分支是否共享、是否允许改历史,以及团队规范。
5. 优先级队列和堆有什么区别
答案:堆是一种数据结构,优先级队列是一个抽象数据类型。优先级队列强调的是“每次能按优先级取出当前最重要的元素”,而堆只是实现这种能力最常见的一种底层结构。也就是说,优先级队列可以用堆实现,也可以用平衡树或其他结构实现,只不过从综合效率上看,堆通常更合适。这题本质上是在看你能不能区分“接口能力”和“底层实现”。
6. 手写过 Makefile 和 CMake 吗,它们的关系是什么
答案:Makefile 更像是直接描述“怎么编译、怎么链接、目标之间依赖是什么”的构建脚本。CMake 则更像一个更高层的构建系统生成器,你写 CMakeLists.txt 描述项目结构、源码、依赖和目标,CMake 再帮你生成 Makefile、Ninja 文件或者其他平台对应的工程文件。所以两者不是简单替代关系,而是一个偏底层执行规则,一个偏工程化组织和跨平台生成。小项目手写 Makefile 没问题,但项目一大,涉及多目录、第三方库、不同平台和不同编译器时,CMake 的可维护性会更好。
代码:
cmake_minimum_required(VERSION 3.16) project(demo) set(CMAKE_CXX_STANDARD 17) add_executable(demo main.cpp)
7. 右值引用和万能引用是一个东西吗
答案:不是完全一样。语法上看它们都可能写成 T&&,但语义不同。普通右值引用出现在具体类型上,比如 int&&、std::string&&,它只能绑定右值;而模板推导上下文里的 T&& 如果发生类型推导,就可能成为所谓的万能引用,既能绑定左值也能绑定右值。这题如果继续问,一般会延伸到引用折叠和 std::forward。
代码:
#include <iostream>
using namespace std;
void f(int&& x) {
cout << "rvalue ref: " << x << endl;
}
template<typename T>
void g(T&& x) {
cout << "universal reference\n";
}
int main() {
int a = 10;
f(20);
g(a);
g(30);
return 0;
}
8. 你在实习或团队开发中是怎么用 Git 协作的
答案:比较常见的协作流程是基于分支开发。个人会在功能分支上开发,开发完成后先自测,再同步主干最新代码,处理冲突后提交合并请求,走代码评审,评审通过后合入测试分支或主干。如果是线上问题修复,一般会单拉 hotfix 分支,修完后再选择性回合到主干和其他维护分支。如果想把这题答得更真实,可以补一句自己在协作里实际踩过的坑,比如 rebase 后提交历史变化导致推送被拒,或者冲突处理时把别人逻辑覆盖掉,后面怎么规避的。
9. git reflog 有什么用,为什么它经常能救命
答案:reflog 记录的是本地 HEAD 和分支引用移动的历史,不只是提交历史本身。很多时候你执行了 reset --hard、误删分支、错误 rebase,表面上看提交好像没了,但只要引用移动记录还在,就可能通过 reflog 找回原来的 commit。所以它经常在“我把代码搞丢了”这种场景下救命。很多人只会 log,但真正排查本地历史丢失时,reflog 往往更关键。
10. 优先级队列的底层数据结构是什么
答案:优先级队列底层最常见的是堆,通常是二叉堆。如果是最大优先级先出,就用大顶堆;如果是最小优先级先出,就用小顶堆。堆能够保证堆顶元素始终是当前最值,插入和删除堆顶的时间复杂度一般是 O(logn),取堆顶是 O(1)。标准库里的 priority_queue 默认也是基于堆实现,而不是有序数组或平衡树,因为它更适合“频繁取最值”的场景。
代码:
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() {
priority_queue<int> pq;
pq.push(3);
pq.push(10);
pq.push(5);
cout << pq.top() << endl; // 10
pq.pop();
cout << pq.top() << endl; // 5
return 0;
}
11. 为什么完美转发要用 std::forward,不能直接传吗
答案:因为函数参数一旦有了名字,在函数体里它就是左值。如果模板函数接收到的是右值,但你在内部直接把这个参数传给别的函数,它会按左值处理,右值属性就丢了。std::forward 的作用就是在模板推导场景下保留原始值类别,让左值继续以左值转发,右值继续以右值转发。这也是为什么完美转发通常和万能引用一起出现。
代码:
#include <iostream>
#include <utility>
using namespace std;
void process(int& x) { cout << "lvalue\n"; }
void process(int&& x) { cout << "rvalue\n"; }
template<typename T>
void wrapper(T&& x) {
process(std::forward<T>(x));
}
int main() {
int a = 1;
wrapper(a);
wrapper(2);
return 0;
}
12. C++11 右值和右值引用怎么理解,为什么要有右值引用
答案:右值一般可以理解成那些生命周期即将结束、不能稳定取地址复用的临时对象。右值引用用 T&&表示,它的出现主要是为了支持移动语义和完美转发。在没有右值引用之前,很多
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.