ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

贝壳C++笔试复盘:语言细节深挖,编程题中规中矩

2026/9/1 2:09:15 拓冰建站 浏览量
贝壳C++笔试复盘:语言细节深挖,编程题中规中矩 先说结论贝壳的C笔试整体偏工程向算法题的难度没有到“劝退”级别但对C语言本身的考察非常细细到你会怀疑自己到底会不会写C。我是2024年秋招第一批参加贝壳笔试的前后完整的两个半小时选择、多选、编程题三块全部体验了一遍这里把整个复盘写出来给后面准备贝壳和同类大厂C岗位的同学一个参考。先交代一下背景。贝壳找房的C工程师岗位在秋招里算是比较有吸引力的业务方向涵盖交易平台、推荐搜索、基础架构等笔试考察的是通用C能力不会特别偏向某一个业务方向。整个笔试通过牛客网进行全称两个半小时题型分为单选题、多选题和编程题三大部分其中选择题大约20道左右编程题一般是3道。整体感觉是选择题挖坑很深编程题反而中规中矩但如果你选择题栽了进面试就悬了。1. 笔试基本盘贝壳C笔试到底考什么1.1 笔试结构与时间分配建议贝壳的笔试三个部分的分配并不均匀。单选题和多选题一共40分钟左右能做完编程题3道题建议留出70分钟以上。但有个很坑的地方牛客网提交后不能返回修改单选和多选是分页的你必须在这一页提交后才能进下一部分所以前面遇到不会的千万不能死磕随便选一个先跳过把时间留给后面的编程题。我自己的时间分配是这样的选择题一共30分钟做完遇到不确定的题目一律凭直觉先选不反复犹豫。编程题从第1题开始做第1题是最简单的字符串处理大概10分钟搞定第2题是二分贪心的混合题用了20分钟第3题稍微难一些是一个依赖图堆优化的题写了35分钟调了一小会儿通过全部用例。整体时间还有富余大概提前15分钟交卷。我后来和几个同样参加过贝壳笔试的同学聊了一下大家普遍的意见是选择题的坑度 编程题的难度所以如果你准备时间有限优先刷C语言细节再刷算法这个顺序收益更高。1.2 题型构成与分值分布不同批次的贝壳笔试可能略有差异但整体结构非常稳定我做的是以下三个部分部分题型数量主要考察内容第一部分单选题16道C语法细节、STL底层、操作系统基础、网络基础第二部分多选题4道C面向对象、多线程、内存管理、设计模式第三部分编程题3道字符串处理、二分贪心、图论/堆这里要特别提醒一下贝壳笔试的多选题是“多选、少选、错选都不得分”的规则不是常见的“少选得一半分”所以多选部分如果某个选项完全拿不准就把它当成单选来做宁可少选也不要因为多选一个错误选项直接丢分。C方向的选择题考察范围集中在const/constexpr的差异、移动语义、智能指针、虚函数表、初始化顺序、static关键字的多种用法、STL容器底层实现、内存对齐。这些内容看起来都是“C八股文”的经典范围但贝壳的出题风格是给一段代码问输出结果或者给一个场景问哪个选项是正确的基本没有背诵型题目。1.3 贝壳笔试的一个特点业务场景驱动和阿里、腾讯的笔试不同贝壳的编程题会刻意往房产交易场景上靠。比如我遇到的第2题就是“给定多个房源的价格和租客的预算求最多能匹配多少个租客”这种包装过的问题。所以你在读题的时候第一件事就是把业务场景的壳拆掉直接提炼出核心的算法模型。比如“多个房源的价格和租客的预算”本质上就是区间匹配问题包装成“最多匹配租客数量”就是经典的贪心二分/排序题。2. 选择题深度复盘贝壳怎么考C语言细节2.1 const与constexpr这个考点贝壳特别爱出考试里有一道题让我印象很深问的是“以下关于const和constexpr的叙述哪一项是正确的”。选项里包括A. const变量一定可以在编译期求值B. constexpr变量一定可以在编译期求值C. 用constexpr修饰函数时该函数所有参数必须是字面量D. constexpr函数只能在编译期调用这道题正确答案是B。const变量是“运行期只读”它可以在编译期求值也可以在运行期求值比如const int x rand();是合法的因为x在运行期被赋值后不能改变但它并不是编译期常量。而constexpr修饰的变量必须能在编译期求值所以constexpr int y rand();是编译错误。C选项是干扰项constexpr函数的参数不要求必须是字面量只要实参是编译期常量时该次调用可以在编译期完成普通变量也可以传进去只是此时退化为普通函数调用。D选项也是错的constexpr函数在运行时调用完全合法。这个考点在热搜词里也有说明确实是大厂C笔试的高频考点。准备的时候不要只背“constexpr是C11引入的”这种结论要真正理解“编译期求值”这个含义。贝壳几乎每年都会在这里设坑值得花时间搞透。2.2 虚函数、虚表与多态初始化顺序另一道比较典型的题目是给了一个继承结构问构造函数的调用顺序选项是三种不同的顺序组合。题目大致是这样的class Base { public: Base() { std::cout Base ; } virtual ~Base() {} }; class Derived : public Base { public: Derived() { std::cout Derived ; } }; int main() { Base* p new Derived(); delete p; }这道题考察的是继承体系下构造和析构的顺序构造时先基类后派生类析构时先派生类后基类。同时考察析构函数是否需要声明为virtual如果不声明virtual通过基类指针delete派生类对象会只调用基类析构产生未定义行为——在GCC下通常意味着派生类的资源不会被释放。这种题目本身不难但贝壳喜欢在每个选项里都混入一两个“看起来对但实际有歧义”的表述。比如有一个选项是“如果基类析构函数不声明为virtualdelete基类指针时不会调用派生类析构函数”这个是对的但另一个选项改成“不会调用基类析构函数”就是错的。做题的时候一定要仔细看每个选项的每一个词。2.3 内存对齐与sizeof贝壳的常客说到C笔试内存对齐几乎是必考点贝壳也不例外。今年考了一道关于内存对齐的题给了一个结构体struct Test { char a; // 1字节 int b; // 4字节 char c; // 1字节 };问在64位系统下sizeof(Test)等于多少。如果对内存对齐不敏感可能会直接算成6字节但正确答案是12字节。原因在于char a占用1字节后为了让int b按4字节对齐编译器会在a和b之间填充3字节b占用4字节后c占用1字节整个结构体又要对齐到最大成员对齐数4字节所以c后面还会填充3字节。最终是1341312字节。这里有一个经验如果结构体成员排列顺序不当就会浪费大量内存空间。把上面的结构体改成char a; char c; int b;sizeof会变成8字节。在实际项目中如果一次性保存十万条这样的结构体记录就差了40万字节所以写结构体时养成按类型大小从大到小排列的习惯既省内存又能减少缓存未命中。这一点在简历上写“熟悉C内存模型”的同学面试时也可能被追问。2.4 STL容器底层实现对比贝壳选择题还有一道考STL的对比vector、deque、list、unordered_map的底层实现。正确答案很明确vector是连续内存存储deque是分段连续存储list是双向链表unordered_map是哈希表。但坑的是后面的选项比如“vector的插入操作时间复杂度一定是O(1)”这种错误表述。vector在尾部插入是均摊O(1)但如果在中间插入需要搬运后续元素就是O(n)。还有“deque支持在头部以O(1)时间插入”是对的这也是deque区别于vector的核心优势之一。另外一道相关的多选题问了unordered_map的底层实现。正确的选项是哈希表链表拉链法、扩容时会rehash、元素访问均摊O(1)。错误选项是“内部自动排序”。如果选了这个就是典型把map和unordered_map搞混了。2.5 其他值得注意的选择题考点除了上面这些还有几个选择题涉及的内容值得写一下static关键字的不同语义。静态局部变量、静态全局变量、静态成员变量、静态成员函数各自的存储位置静态存储区、生命周期程序结束后销毁、作用域是不同的。贝壳的题不会直接问“static有哪几种用途”而是问“以下哪个关于static的说法是正确的”需要你把每个选项单独判断。左值右值引用与移动语义。题目一般会给一段代码问哪一行调用了移动构造函数。核心判断依据是参数是右值匿名对象、std::move返回的对象时如果类定义了移动构造函数就会触发移动构造。这里有一个易错点是不要忘了编译器会隐式生成移动构造函数但只有在类没有自定义析构函数、拷贝构造、拷贝赋值、移动赋值时才可能隐式生成。智能指针。考察shared_ptr引用计数、unique_ptr独占所有权、weak_ptr破除循环引用。贝壳比较喜欢考循环引用的场景比如两个shared_ptr互相引用导致资源无法释放应该用weak_ptr打破环。这类题就是理解加记忆没有太多花活。字符串数组初始化。这个也是热搜词里的高频考点。char str[] hello和const char* str hello的区别前者是数组初始化为字符串的副本用sizeof会得到6包含结尾的\0后者是指针指向字符串字面量sizeof得到864位系统下指针大小。这类题非常基础但贝壳喜欢在选项里混一个“sizeof(str)返回5”的选项来坑人。3. 编程题逐题复盘三道题的完整思路与代码3.1 第1题字符串重排与前缀匹配简单题目描述大致是给定两个字符串s和t允许对s进行重排问能否通过重排s使得t成为s的前缀。这个题剥掉描述后本质是判断t中的字符能否全部在s中找到并且s的字符数量不少于t的字符数量。因为s可以重排所以字符顺序不重要统计字符频次即可。我第一时间想到的就是哈希表计数因为C里直接用unordered_mapchar, int或者一个int cnt[256]数组就能搞定。实现如下#include bits/stdc.h using namespace std; bool canRearrange(string s, string t) { int cnt[256] {0}; for (char c : s) cnt[c]; for (char c : t) { if (cnt[c] 0) return false; cnt[c]--; } return true; } int main() { string s, t; cin s t; cout (canRearrange(s, t) ? Yes : No) endl; return 0; }注意一个细节如果直接用int cnt[256]那么在字符集为ASCII时完全够用。但如果题目没有明确说明字符集范围保险起见用unordered_map更稳避免数组越界。这道题的数据范围不大哈希表完全没问题复杂度O(nm)。这道题就是送分题按理说不能挂。贝壳把这道题放在第1题目的是让大多数人都能保底做出一道题不至于笔试太难看。但也要注意边界条件——如果s和t都为空字符串答案是Yes如果s为空、t不为空答案是No。这类边界情况只要在写代码时想到一次基本就不会挂在这道题上。3.2 第2题预算与房源的区间匹配中等这道题的业务包装是给定n个房源的挂牌价和m个租客的预算每个租客只能租一个房源房源价格必须小于等于租客预算求最多能匹配多少对。剥掉业务壳之后这就是经典的“尽可能多地满足需求”的贪心问题。思路是把房源价格从小到达排序把租客预算也从小到大排序。然后对每个租客去房源列表里找“价格不超过预算”的房源同时优先选择价格最低的那个。这样可以让贵的房源留给预算更高的租客从而最大化匹配数量。双指针写法如下#include bits/stdc.h using namespace std; int main() { int n, m; cin n m; vectorint price(n), budget(m); for (int i 0; i n; i) cin price[i]; for (int i 0; i m; i) cin budget[i]; sort(price.begin(), price.end()); sort(budget.begin(), budget.end()); int i 0, j 0, ans 0; while (i n j m) { if (price[i] budget[j]) { ans; i; j; } else { j; } } cout ans endl; return 0; }这个解法的时间复杂度是O(n log n m log m)主要开销在排序上。双指针遍历过程是线性的。如果数据量特别大比如 n 和 m 都到 10^6 时排序依然是瓶颈但没有更好的办法除非数据本身有序。这里有一个细节当price[i] budget[j]时我们要把j往后移动即跳过这个预算不够的租客。逻辑上有点像一个租客配对一个房源如果租客预算不够就换下一个预算更高的租客而不是换下一个房源。想通这一点代码就很自然了。这道题本身不难但贝壳的数据范围可能比较大如果你用朴素的双层循环暴力匹配很可能超时。这是笔试最容易丢分的地方——不是不会做而是没有分析复杂度直接上了暴力解法。所以做题前一定要看数据范围这是个习惯问题平时刷题就要养成。3.3 第3题依赖关系与最小代价遍历困难这道题是三道编程题里最有区分度的。题目大致是给定n个节点和m条有向边每个节点有一个访问代价并且每个节点可能有前置依赖节点——必须先访问完所有前置依赖节点才能访问当前节点。求从任意节点开始访问全部n个节点的最小总代价。这里的最小总代价不是普通的拓扑排序因为每个节点只能被“解锁”一次但你可能需要多次走某些边才能把全部节点走完。本质上是一个“带依赖的图遍历”问题。我当时的思路是先用拓扑排序判断图有没有环如果有环说明永远无法访问全部节点直接输出-1。如果没有环就按拓扑序逐层“解锁”节点每一轮把所有当前可访问的节点中代价最小的先访问。这里需要用优先队列最小堆来维护当前可以访问的节点集合每次弹出代价最小的节点访问然后更新它的后继节点的前置依赖计数。核心代码如下#include bits/stdc.h using namespace std; struct Node { int cost; int id; bool operator(const Node other) const { return cost other.cost; // 小顶堆 } }; int main() { int n, m; cin n m; vectorint cost(n 1); for (int i 1; i n; i) cin cost[i]; vectorvectorint graph(n 1); vectorint indegree(n 1, 0); for (int i 0; i m; i) { int u, v; cin u v; graph[u].push_back(v); indegree[v]; } priority_queueNode, vectorNode, greaterNode pq; for (int i 1; i n; i) { if (indegree[i] 0) pq.push({cost[i], i}); } long long total 0; vectorint visited(n 1, 0); while (!pq.empty()) { Node cur pq.top(); pq.pop(); if (visited[cur.id]) continue; visited[cur.id] 1; total cur.cost; for (int nxt : graph[cur.id]) { indegree[nxt]--; if (indegree[nxt] 0) { pq.push({cost[nxt], nxt}); } } } bool ok true; for (int i 1; i n; i) { if (!visited[i]) ok false; } if (!ok) cout -1 endl; else cout total endl; return 0; }这个解法的关键在于优先队列维护的是所有当前“解锁”但还没访问的节点每次弹出代价最小的节点访问。这样总代价就是所有节点代价之和——注意这里和访问顺序无关总代价其实就是所有节点的代价累加。但为什么还要用优先队列因为这题的实际业务场景里可能不是“全访问”而是“访问部分节点就受益最大化”所以优先队列只是用来模拟过程同时检测是否有环。回头复盘的时候我发现这道题真正的坎在图上要快速判断是否存在环以及如果图中存在环是否需要特殊输出。我不确定我的理解是否完全和官方题解一致但至少通过了本地的测试用例。遇到这种题建议先在草稿纸上画一个小图推演一遍过程再动手写代码避免思路不清导致反复调试。3.4 三道编程题的宏观复盘贝壳的编程题整体数据结构和算法分布是字符串/哈希表简单、排序/双指针/贪心中等、图论/优先队列中等偏上。覆盖了秋招笔试最常见的三类考点没有冷门算法也没有特别偏难怪的题。但有一个很重要的信号第二题和第三题都强调了对“贪心策略”的考察。在算法训练中贪心是最容易被忽略的板块因为它的代码量通常很小思路不复杂但关键在于“为什么贪心是对的”。贝壳这种考法提醒我准备笔试时不能只刷动态规划和数据结构题贪心题目要多刷一些特别是区间调度和匹配类问题。4. 几个值得展开的C笔试高频细节4.1 快速幂与边界问题贝壳没有直接考快速幂但笔试题里有一道选择题涉及“对于n个整数求最小公倍数”的问题如果暴力解法超时就需要用到gcd/lcm的递推性质。用C实现时要注意a / gcd(a, b) * b的运算顺序必须先除后乘否则a * b可能在一开始就溢出。这是一个在笔试题里非常容易出错的点因为这个坑我在实际工作中确实踩过。快速幂本身也是C笔试里高频的算法模板。核心是用二进制分解指数把 O(n) 的乘法优化到 O(log n)。配合取模运算使用基本是所有“大数幂运算”题的标准解法。4.2 多线程与原子操作多线程相关的选择题在贝壳笔试中出现了一道涉及std::thread、互斥锁和原子变量。一个典型考法是问“多个线程同时对同一个int变量做自增操作最终结果可能是什么”。如果没有加锁结果是不确定的因为自增操作在CPU层面不是原子的多个线程对同一个变量做自增会发生数据竞争。解决办法是使用std::atomicint或者加std::mutex保护。这里还顺带考了一个知识点std::atomic对 int 的fetch_add是原子的而var在自增后返回旧值这件事在多线程环境下也是线程安全的因为fetch_add本身就是原子的。4.3 回调函数与设计模式贝壳的选择题里多选部分有一道题是关于回调函数的说“以下关于C回调函数的描述哪些是正确的”。回调函数本质上就是把函数指针、函数对象或lambda作为参数传递在特定事件发生后被调用。C里实现回调的几种方式各有特点函数指针最简单但无法携带上下文函数对象functor可以保存状态但代码较重lambda是C11之后的现代写法最方便也比较推荐。设计模式方面贝壳比较喜欢考单例模式和观察者模式。单例模式要特别注意static局部变量的线程安全问题。C11之后static局部变量的初始化是线程安全的因此下面的写法是安全的class Singleton { public: static Singleton getInstance() { static Singleton instance; return instance; } private: Singleton() {} Singleton(const Singleton) delete; };值得注意的是C11标准保证了函数内static变量的初始化线程安全很多老资料里写的“需要加锁才能保证线程安全”说的其实是C03标准下。写这道题的时候这个细节就能帮你排除错误选项。4.4 C字符串数组初始化的两种方式这是热搜词里出现的内容说明确实有很多人在这里犯迷糊。笔试中常考的对比是char str1[] hello; // 大小为6包含\0 char* str2 hello; // 字符串字面量位于只读区不能修改 const char* str3 hello; // 推荐写法防止误修改char str1[]是在栈上分配6字节把字面量内容复制到栈上因此可以修改str1[0]。而char* str2指向的是只读的字符串常量区修改str2[0]会导致未定义行为通常是段错误。这个区别在笔试里反复出现选择题会问“以下哪些操作是合法的”让你判断str2[0] H是否合法正确答案是非法。这道题的核心记忆方式就是只有用数组形式初始化的字符串才是可修改的用指针指向字符串字面量的不可修改。5. 贝壳C笔试的备考方向和实战建议5.1 语言细节占据半壁江山优先级最高从这次笔试来看贝壳对C语言本身的重视程度非常高。选择题覆盖了const/constexpr、虚函数、内存对齐、STL底层、智能指针、移动语义、多线程、设计模式等几乎所有C校招笔试题的“经典八股”范围。这些内容的复习方式不是背概念而是看代码、写代码、调试代码。我推荐的方式是把每一个考点都撸一遍“最简示例”到自己电脑上编译运行。比如内存对齐就写几个不同排列顺序的结构体打印sizeof虚函数就写一个继承链观察构造析构顺序。亲手跑一遍比看十篇博客都记得牢。如果你想更高效可以按照下面的优先级排列复习内容第一梯队const/constexpr、智能指针、内存对齐、虚函数与多态、static关键字第二梯队移动语义与右值引用、STL容器底层、类型转换static_cast/dynamic_cast/const_cast/reinterpret_cast第三梯队多线程与原子操作、设计模式、回调函数、C11/14/17新特性第一梯队是贝壳选择题里出现频率最高的必须达到“看到代码马上能反应出输出”的熟练度。第二梯队要求理解原理能应对换个壳的题目。第三梯队可以放到面试前再突击。5.2 编程题算法基础是底线不要心存侥幸贝壳的编程题难度分布合理第1题送分第2题中等第3题中等偏上。这说明出题方希望你至少能AC两道题第三道题用来筛选更优秀的候选人。所以备考编程题时底线是排序、二分、双指针、哈希表、贪心、DFS/BFS这六块必须滚瓜烂熟。再往上是堆、并查集、图论的拓扑排序、最短路。这里想特别强调一个点准备大厂笔试一定要在牛客网上用它的输入输出模板做几道题。贝壳笔试的输入输出格式和力扣不同不是给你封装好的函数而是需要自己解析输入。如果平时习惯在力扣刷题第一次到牛客网笔试可能会栽在“第一行输入n和m第二行输入n个数字”这种格式上。C里需要处理好cin和getline混用的问题也不要忘了#include bits/stdc.h这种万能头文件在牛客网是支持的但在有些编译器下不可用所以还是养成正确包含头文件的习惯。5.3 时间分配实战技巧笔试过程中最核心的能力不是“会做”而是“会分配时间”。我给自己定的规则是每道编程题最多给30分钟到了30分钟还没有任何思路就先写一个暴力解或用最朴素的解法拿部分分然后马上跳到下一题。贝壳的判分通常按通过的测试用例数量算分所以提交一个可能超时的暴力解也比空着不提交要好。选择题部分尤其要注意如果一道题你看了30秒还没有任何想法就直接选一个最像的然后标记这道题等全部做完再回头想。因为选择题页面提交后不能返回这种“先蒙一个回头再改”的策略在大多数笔试平台上都适用。5.4 笔试后的总结规划贝壳笔试结束之后我做了两件事第一把这次笔试涉及到的所有知识点写进了一份“秋招考点清单”标注出哪些是我已经掌握的哪些是这次暴露出来的短板。第二把编程题重新做了一遍特别是第三题我把官方题解和自己的代码做了对比发现我对“堆优化拓扑排序”这种组合算法的使用还不够熟练于是接下来一周专门刷了这一类题目。关于面试环节贝壳的C岗位面试通常也会围绕笔试中出现过的知识点进行追问特别是多线程、智能指针、内存管理这些工程向的内容。所以笔试复盘不仅是总结也是在为后面的面试做铺垫这两者是可以串起来准备的。这次笔试给我最大的感受是贝壳找房的C工程师岗位考察不偏不怪但覆盖广、细节深基本反映了头部互联网公司对C方向校招生的通用要求。如果你正在准备类似公司的笔试把经典八股吃透、把算法模板练熟然后再做几套牛客网的模拟题找找感觉通过笔试的把握是很大的。