搜狗2017校招C++工程师笔试试卷,这个话题放到现在来看,依然有一批人在讨论。原因其实很简单:搜狗的笔试风格在当年的互联网公司里属于相当有代表性的一类——题量大、时间紧、C++底层细节抠得深,还夹杂着大量考察“工程直觉”的题。你光背《C++ Primer》不一定能过,但如果你真的动手写过几个小项目、踩过内存问题的坑,这套卷子会让你答得非常舒服。所以哪怕你已经不打算投搜狗,拿这套题来检验自己的C++水平,也绝对值回票价。
我结合近些年网上流传的回忆版题目和同类大厂笔试的常考方向,把这套卷子拆解开,从考点结构、高频错题、代码模板到应试策略,完整梳理一遍。内容分成六块,前两块讲卷子本身和选择题里的雷区,中间几块重点讲手写代码和算法模板,最后给出一个可以直接照着做的复习清单。无论你是准备秋招的应届生,还是想查漏补缺的在职开发者,都可以拿这篇文章当一个自测索引。
1. 这套卷子的底牌:卷面结构、题量与隐藏筛选逻辑
1.1 卷面结构拆解与时间分配
搜狗2017校招C++笔试延续了当时的典型风格:选择题 + 编程题的组合,总时长在两个半小时左右。选择题覆盖C++语法、数据结构、操作系统、网络基础四块,大概有二十到三十道,每道题的分值不高,但胜在覆盖面极广。后面跟着两道到三道编程题,要求在白板式的在线编辑器里手写完整代码,不允许本地编译调试,时间压力非常大。
这个结构本身就在筛选人。选择题考察的是“你知不知道”,编程题考察的是“你能不能写出来”。前者可以用排除法蒙,后者完全靠硬功夫。很多同学死在时间分配上:前面选择题纠结太久,后面编程题只够写一题,甚至一题都跑不通。我的建议是,拿到卷子先花两分钟整体扫一遍题目分布,心里对编程题的题量有个数,选择题遇到卡壳超过两分钟的,先标记跳过,别恋战。
从网上回忆版的反馈来看,编程题基本都在LeetCode中等难度以内,但有一个微妙的地方:它要求你用纯C++实现,连STL的使用有时都会受限。比如有的题目明确要求“不能使用额外的容器”或“只能使用O(1)空间”,这就是在考察你愿不愿意老老实实操作指针和下标,而不是丢一个 map 过去糊弄。
1.2 搜狗笔试的隐藏筛选逻辑
搜狗的业务以搜索和输入法为主,背后牵扯到字符串处理、索引构建、并发调度、内存复用这些高密度场景,所以它的笔试题有一个隐藏偏好:所有考点都会向“字符串”“并发”“底层内存”三个方向倾斜。你在备考时如果时间有限,优先把这三大块吃透,通过率会高很多。
举个例子,操作系统层面它很少直接问“进程和线程的区别”这种八股,而是会问“一个多线程程序里,两个线程同时对一个int变量做++操作,最终结果是否一定是2?为什么?”这种题。表面考并发,实际上考你对CPU缓存、指令重排、原子性的理解深度。如果你只背了“多线程共享全局变量会有问题”这句话,答不出本质,分数就拿不全。
所以备考这套试卷,本质上是备考一场“C++工程实践能力的摸底考试”。它不像ACM那样追求极限算法,更看重你写出的代码是不是干净、健壮、能在生产环境里跑起来。有了这个认知,后面的复习方向就有谱了。
2. 选择题里的高频雷区:C++语法与内存布局
2.1 C++对象模型与虚函数考点
选择题里几乎必考虚函数,而且问法非常刁钻。常见的有这么几类:含虚函数的类,sizeof()是多少;虚函数表在内存里的什么位置;构造函数里调用虚函数会发生什么;析构函数要不要声明为virtual,为什么。
先说sizeof的问题。很多新手以为“有虚函数就是4字节”,因为有个虚表指针。但你得记住,指针大小是平台相关的,32位下是4字节,64位下是8字节。而且类里如果还有别的成员变量,还要考虑内存对齐。比如一个类有一个char和一个虚函数,64位下sizeof不是9,而是16,因为char后面要填充7个字节,让虚表指针对齐到8字节边界。
再说构造函数里调用虚函数的问题。答案是:不会触发多态。因为构造子类对象时,基类构造函数先执行,此时子类部分还没初始化,虚表指针指向的是基类的虚表,所以调用虚函数只会调用基类版本。这个考点在《Effective C++》里有专门的条款,但笔试时很多人凭直觉作答,掉进陷阱。
我在实际项目里被这个坑过一次。当时在基类构造函数里调了一个虚函数做日志初始化,结果子类的初始化逻辑根本没执行,排查了半天才发现是对象模型在作祟。从那以后我给自己定了个规矩:构造函数和析构函数里绝不调用虚函数,任何需要在构造阶段完成的逻辑,一律改成显式的init()方法。
2.2 STL容器选型与迭代器失效
STL相关的选择题,核心就两个字:失效。vector、deque、list、map这些容器,在插入、删除元素时,哪些迭代器会失效,哪些不会,是高频考点。
规则其实不复杂:
- vector:插入元素导致重新分配内存时,所有迭代器和引用全部失效;删除元素后,被删除位置之后的迭代器全部失效。
- deque:插入元素到首尾两端时,其他迭代器不受影响;插入到中间位置,所有迭代器失效。
- list:插入和删除都不会使其他迭代器失效,只是被删除元素的迭代器指向的内容没了。
- map/set:删除节点只影响被删除节点的迭代器,其他迭代器不受影响。
为什么搜狗爱考这个?因为搜索引擎的底层要高频处理文档列表、倒排索引,这些场景里几乎全是用vector加自建内存池来管理,如果你在遍历时乱删元素导致迭代器失效,线上就是崩溃。笔试里会问“下面这段代码有什么问题”:
for (auto it = vec.begin(); it != vec.end(); ++it) { if (*it % 2 == 0) { vec.erase(it); } }这题一眼就能看出问题:erase之后it已经失效,再执行++it就是未定义行为。正确写法是:
for (auto it = vec.begin(); it != vec.end();) { if (*it % 2 == 0) { it = vec.erase(it); } else { ++it; } }C++11之后可以用erase返回下一个有效迭代器这个特性,这个知识点虽然简单,但在面试手写代码时特别容易因为紧张写错。建议备考时把vector的删除、去重、合并这几类操作各写一遍,形成肌肉记忆。
2.3 static、const、constexpr等概念辨析
这一块属于经典八股,但搜狗的题目往往会把它们混合在一起,考察你对“声明”和“定义”、对“编译期”和“运行期”的理解。
static放在不同位置,含义完全不同:
- 修饰全局变量/函数:限制为文件内部可见,内部链接属性。
- 修饰局部变量:延长生命周期到程序结束,且只在首次执行时初始化。
- 修饰成员变量:所有对象共享一份,必须在类外单独定义(C++17之后inline static可以简化)。
- 修饰成员函数:不依赖对象实例,不能访问非static成员。
constexpr则是C++11引入的、C++14/17逐步放宽的“编译期求值”能力。很多人的困惑是constexpr和const到底什么区别。最简单的理解:const是“运行期只读”,constexpr是“编译期常量”。一个const变量的值可以在运行时才确定,而constexpr变量的值必须在编译期就能算出来。所以constexpr函数天然可以用于数组长度、模板参数等需要编译期常量的场景。
搜狗的笔试题里常出现这类判断:“以下哪种写法能通过编译”。你看多了就会发现,它真正想考的不仅是语法,而是你对C++各个版本演进的理解。C++11、C++14、C++17、C++20,每个版本都在修正上一版本的痛点,如果你能说出constexpr在C++14里放宽了“可以包含循环和局部变量”这个变化,答这些题就很有底气。
我在VSCode里配C++开发环境时,经常用C++17标准编译。如果你也是这个环境,记得在tasks.json里加上-std=c++17,不然默认的编译器标准可能比较老,跑constexpr相关代码会报莫名其妙的错误。这个小坑我帮好几个同事解决过。
3. 必背的编程题模板:算法题解与手写代码
3.1 字符串与数组:循环右移、字符串转数组、最小公倍数
搜狗的编程题,字符串是绝对主力。因为搜索、输入法、推荐——这些业务全是字符串驱动的。下面这几个题目在回忆版里反复出现,你最好能直接默写出来。
字符串循环右移是常客。比如“abcde”循环右移2位变成“deabc”。最经典的解法是三次反转:先把整体反转变成“edcba”,再把前k个反转变成“decba”,再把后n-k个反转变成“deabc”。时间复杂度O(n),空间O(1),代码极短:
void reverseStr(string& s, int l, int r) { while (l < r) swap(s[l++], s[r--]); } void rightRotate(string& s, int k) { int n = s.size(); if (n == 0) return; k %= n; if (k == 0) return; reverseStr(s, 0, n - 1); reverseStr(s, 0, k - 1); reverseStr(s, k, n - 1); }注意k要取模,否则当k大于n时会越界或者逻辑错误。这个边界条件我在笔试时吃过亏,第一次写没取模,结果用例只过了一半。
再比如字符串转数组。这里要分清楚是“每个字符转成int”还是“把整个数字字符串转成一个数”。如果是前者,直接减'0'即可;如果是后者,就要考虑正负号、溢出、非法字符判断。笔试时我建议写一个防御性强的版本,把所有异常情况都考虑进去,否则在线评测的隐藏用例会把你打回原形。
int strToInt(const string& str) { if (str.empty()) return 0; long long res = 0; int i = 0, sign = 1; while (str[i] == ' ') ++i; if (str[i] == '+' || str[i] == '-') { sign = (str[i] == '-') ? -1 : 1; ++i; } for (; i < str.size(); ++i) { if (str[i] < '0' || str[i] > '9') break; res = res * 10 + (str[i] - '0'); if (res * sign > INT_MAX) return INT_MAX; if (res * sign < INT_MIN) return INT_MIN; } return res * sign; }这类题还有一个变体:求n个整数的最小公倍数。考的是你知不知道“先算最大公约数再两两合并”这条路。两个数的最大公约数用辗转相除法,也就是欧几里得算法,然后最小公倍数就是两数乘积除以最大公约数。
int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } int lcm(int a, int b) { return a / gcd(a, b) * b; } int lcmOfArray(const vector<int>& nums) { int res = 1; for (int num : nums) { res = lcm(res, num); } return res; }注意先除后乘,避免a*b溢出。这个细节我在实际写代码时经常注意,笔试考场上如果你能写出这一步,评卷人会觉得你确实有工程经验。
3.2 链表与指针:单链表找环、链表反转的边界
链表题是C++笔试的另一座大山。原因很现实:手写链表能给足你操作指针的空间,同时极易出错,考察“代码精准度”。搜狗常考的链表题无非那么几道:反转链表、找中间节点、判断是否有环、找到环的入口、合并两个有序链表。
判断单链表是否有环,用快慢指针,一个走一步,一个走两步,如果相遇说明有环。但要找到环的入口,就需要一个数学推导:从链表头到入口距离记为a,入口到相遇点距离记为b,相遇点再回到入口距离记为c。快指针走的距离是慢指针的两倍,可以推出a = c + n圈。所以相遇后,一个指针从头走,一个指针从相遇点走,再次相遇的位置就是环口。
ListNode* detectCycle(ListNode* head) { ListNode* slow = head; ListNode* fast = head; while (fast != nullptr && fast->next != nullptr) { slow = slow->next; fast = fast->next->next; if (slow == fast) break; } if (fast == nullptr || fast->next == nullptr) return nullptr; slow = head; while (slow != fast) { slow = slow->next; fast = fast->next; } return slow; }这段代码很短,但每一步都有讲究。第二段循环里,如果链表无环,fast会提前遇到空指针,所以必须得在一开始就判断好。还有一个容易忽略的地方:快指针走两步这种写法,如果不判断next是不是空,直接fast->next->next,在链表末尾时会解引用空指针,导致运行时错误。笔试平台不会给你看变量值,只能靠你写代码时的谨慎来规避。这类问题在实际工程里也会遇到,比如内存池的空闲链表管理,链表结构虽然被封装起来了,但指针边界问题始终是悬在头顶的一把刀。
链表反转也是必背。我以前整理过一个通用写法,用三个指针prev、cur、next,每轮循环记录next再反转当前节点指向。写完之后一定要记得把原本头节点的next置空,否则链会出现自环,一旦跑起来就是死循环或者栈溢出。
ListNode* reverseList(ListNode* head) { ListNode* prev = nullptr; ListNode* cur = head; while (cur != nullptr) { ListNode* next = cur->next; cur->next = prev; prev = cur; cur = next; } return prev; }这道题还有一个高阶变体:按k个一组反转链表。这意味着你需要同时实现“找第k个节点”“反转局部链表”“拼接前后部分”三个动作,每一段都要极其小心,笔试时如果要写,建议先在草稿纸上画好指针变化图。我见过太多人在这题上翻车,本来思路是对的,就是指针边界没处理好,连样例都没跑通。
3.3 进阶算法:快速幂、单调栈、多线程安全计数器
再往下,就是搜狗笔试题里用来拉开差距的进阶题目。快速幂是其中一个典型代表,题面一般描述成“求x的n次方,n可以很大,要求O(log n)”。核心思路是把n拆成二进制,举个例子,求x的13次方,13的二进制是1101,所以x^13 = x^8 * x^4 * x。每次对指数移位,如果当前二进制位为1,就乘上当前的base。代码实现简洁且适合背诵:
long long fastPow(long long x, long long n, long long mod) { long long res = 1 % mod; x %= mod; while (n > 0) { if (n & 1) res = res * x % mod; x = x * x % mod; n >>= 1; } return res; }注意两点:一是n很大时要用long long,二是取模运算要尽早做,否则中间过程可能溢出。快速幂的计算过程本身就是一个典型的位运算练习,理解了它,二进制枚举和状态压缩相关的题也会顺手很多。
单调栈是很多同学容易忽略的考点,但它在“求下一个更大元素”“求柱状图最大矩形面积”这类题里几乎是标配解法。它的思路很朴素:维护一个栈,让栈内元素保持单调递增或递减,当新元素破坏了单调性,就弹出栈顶元素,同时栈顶元素的“答案”就是当前新元素。这个算法在事件驱动编程、广告投放的候选排序里也有点影子。
笔试里如果遇到单调栈的题,注意栈里存的是下标不是值。这一个小差别,对应了“我要知道当前元素和栈顶元素之间的距离”的需求。你不存下标,算面积或者距离就算不出来。这个细节新手很容易踩坑。
多线程安全计数器是我要特意提的一道题,它已经不属于纯算法的范畴,而是考察并发能力。搜狗常出这类题:要求写一个支持并发环境下线程安全的自增计数器。最简单的做法是加锁:
#include <mutex> class SafeCounter { public: int increment() { std::lock_guard<std::mutex> lock(mtx_); return ++val_; } int get() { std::lock_guard<std::mutex> lock(mtx_); return val_; } private: std::mutex mtx_; int val_ = 0; };但如果你能更进一步,用atomic实现无锁版本,加分项会非常明显:
#include <atomic> class SafeCounter { public: int increment() { return val_.fetch_add(1) + 1; } int get() { return val_.load(); } private: std::atomic<int> val_{0}; };为什么能这样改?因为fetch_add和load都是原子操作,不需要额外加锁。这个知识点涉及ABA问题、内存序、CAS操作,如果你能顺带说清楚“atomic用的是CAS指令,不是互斥锁”,面试官会高看你一眼。我当年在项目里维护过一个全局计数器,最开始用std::mutex,压测发现并发上不去,换atomic之后性能提升明显,这个经验在笔试里直接变成了一道题的答案。
4. 编程题实战:三道高频题的完整推导
4.1 题目:实现一个字符串循环右移函数
这是我在多个版本回忆帖里都看到过的题,考法挺直白:实现函数void rightRotate(std::string& s, int k),把字符串循环右移k位。题面允许你使用额外空间,但要求尽量O(1)空间。
第一种思路,利用std::string的substr拼接。缺点是使用了O(n)的额外空间,而且如果题目要求原字符串翻转,这种方式会被判超时或者内存超限。第二种思路就是我在前面写的三次反转法,空间O(1),时间O(n),是这道题的满分答案。第三个容易犯的错误:如果允许k是负数,表示左移方向相反,需要在代码开头额外判断。虽然大部分笔试用例不会卡这一点,但作为一个追求稳健的工程师,写防御性代码总没有坏处。
我还会习惯性在函数开头加上对空串的检查。很多线上笔试用例会用空字符串来测试边界条件,如果不处理,s.size()为0时取模会直接出错。这个看起来很小的细节,往往决定你是不是能一次通过全部用例。
4.2 题目:删除链表的倒数第N个节点
这道题在LeetCode上也是经典中等题,搜狗笔试出现频率同样不低。题面要求:一次遍历删除倒数第N个节点。既然是倒数,很多人下意识会想到先遍历一遍求出链表长度,再删除正数第len-N个节点。但题目要求一次遍历,所以需要用双指针:第一个指针先走N步,然后两个指针同步前进,当第一个指针走到链表末尾时,第二个指针正好指向要删除节点的前一个节点。
这里有一个经典陷阱:删除的是头节点。如果第一个指针走完N步后已经走到空指针,说明倒数第N个节点就是头节点,此时直接返回head->next就行。我写这道题的代码时,习惯用一个哑节点dummy指向head,这样统一处理删除头节点和删除中间节点,逻辑简洁很多,面试官看着也舒服。
ListNode* removeNthFromEnd(ListNode* head, int n) { ListNode dummy(0); dummy.next = head; ListNode* first = &dummy; ListNode* second = &dummy; for (int i = 0; i < n; ++i) { first = first->next; } while (first->next != nullptr) { first = first->next; second = second->next; } ListNode* toDelete = second->next; second->next = toDelete->next; delete toDelete; return dummy.next; }注意最后一个细节:返回的是dummy.next,而不是head。因为当删除的是头节点时,head已经被delete掉了,再返回它就是悬空指针。这个坑我当年在牛客网上刷题时踩过,那之后凡是涉及链表头节点可能变动的题目,我全部用哑节点方案。
4.3 题目:用生产者和消费者模型描述C++多线程编程
这题在搜狗笔试里出现得比较巧妙,可能不出成编程题,而是以一个问答题的形式出现:请使用C++11及以上标准实现一个多线程的生产者消费者队列,并说明你的方案如何避免竞态条件。
核心解法是互斥锁加条件变量。生产者往队列里push,消费者从队列里pop,当队列为空时消费者需要等待。C++11提供了std::condition_variable配合std::unique_lock,流程固定如下:
#include <queue> #include <mutex> #include <condition_variable> template<typename T> class BlockingQueue { public: void push(const T& item) { { std::lock_guard<std::mutex> lock(mtx_); queue_.push(item); } cv_.notify_one(); } T pop() { std::unique_lock<std::mutex> lock(mtx_); cv_.wait(lock, [this] { return !queue_.empty(); }); T item = queue_.front(); queue_.pop(); return item; } private: std::queue<T> queue_; std::mutex mtx_; std::condition_variable cv_; };有几个细节值得展开说。第一,void push里我用了两个代码块来限制锁的粒度,先把item压进队列就释放锁,再通知消费者。如果你把notify放在加锁区域内,消费者被唤醒后去抢锁,还是要等生产者解锁,等于白醒一次,白白增加上下文切换。第二,pop里用cv_.wait(lock, predicate)而不是裸wait(lock),是因为条件变量存在“虚假唤醒”的可能。你用predicate多检查一次队列是否真的非空,这个严谨性写上去,就是和普通写代码的分水岭。第三,析构函数里需要唤醒所有等待线程,否则程序退出时会有线程卡死。
这个方案在搜狗常见场景里有直接对应:输入法候选词的后台扩展、搜索日志的异步写入,都依赖这种线程安全队列来解耦生产端和消费端。笔试能写出线程安全队列,再往业务上扯一句应用场景,本身就是对“校招”这个身份的加分项。
5. 考试之外的硬功夫:环境、调试与设计模式
5.1 本地开发环境与编译选项
虽然笔试是在线上环境里进行,但你在本地准备的时候,一定要把开发环境调得跟线上接近,避免因为本地能编译、线上编译不了这种问题翻车。近几年的热搜词里,VSCode配置C/C++环境一直是高频话题,说明现在还是有很多人在这一步卡壳。
我用的是VSCode加MinGW-w64这套组合,重点在于三处配置:c_cpp_properties.json里设置编译器路径和C++标准,tasks.json里配置编译任务,launch.json里配置调试器。编译命令建议显式写成:
g++ -std=c++17 -O2 -Wall -Wextra main.cpp -o main-Wall和-Wextra会把潜在警告全列出来,平时写题可能觉得吵,但备考阶段开着比较安心。笔试时编译器可能没有任何警告提示,本地养成习惯后,代码质量会明显优于裸写。还有一个容易忽略的事:如果你的Windows机器装的是Visual C++ Redistributable系列运行库,那只是让编译后的exe能跑起来,和实际编译环境是两码事。很多人误以为自己装了运行库就能用g++编译,这是环境准备期最大的误区。
5.2 设计模式与回调函数
搜狗笔试对设计模式的考察不算深,但偶尔会出选择题。常考的是单例、工厂、观察者、策略这几种。我遇到过一个题目,问的是“在多线程环境下实现单例需要考虑什么”,答案牵扯到双检锁、静态局部变量初始化、原子指针。如果你能写出来静态局部变量版本的线程安全单例,那就很稳:
class Singleton { public: static Singleton& getInstance() { static Singleton instance; return instance; } Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; private: Singleton() {} };C++11标准规定函数内静态局部变量的初始化是线程安全的,所以这个写法既简洁又正确,比双检锁容易理解得多。我个人的建议是,设计模式的备考关键词是“名字+场景+代码骨架”,不需要过度背诵,但至少看到题目能反应过来它考的是哪种模式。
回调函数在笔试里也经常出现,本质就是函数指针、std::function或者lambda表达式。搜狗可能会让你写一个简单的回调示例,比如某个事件发生时调用一个外部传入的函数。这里要特别注意回调的生命周期问题:如果回调里捕获了this指针或者引用,一旦对象销毁后回调仍然被调用,就会产生悬空引用。笔试不会让你跑起来看崩溃,但面试官会在后续追问里深入这一点。
5.3 内存泄漏与智能指针
C++工程笔试的另一个隐藏考点是内存管理,尤其是你写完代码后,面试官一定会追问“你的代码有没有内存泄漏”。所以在练习阶段,凡是手动new出来的对象,都要想一想delete在哪里。笔试的时候建议优先使用智能指针,std::unique_ptr和std::shared_ptr在C++11之后已经成为标配。如果你写一个函数返回动态分配的对象,直接返回std::unique_ptr比裸指针更符合现代C++风格。
我见过一道回忆版的选择题,问“下面哪段代码会自动释放内存”,特意把裸指针、unique_ptr、shared_ptr混在一起。这个知识点本身不难,但它背后代表的安全编程习惯才是真正的考察目的。你要是能主动说出“C++的RAII机制让资源管理变得更安全”,这类题目就完全没法难倒你。
还有一点值得注意:搜狗每年校招的进程里,代码review和面试环节往往会围绕笔试代码展开。这意味着你写在试卷上的代码可能会被打印出来,面试官看着你的代码问你“这里为什么不用智能指针”。如果你在笔试时就把代码写得现代、规范,后面能省掉不少麻烦。
6. 应试策略与复盘清单
6.1 时间分配与做题顺序
结合回忆版的题量和难度,我给出一套亲测有效的时间分配策略:总时长150分钟,前60分钟解决全部选择题,剩下90分钟留编程题。选择题遇到完全不会的,直接蒙一个赶紧跳,别回头改。很多时候第一直觉是对的,越是反复纠结越容易改错。
编程题的做题顺序也有讲究。先写自己最有把握、代码量最小的题目,建立信心。第二写需要思考但思路清晰的题目,最后再啃最难的。如果时间剩得不多,哪怕只写出核心函数和关键逻辑,也把代码框架摆在编辑器里,别留空题。判卷系统给出的0分和部分分之间,差的往往只是你有没有写下那几句核心代码。
我考场上有一个习惯:拿到编程题先花五分钟在纸上列出输入输出、边界条件、特殊用例,然后再动键盘。这个过程能有效降低写代码时的返工率。实际执行下来,代码一次跑通的概率比直接上手高很多。
6.2 复盘清单与延伸追问
考完试当天不要对答案,但第二天一定要完整复盘。我的做法是,自己建一个表格,把每个错题归类到“语法”“算法”“系统设计”“并发”“网络”这几列里,看看自己的薄弱环节到底在哪。如果你选择题错得最多的是网络相关,那说明你复习的时候OS和网络的优先级可以提前;如果你反复死在一些C++语法细节上,那就可以《C++ Primer》的对应章节再刷一遍。
这里附上一个核心考点速查表,基本覆盖搜狗2017校招笔试的全部主线内容:
| 知识领域 | 高频考点 | 备考建议 |
|---|---|---|
| C++语言 | 虚函数、对象内存布局、static、constexpr、移动语义 | 认真读一遍C++ Primer对应章节,配合在线小题练习 |
| STL | vector扩容机制、迭代器失效、map底层红黑树 | 手写一个简易vector,加深理解 |
| 数据结构 | 链表反转、找环、栈、队列 | 刷LeetCode链表和栈的经典题 |
| 算法 | 二分、快速幂、单调栈、双指针、字符串匹配 | 每类总结一个模板,反复默写 |
| 操作系统 | 进程线程、死锁、内存碎片、缓存一致性 | 结合多线程编程实践来复习 |
| 并发 | 原子操作、互斥锁、条件变量、线程池 | 手写一个线程安全队列 |
| 网络 | TCP三次握手、HTTP状态码、DNS流程 | 画流程图辅助理解,配合抓包加深记忆 |
| 设计模式 | 单例、工厂、观察者、策略 | 每个模式写一个可运行的最小示例 |
| 工程能力 | 内存泄漏、智能指针、RAII、回调生命周期 | 定期写小项目,保持代码手感 |
6.3 推荐复习Roadmap:四周冲刺方案
如果你想系统性准备接下来十来天的校招笔试,我给一个简单的四周方案。第一周专攻C++语言基础和内存模型,第二周刷数据结构和算法,第三周集中解决操作系统和并发编程,第四周练习整套模拟卷并复盘错题。把每天拆成三个小时:一个小时看书补概念、一个半小时写代码、半个小时整理错题。
我个人觉得最关键的一点是:练代码必须动手敲,不能只看。看一百道题的题解不如自己手写十道。笔试考场上你会紧张、会手抖,只有形成条件反射的代码才可能稳定输出。我当年备考时用了很笨的办法,把高频题目的代码模板反复默写,直到不再需要思考就能准确写出来。
这套搜索、输入法背景下的C++笔试试卷,放到今天依然值得研究。它不偏不怪,考点全部落在真实的工程场景里,答完你会对自己“到底会不会写C++”有一个非常诚实的判断。备考之余,多写几个小项目,多踩几个内存和并发的坑,比疯狂刷题更能带给你底气。
最后分享一个我踩过几次坑之后养成的习惯:在线笔试开始前,无论平台提不提醒,先看一眼编译环境默认的C++标准。如果默认是C++14或者C++17,就可以放心用lambda、auto、智能指针;如果平台比较老,只支持C++11,那就要避免使用C++14之后才引入的语法特性。这个小细节,可能直接决定你某道题能不能通过编译。祝你笔试顺利,写出的代码一版通过。