ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

C++笔试题复盘:从虚函数到快速幂的核心考点拆解

C++笔试题复盘:从虚函数到快速幂的核心考点拆解 前阵子整理移动硬盘翻出2017年秋招期间的一份笔试题是美图当年C开发工程师的校招试卷。那年互联网公司招生还没现在这么卷但美图这种做影像工具类App的技术栈里C占了很大比例笔试题目出得相当有代表性基本把C求职者的基本功、算法底子和工程意识都覆盖到了。我后来拿这份卷子跟不少公司的C校招题做过对比发现它的考察逻辑很能说明问题——不是死抠语言冷门语法而是看你能不能把语言、算法、操作系统这些知识串起来解决实际问题。这篇东西适合正在准备C校招或社招笔试的人看也适合那些自认为C基础不错、但想系统自查一遍的人。我不打算把原题原样贴出来然后给标准答案那份试卷网上还能搜到但题目本身并不重要而是把这套试卷背后真正想考的东西拆开讲清楚每个考点配当时的答题思路、正确的解题姿势以及我踩过的坑。看完之后你再去刷任何公司的C笔试题应该都能一眼看穿出题人想考什么。1. 笔试的整体结构与考察思路1.1 从题型分布反推美图的岗位要求美图2017年这份C笔试试卷当时我印象最深的不是某道题特别难而是它的题型配比很“互联网公司”——不是纯算法题海也不是纯语言理论而是三者混搭。整体上看分为三块客观选择题大概20到25道、简答/设计题三到五道、在线编程题两道到三题。选择题覆盖的全是C语言本身的硬知识点const关键字的各种用法、指针和引用的区别、构造函数与析构函数的调用顺序、虚函数机制、类型转换方式、内存分配相关等。这些题目并不偏但每道题都有“陷阱选项”——不是让你背结论而是要看你是不是真懂底层原理。比如常见的一道题在继承体系中构造和析构的调用顺序是什么很多靠背题的人会记得“先构造基类再构造派生类”但把“先析构派生类再析构基类”写反或者在虚继承场景下就蒙了。这类题的价值在于能写对的人大概率真的调试过复杂继承链而不是只看过书。简答题更像是考查工程理解力。我记得有一道让阐述“为什么C需要智能指针能不能用裸指针完全替代”表面上是问RAII实际上是在考察你写代码时有没有内存安全和异常安全的概念。另一道是让写出一个线程安全的单例模式并说明推荐的实现方式的理由。这两个题放到今天依然是C面试的高频题因为几乎所有C服务端岗位都躲不开这两件事资源管理和并发控制。在线编程题才是拉开差距的地方。一般有一道算法题、一道偏应用的题目。算法题那年在很多公司都出现了快速幂相关变体美图这份卷子也类似。第二题通常偏向字符串处理或链表操作比如“反转字符串中的单词顺序”或“合并两个有序链表”难度不高但要求一次编译通过、边界处理好否则测试用例过不了。1.2 这套出题逻辑想筛选什么样的人我复盘完这套卷子最大的感受是美图的出题人想招的不是“知道很多C名词”的人而是“真写过不少C、遇到问题能自己查原因”的人。选择题考察语言细节是否扎实简答题考察有没有工程素养编程题考察动手能不能把想法转成无bug的代码。三者缺一不可。2017年正是移动端影像技术快速发展的阶段美图的App核心底层涉及大量图像处理、编解码、跨平台引擎开发C作为高性能底层语言岗位责任就是保证性能、保证稳定性、解决内存问题。所以那些看似零散的选择题实际上都在为这些真实工作场景埋伏笔。你想想如果一个人连虚函数表的基本机制都解释不清楚那他真去维护一个基于多态的渲染引擎时肯定寸步难行。另外这份试卷还有一个隐藏考点时间管理。题量不小选择题部分又纠结细节很多人做到算法题时只剩十几分钟。我交卷时旁边就有人选择题做得特别仔细导致最后编程题完全没写。这个情况在真实考试里无数次上演所以我对准备C笔试的朋友始终一个建议先扫全卷按分值分配时间编程题永远要留足40分钟以上。2. C语言基础考点逐题拆解2.1 字符串数组初始化与constexpr边界条件知识点里高频到绕不开的两个小点char数组的初始化和constexpr约束当年试卷上都有。先说字符串数组初始化别看题目简单坑是真不少。C里字符串初始化有几种写法// 方式一字符数组 字符串字面量 char str1[] hello; // 方式二char指针指向字符串字面量 const char* str2 hello; // 方式三标准库string std::string str3 hello;sizeof和strlen的结果这是选择题经典里的经典。对于char str1[] hello;sizeof(str1)是6因为末尾还有一个隐藏的\0strlen(str1)是5。但换成const char* str2 hello;sizeof(str2)在64位系统上是8指针大小strlen(str2)才是5。当年这张卷子就在这里挖了个坑选项里混着4、5、6、8四个数字只要没亲手打印过很容易凭感觉选错。再说constexpr这是考察“知不知道C版本演进”的题。constexpr是C11引入的关键字到了C14放宽了函数体限制C17又支持了if constexpr编译期分支C20才支持constexpr virtual等更复杂的特性。试卷当时不会考到20标准毕竟2017年C17才刚刚发布但常考的是constexpr和const有什么本质区别简单记const承诺“这个变量在作用域内不变”但它可以是运行期才知道的值constexpr则强制“必须在编译期就能算出值”。所以下面这段代码int n; cin n; const int a n; // 合法 // constexpr int b n; // 编译错误n不是常量表达式为什么需要constexpr因为某些场景必须在编译期就确定值数组大小、模板非类型参数、switch的case标签等。C11之前的做法是用宏或enum hackconstexpr就是给C一个类型安全的编译期常量机制。现在很多年轻人写代码不知道这个区别一问就说“constexpr就是更强的const”但那不够准确严格来说它们是不同维度的概念——一个管“编译期可求值”一个管“运行期不可变”。从这里引出的另一道高频选择题是数组大小能不能用变量int n 10; int arr[n];这种写法在标准C里是非法的VLA是C99的特性C标准从未正式采纳但很多编译器比如GCC做了扩展支持。如果你目标平台是MSVC这段代码会直接报错。这个知识点在当年的笔试里也有类似变体属于“看似写过但又没完全写对”的典型考点。2.2 虚函数、多态与内存布局的底层细节笔试选择题里最绕不开的肯定是虚函数这几乎是C岗位的“见面礼”。面试官和试卷都爱问含有虚函数的类对象内存长什么样这题的关键是理解虚函数表vtable和虚指针vptr。我一般建议用“班级点名册”来理解每个含有虚函数的类编译器都会偷偷给类对象加一个指针成员这个指针指向一张函数地址表。当调用虚函数时程序先去查这张表拿到真正的函数地址再跳转。所以多态能成立依赖的正是这层“间接寻址”。没有virtual的普通函数编译期就决定了调谁有virtual的要等运行期根据对象的实际类型去查表。笔试如果出得更细会追问“一个对象里有多个虚函数占用几个虚表指针”。答案是一个类一个虚表指针不考虑虚继承的额外复杂度不管这个类里有多少个虚函数。虚函数再多也只是虚表里的条目变多对象本身只多一个指针的大小。这个问题我当时答的时候就差点选错因为我一度以为每个虚函数都要占一个指针空间。还有一道跟虚函数强相关的简答变体构造函数里能不能调用虚函数答案是“不要指望它能多态”。在构造过程中对象的动态类型被认为是“当前正在构造的这个类”所以派生类构造时还没构造完虚函数调用会解析到当前类的版本而不是最派生类的版本。这是C里一个很经典的陷阱。如果你在基类构造函数里调用了一个虚函数实际执行的是基类版本哪怕你创建的是派生类对象。2.3 指针、引用、内存管理的一连串追问指针和引用的关系也是那段时期几乎所有C试卷都爱出的一道选择题。我总结最干净的一句话引用是“变量的别名”指针是“保存地址的变量”。所以引用必须初始化、不能重新绑定指针可以改变指向、可以为nullptr。底层实现上引用往往也是一个指针的内存占用和地址传递但语言层面限制死了它的行为方式。另一组必问题new[]和delete[]为什么必须配对如果只用delete回收new[]出来的数组会发生什么对内置类型如int数组来说很多实现下侥幸不崩因为不需要调用析构函数但对于自定义类型的数组那就会导致前面的元素被析构而后面的元素析构函数不调用甚至直接崩掉。所以正确写法永远是一一对应。这份试卷里虽然没有直接出这题但在简答题里问智能指针时会延伸到“unique_ptr管理数组需要用unique_ptrT[]而不是unique_ptrT”否则析构时会调用delete而不是delete[]于是又回到这个坑。内存管理这个考点在美图这种做客户端底层开发的公司尤其重要。他们的代码要跑在低端Android机上内存动辄只给几十MB给App一个图片解码模块如果出现几个字节的泄漏运行一晚上就崩。所以相关题目不是空穴来风而是工作场景直接映射。3. 算法与数据结构题目实操复盘3.1 排序算法冒泡、选择、快速排序的边界控制2017年前后的C笔试在线编程题很少直接让你“写一个冒泡排序”但选择题里经常会拿排序算法做背景。比如冒泡排序在最好情况下的时间复杂度是多少如果已经提前加了“无交换提前终止”的优化最好情况是O(n)否则是O(n^2)。很多人一看到冒泡就条件反射写O(n^2)丢了那题的分。真正笔试喜欢考的还是快速排序尤其是“快排为什么最坏是O(n^2)怎么避免”。最坏场景是每次选的基准值都正好是当前区间最大或最小值导致区间极不平衡。解决办法是“三点取中”或者随机选基准。有些公司笔试会直接让你手写三路快排这个思路在大量重复数字时很有用。美图这套卷子算法题虽然没直接考快排但我在同期的其他公司卷子里见到很多次。选择排序则更基础它的特点是交换次数少最多n-1次但比较次数固定为n(n-1)/2不管数据是否有序都是这个值。这也是选择题一个常见选项设计。当年我看到选项里有“选择排序在最优情况下是O(n)”这种干扰项就是拿来考大家是不是真理解选择排序“无脑找最小值”的本质。3.2 快速幂与整数运算的极致性能“快速幂算法”这个热搜词不是偶然出现的它确实是笔试算法题里的常青树。美图2017年笔试的编程题我印象里就有类似场景求一个整数的n次幂n可能很大要求不能直接用pow。正确答案就是二分快速幂时间复杂度从O(n)降到O(log n)。快速幂的思路说白了就是“指数二进制展开”。比如要算3^13把13写成二进制1101也就是3^(841)所以只需要算3^1、3^4、3^8这些2的幂次把它们相乘即可。核心循环体非常短long long quickPow(long long a, long long n, long long mod) { long long res 1; while (n 0) { if (n 1) res res * a % mod; a a * a % mod; n 1; } return res; }这里特别要注意取模。笔试题目经常会说“结果对1e97取模”因为1e97是质数且足够大可以在加减乘过程中安全使用int64避免溢出。当年有同学写完快速幂忘记取模大数直接溢出变负数测试用例全挂。如果你现在刷题请一定养成“看到幂运算就条件反射取模”的习惯这个可以救命。还有一个易错点n的数据类型。如果题目说n最大可达10^18用int就炸了必须用long long。笔试环境里编译器不像IDE那么智能类型错了不会给警告只能靠自己的敏感度。3.3 字符串处理与数学题的常见变体字符串类编程题在美图卷子里出现过“字符串转数组”相关方向。不要小看这类题LintCode/LeetCode上easy难度的题到了笔试环境就变成“一次通过率不到一半”的绊脚石因为边界条件非常多。比如把字符串按逗号分隔转成int数组要处理空串、连续分隔符、前导空格、数字溢出等。代码层面有一个小技巧用std::istringstream配合std::getline读取分隔符分割的字符串std::string input 1,2,3,4,5; std::vectorint nums; std::stringstream ss(input); std::string token; while (std::getline(ss, token, ,)) { nums.push_back(std::stoi(token)); }这里又引出一个C11以后很实用的类——std::stringstream。笔试中经常用它做类型转换或字符串分割比手动遍历字符要少写很多边界判断。但注意std::stoi在遇到非法字符串时会抛异常笔试环境下异常会导致程序崩溃所以要么自己先检查字符串合法性要么用std::strtol这类C风格函数它可以通过endptr判断是否转换成功。数学题方面热搜词里有个“n个整数的最小公倍数怎么求c”。这类题在笔试中一般作为简单题出现知识点是“两数最小公倍数 两数乘积 / 最大公约数”gcd用辗转相除法。多个数的最小公倍数就反复迭代先求前两个的lcm再和第三个算以此类推。这个题容易踩的坑是乘法溢出——如果直接a * b / gcd(a,b)a和b都接近int上限时乘积会先溢出。正确的姿势是先除后乘a / gcd(a,b) * b。这种细节就是拉开差距的地方出题人不会那么好心提醒你“小心溢出”。4. 多线程、设计模式与综合应用类题目4.1 C多线程与ABA问题从底层到工程多线程在2017年的C笔试里已经是重点了现在更是标配。选择题会问std::thread能不能拷贝、std::mutex的lock和try_lock区别等简答题可能会延伸到“什么是竞态条件、怎么加锁保证线程安全”更进阶的会聊无锁编程。有关“ABA问题c”这个热搜词出现在C面试中非常合适。ABA问题是无锁编程里一个著名的坑假设共享变量当前值是A线程1准备把它改成B但被挂起线程2先把A改成B再把B改成A这时候线程1恢复执行CAS比较发现值还是A就误以为没人动过于是执行修改。但在这个“A→B→A”的过程中数据的语义可能已经被破坏了。当年美图的试卷没有直接考无锁编程毕竟校招应届生能写明白锁就不错了但有一个类似题考了“单例模式线程安全怎么保证”——这本质上就是并发控制。常规做法有三种懒汉式加锁、饿汉式静态初始化、Meyers Singleton函数内静态局部变量。C11以后函数内static局部变量的初始化是线程安全的所以最简洁的写法是class Singleton { public: static Singleton getInstance() { static Singleton instance; return instance; } private: Singleton() default; Singleton(const Singleton) delete; Singleton operator(const Singleton) delete; };这道题在笔试卷子里出现频率极高如果你能在这个基础上再答一句“C11标准保证magic static的初始化线程安全所以不需要双重检查锁”往往能让阅卷人眼前一亮。但要注意有些公司老代码还在用C98标准那里的静态局部变量初始化不保证线程安全所以如果你写的是“兼容C98项目”就不能这么搞。4.2 回调函数与设计模式笔试会怎么考回调函数在C笔试中常以两种形式出现一种是让你解释回调是什么另一种是给一段代码让你指出哪里用了回调。以前C的回调靠函数指针C里更推荐std::function和lambda表达式。比如“在按钮点击库中注册一个点击事件处理函数”这个场景你可以写成void onClick(int x, int y); button.setCallback(std::bind(onClick, std::placeholders::_1, std::placeholders::_2)); // 或者C11之后的lambda写法 button.setCallback([](int x, int y) { // 处理点击 });出了回调之外设计模式这块美图笔试也考了“观察者模式”或者“策略模式”的概念只不过不是直接说“请写出观察者模式的代码”而是给一个图像处理滤镜的应用场景让你设计不同滤镜可插拔的架构。这其实就是在考策略模式。核心思路是定义统一接口不同滤镜实现不同类运行时用基类指针数组统一管理。面向对象三大特性的“多态”在这里得到应用。我在复盘这份试卷后对设计模式这块的建议是不要死记模式结构图而是每个模式都配一个实际工程例子。比如“策略模式”配“多种滤镜切换”、“观察者模式”配“进度条监听图片处理进度”、“工厂模式”配“从文件扩展名创建对应的解码器”。面试官听到你举例的时候如果是一个真实的、来自工作或自己项目的例子可信度会高很多。4.3 综合场景题小游戏与网络模型热搜词里那些“c小游戏”、“物流网络”、“消息传递(news)”看着像是五花八门的题目来源但它们某种程度上恰恰是C笔试综合题的素材库。像“C小游戏”经常被用来考察面向对象设计能力。校招笔试可能会让你写一个“贪吃蛇”或“俄罗斯方块”的类设计不是让你实现完整游戏而是看你怎么拆类蛇身用什么结构存、移动逻辑放哪个类、碰撞检测放哪层。我实际面试时遇到过类似的设计题我的经验是把“数据”和“表现层”分开别在一个类里又存数据又画界面这样后续扩展才方便。另一个方向是网络或图论题比如热搜词里的“物流网络”和“消息传递(news)”这类题目本质上是在考图的遍历或最短路。美图2017年的卷子虽然没有考这类题但从那个时间段开始不少公司开始在C题里混合数据结构和算法题比如“有n个城市和m条线路求某个城市到其他城市的最短时间”标准解法就是Dijkstra或BFS。如果你报的是C服务端方向这类图论题可能比图像处理更贴近面试方向。所以我建议大家准备笔试时不要只盯着语言细节《算法》教材里的常见题型排序、栈、队列、二叉树、图、动态规划至少要过一遍。C语言题决定你能不能过第一轮筛选算法题决定你排名能不能进前20%。5. 常见失分点与笔试实战避坑5.1 环境配置与编译细节导致的非技术失分笔试不光是考你会不会还考你在“不熟悉的环境”里能不能顺利写代码。2017年那会儿很多公司笔试平台还是网页编辑器没有代码补全、没有编译错误提示很多人平时在VSCode或VS里写代码依赖智能提示到了笔试页面直接卡壳。热搜里的“vscode 配置c/c环境”、“vscode 配置c”—这些都是当下新手入门的痛点谁能想到放到笔试现场环境问题同样致命。我的做法是备考阶段就逼自己在“裸编辑器”里写C代码。不开补全、不开编译实时报错写完再放进编译器检查。这样训练一个月笔试时手写代码的准确率会明显提升。另外笔试前一定要确认目标平台用的什么编译器比如有些在线平台用GCC而本地Windows上用MSVC两者在很多细节上行为不同比如#include bits/stdc.h只有GCC标准库里有MSVC就没有。如果笔试题涉及这种可移植性差异提前知道能省不少时间。还有一个看着跟技术无关但非常关键的点留意题目要求的输入输出格式。很多C笔试编程题采用标准输入输出stdin/stdout而不是读文件。新手最常见的问题是忘了#include iostream、忘了using namespace std、或者输出多了打印调试信息导致格式错误。这些在IDE里跑没问题但在在线评测平台就是0分。我当年就犯过这种错白白丢了一道题。5.2 时间分配与检查清单复盘完整份试卷我总结了一套适合自己的笔试时间管理方案这里分享给你。总时长如果是120分钟我一般这样分配前10分钟快速浏览整张试卷标记出每道题的分值和难易程度。接下来40分钟做选择题和简答题遇到卡壳超过两分钟的题先跳过绝不恋战。中间20分钟把简答题中涉及代码的题目比如写单例先做掉因为这种题只要掌握了就是固定送分题。最后40到50分钟全力做在线编程题从分值最高或最有把握的开始。最后5分钟检查所有代码有没有编译问题、有没有多余输出、边界条件是否处理。这个分配不是万能的但对“题量偏大”的C笔试非常适用。因为选择题往往一道只有一两分而编程题一道能占到20到30分为了一个选择题的细节纠结到编程题没时间写是最亏的。我当年考这份卷子时最后编程题有一题我本来没思路但因为留足了时间冷静下来把问题拆分后居然做出来了。笔试考的不只是知识还有“在有限时间内调度自己能力”的元技能。这也是为什么我建议平时刷题就严格计时而不是漫无目的地磨蹭。5.3 做完题后的复盘心得笔试结束不代表结束真正的价值在复盘。我后来拿到美图的面试通知虽然后来因为薪资问题没去但这份卷子我保存了下来每次给别人讲C面试都把它当成一个典型的“能力体检表”。你会发现做题做错的地方往往不是因为你不知道某个知识点而是因为你从来没有亲手验证过。比如sizeof和strlen那个坑只要你在本地敲一遍一辈子都忘不了constexpr和const的区别只要你亲手写过一次编译失败的代码就永远不会再混。这也是我给所有准备C笔试的人最终的建议不要只看书、只看面经一定要动手。C是一门非常讲究“亲手感受”的语言很多报错信息、内存行为、标准库特性只有你真实碰过、踩过才能在笔试这么紧张的环境下形成条件反射。那些热搜词里的“c八股文”、“c面试题”给你的是“数据”但真正的竞争力来自你对这些数据做过多少次验证和思考。回头来看2017年美图这份试卷的很多考点到今天依然是C岗位笔试的高频题虚函数、内存管理、多线程、智能指针、设计模式、基础算法。语言标准从C11走到C17再到C20但考察的内核从来没有变过就是“你能不能写出一份健壮的、可维护的、高性能的C代码”。准备好了这些再去做任何一份C笔试试卷你都会发现它只是同一场考试的不同马甲罢了。
返回列表