尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

2015搜狗C++笔试题复盘:虚函数、内存模型与STL考点解析

2015搜狗C++笔试题复盘:虚函数、内存模型与STL考点解析 搜狗2015年那套C工程师笔试题我到现在还有印象。那年头的互联网公司笔试不像现在动不动就是系统设计、场景题C岗位的卷子基本就是硬核八股加算法搜狗这套题的风格尤其典型不跟你绕弯子直接往语言底层和内存模型上怼。很多题目看起来是选择题或者简答题实际上一旦展开就能看出一个人是背过书还是真的写过代码、调过崩溃。这篇文章不打算只复述题目和答案我想从出题人到底想考什么的角度把2015年搜狗C笔试题背后涉及的几个核心知识模块拆开聊一聊。无论你是准备面试的在校生还是工作几年后想回头补补底子的开发者这篇文章都值得花十几分钟看完。我会把当年高频出现的考点、容易踩的坑、以及今天回头看依然适用的复习思路串起来讲尽量还原一套完整的C工程师笔试复盘。1. 一份老笔试题的解剖思路从题目倒推考点1.1 为什么2015年的题放到今天还有复盘价值先回答一个很多人会问的问题C都发展到C20了甚至C23都出了好几年翻2015年的老题还有意义吗有而且意义不小。原因有三个。第一C笔试的核心考察点变化远比想象中慢。2015年搜狗在考的虚函数、内存布局、STL底层机制、const用法、指针与引用今天依然是面试高频题。C11带来的移动语义、智能指针到2015年已经进入工业界视野所以那套题里已经能看到一些新特性相关的考察比如auto_ptr的缺陷、shared_ptr的循环引用这些至今仍是经典问题。第二这类题目考察的其实不是语言版本而是你写C的时候脑子里有没有一张内存图。无论标准怎么演进栈、堆、虚表、对象切片、迭代器失效这些底层模型不会变。把一张老卷子吃透相当于把C最核心的内存模型和对象模型过了一遍这是放之四海而皆准的底子。第三从出题风格能反推一家公司的技术文化。搜狗当年是搜索公司对性能敏感的C后端岗位笔试一定关注候选人对底层机制的理解深度。所以它出的题很少是背概念就行的更多是给你一段代码让你说出输出结果和原因的题型。这种考法今天很多公司的笔试仍然在沿用。1.2 拆题方法论先分类再找知识边界拿到一套笔试题不要急着逐题硬做。我的习惯是先花两分钟把题目分类分类标准是这道题在考什么知识模块。一般来说C笔试题可以分成四类语言语法细节类类型转换、const与static的语义、重载与覆盖的区别、typedef与define的差异、sizeof的结果计算。对象模型与内存类构造与析构顺序、拷贝构造与赋值运算符、虚函数机制、内存对齐、堆栈分布。STL与泛型类容器底层结构、迭代器失效场景、sort的底层实现、map与unordered_map的选型。算法与代码输出类经典排序、字符串处理、链表的操作、给定代码段推演输出。分类之后你很快就能发现自己的薄弱区。如果你发现自己每次都错在对象模型类题目上那说明你读代码的时候没有在脑子里建内存图如果总错在STL选型上说明你对底层数据结构还不够敏感。2015年搜狗这套题我印象里四类都有涉及但比重上更偏向对象模型和内存类这跟它对后端工程师的定位是一致的。我还想多说一点做笔试题时题目本身就是最好的复习提纲。比如一道题考了vector扩容时迭代器为什么失效你不仅要知道答案还应该顺手把vector的扩容策略、析构顺序、元素移动方式全部复习一遍。一道题拉出一张知识网比刷十道孤立的题有用得多。2. 虚函数、多态与对象内存布局笔试题里的硬骨头2.1 经典问法基类指针delete时的析构顺序搜狗这类公司特别爱考一道跟析构相关的题看起来简单稍不注意就翻车。题目通常长这样class Base { public: Base() { cout Base() endl; } ~Base() { cout ~Base() endl; } }; class Derived : public Base { public: Derived() { cout Derived() endl; } ~Derived() { cout ~Derived() endl; } }; int main() { Base* p new Derived(); delete p; return 0; }问输出是什么如果你答Base() Derived() ~Derived() ~Base()那你就上当了。因为Base的析构函数没有声明为virtualdelete p时只会调用Base的析构函数Derived的析构函数根本不会执行。输出应该是Base() Derived() ~Base()。这道题背后真正想考察的有两层。第一层你是否知道基类析构函数要声明为virtual这条铁律。这也是一个实际工程中最隐蔽的内存泄漏来源——Derived里如果持有堆资源析构不被调用资源就永远没法释放。搜索类公司长期跑着海量服务资源泄漏是运行稳定性的大敌所以这个点几乎必考。第二层你是否理解编译器的静态类型与动态类型。p的静态类型是Base*动态类型是Derived但因为析构不是虚函数delete时走的是静态绑定只调用Base的析构。一旦把析构声明为virtual调用就会走虚表形成动态绑定完成从Derived到Base的完整析构链。这题给我的启示是写类的时候只要这个类可能被继承析构函数就默认写成virtual哪怕当时感觉不需要也要写。很多面试者说我知道这个知识点但写代码时就是忘了其实笔试想筛掉的正是这种知道但做不到的状态。2.2 虚函数表到底长什么样虚函数表这个问题本身不算难但它能引出连环问所以搜狗这类笔试喜欢把它当简答题来出。要点其实就几句每个包含虚函数的类在编译期会生成一张虚函数表vtable表里按声明顺序存放虚函数指针。每个对象在内存中有一个虚表指针vptr指向所属类的vtable。调用虚函数时编译期并不知道具体调哪个函数运行时通过对象的vptr找到vtable再从vtable中取出函数指针完成调用这就是动态绑定。笔试里常见的一个变体是问构造函数能不能是虚函数答案是不能。原因是虚函数调用依赖vptr而vptr的初始化发生在构造函数体内、在成员初始化列表之后。在构造函数执行期间vptr指向当前类的vtable。如果构造函数本身是虚的调用它时连对象都还没构造出来vptr根本不存在也就无法通过虚表找到它。换个角度说构造函数的作用是确定对象类型虚函数调用的前提是对象已经确定类型逻辑上就不成立。同样常考的还有虚函数能不能内联一个基础但稍微绕一点的问题。理论上函数声明inline只是给编译器一个建议是否内联由编译器决定。虚函数在编译期不确定调用目标通常无法内联但如果编译器能确定对象的动态类型比如直接通过对象调用而不是通过指针或引用则可能内联。笔试里遇到这种问题建议答需要根据上下文区分但通常情况下通过指针或引用调用时无法内联并说明原因。我还记得当年复习到虚函数这块时自己画过一张对象内存图。一个Derived对象里先是Base的成员和vptr再是Derived自己的成员和vptr如果有虚继承还要多一个虚基类指针。这张图画熟了后面理解多继承、对象切片、dynamic_cast就都顺畅了。建议你也画一画遇到搞不明白的地方就别急着背结论先把图摆出来。2.3 多继承下的二义性与菱形继承2015年搜狗C笔试题里对多继承的考察方式通常是让读者看一段菱形继承的代码然后问某个函数调用会产生什么问题。菱形继承的问题本质上是数据冗余和二义性。比如class A { public: int x; }; class B : public A { }; class C : public A { }; class D : public B, public C { };这个时候D对象里实际上有两份A的子对象也就是有两个x。访问d.x时编译器不知道你要访问B继承来的那个还是C继承来的那个直接报编译错误。解决办法有几种一是在D中自己加一个x成员消除二义性这会让B和C里各自的x仍然存在但被隐藏二是用作用域限定符显式指定比如d.B::x三是用虚继承从根源上解决让B和C都虚继承自A这样D中只有一份A子对象。这里有个比较关键的细节虚继承的表象是只保留一份公共基类子对象但它的实现机制因编译器而异。常见做法有虚基类表vbptr每个虚继承的子类对象里会多一个指针指向一个虚基类表表中记录了虚基类子对象在整体对象中的偏移量。这样做的代价是访问虚基类成员时多一次间接寻址性能略受影响所以不是所有继承场景都适合虚继承。面试官如果继续追问虚继承的构造函数调用顺序你要知道最派生类的构造函数负责初始化所有虚基类无论这个虚基类在继承体系中隔了多少层。也就是说D的构造函数不仅要初始化B和C还要初始化A虚基类而且A的构造要发生在B和C之前。这个规则和普通继承完全不同普通继承的构造顺序是从最远的基类到最派生类依次调用虚继承则是虚基类优先于所有非虚基类且只初始化一次。我现在遇到涉及多继承的代码依然会先问一句是否真的需要多继承。笔试题考察的是你能不能驾驭机制工程上则要尽量避免复杂继承体系。一段用虚继承解决的代码通常也能用组合加接口的方式改写得更清晰。这是笔试答案之外的工程智慧。3. STL容器与泛型编程选型和底层实现的博弈3.1 vector扩容机制与迭代器失效STL这块搜狗2015年的题里有一个高频考点vector在push_back过程中的扩容以及扩容导致的迭代器失效问题。vector底层是连续内存当size达到capacity时再插入元素就必须申请一块更大的内存、把原有元素搬过去、释放旧内存。这个更大通常是原容量的两倍具体增长因子因实现而异GCC是2msvc是老版本1.5到2不等。扩容涉及的是整个内存空间的搬迁所以任何指向原内存的迭代器、指针、引用全部失效。经常在笔试里出现的有三个问法问扩容后老迭代器还能不能用。答案是不能。这是使用vector最容易犯的错误之一因为有时候你只是插入一个元素代码看起来完全正常但一旦触发扩容后面用老迭代器操作就是未定义行为可能崩溃也可能碰巧不崩这种间歇性最坑。问reserve和resize的区别。reserve只改capacity不改变size不构造元素resize修改size可能需要构造或销毁元素。如果你提前知道要放1万个元素先reserve(10000)再push_back可以免去多次扩容搬移的开销。这个优化在性能敏感的服务端代码里非常实用。问shrink_to_fit的作用。它用来请求减小capacity到size大小但要注意标准只规定它是非强制的具体是否真的释放内存由实现决定而且调用后所有迭代器也可能失效。关于迭代器失效的完整地图我一直建议面试者整理成一张表容器插入导致失效删除导致失效vector所有迭代器扩容时插入点之后的迭代器未扩容时删除点及之后的迭代器deque两端插入不影响已有迭代器中间插入会失效同上list不影响其他迭代器仅被删元素迭代器失效map/set不影响其他迭代器仅被删元素迭代器失效unordered_map/set触发rehash时全部失效否则不影响仅被删元素失效这张表不是背出来就完了关键是理解背后的原理。list和map的节点是独立的插入和删除只动指针不搬内存所以其他迭代器自然不受影响vector是连续内存搬移数据就等于所有迭代器指向的都是旧地址。理解了原理即使换一个你没见过的容器也能推出迭代器会不会失效。3.2 map与unordered_map的选型依据笔试里常考什么时候用map什么时候用unordered_map。看似一道送分概念题实际考验的是你对数据结构底层实现的敏感性。map底层是红黑树插入、删除、查找都是O(log n)元素按key有序排列。unordered_map底层是哈希表查找平均O(1)最坏O(n)元素无序。选型时主要看三点一是是否需要有序遍历。如果需要按键排序后的顺序访问map有天然优势直接迭代就是有序的如果用unordered_map就得额外把key拿出来排序。二是性能对最坏情况的容忍度。哈希表在有大量哈希冲突时性能可能退化到O(n)而红黑树是稳定的O(log n)实时系统或对延迟敏感的模块更需要稳定上界。三是哈希计算成本。如果key是长字符串一次哈希计算的代价可能高于红黑树比较若干次这时候map不一定慢。笔试卷子里比较狠的问法是如果unordered_map的key是自定义struct需要做什么答案是要提供两个东西哈希函数和相等比较函数或重载operator。很多人知道要写哈希函数却忘了相等比较这反映的是对哈希表工作原理理解不够完整——哈希表定位到桶之后还要用相等比较来精确匹配否则两个哈希值相同的key没法区分。另外还常考一个坑unordered_map的迭代器在rehash时全部失效。这是因为rehash意味着桶数组迁移节点指针位置全部变化。如果你在遍历unordered_map的时候插入了元素导致rehash再用之前的迭代器就触发了未定义行为。稳妥做法是边遍历边收集需要修改的元素遍历完成后再统一操作。3.3 自定义类型的排序与比较函数STL排序是笔试和工程热门的交叉地带。搜狗那套题里对排序的考察通常不只是写个冒泡或者快排而是给一个自定义struct让你实现排序。这时核心考点就变成了如何给sort提供比较规则。几种做法在工程里都常见重载operator让自定义类型的对象天然支持小于比较写一个仿函数函数对象比如struct Compare { bool operator()(const Item a, const Item b) const { ... } };用C11的lambda表达式。笔试如果你写C11lambda是最简洁的如果环境受限仿函数是最稳的。这里有个细节必须提醒比较函数必须满足严格弱序strict weak ordering。也就是说对于同一个元素comp(a, a)必须返回false如果comp(a, b)为true那么comp(b, a)必须为false比较关系还必须具有传递性。很多排序崩溃的bug根源就是比较函数不满足严格弱序。比如你写return a.score b.score这个比较在a和b分数相等时会返回true而comp(a,a)也可能因为而返回true破坏了严格弱序sort就可能越界甚至死循环。正确写法是 return a.score b.score带上等号的都是坑。还有个隐藏考点sort不稳定如果需要相等元素保持原相对顺序要用stable_sort。stable_sort底层通常是归并排序需要额外内存但能保证稳定性。笔试里如果题目明确要求排序稳定直接答stable_sort并说明sort不稳定是因为它通常用快速排序在数据量大时会用堆排序兜底。4. 智能指针、RAII与C11新特性老题里的新意4.1 从auto_ptr到unique_ptr所有权转移的教训2015年考查智能指针时auto_ptr还在不少老代码里存在笔试出题人也喜欢拿它做文章。auto_ptr最被人诟病的问题是它的拷贝构造和赋值会把源指针置空实现所有权转移。这在语义上非常反直觉你以为是在拷贝实际上源对象已经失效。看这段经典代码auto_ptrint p1(new int(10)); auto_ptrint p2 p1; *p1; // 未定义行为p1已经被置空许多面试者试图用auto_ptr管理vector等容器时也会遇到编译问题因为容器要求元素可拷贝且拷贝后源对象仍然有效auto_ptr不满足这个要求。C11之后auto_ptr被标记为deprecatedC17直接移除取而代之的是unique_ptr。unique_ptr通过移动语义实现了独占所有权不允许拷贝只能通过std::move转移所有权。这个设计从语义上就杜绝了两个指针同时管理同一块内存的可能也杜绝了auto_ptr那种隐式转移导致的悬空。笔试里常考unique_ptr为什么不能拷贝答案就是它删除了拷贝构造和拷贝赋值move操作会转移内部裸指针源对象置空。复习到这里我想多说一句笔试中谈到auto_ptr许多人只知道有坑却说不清坑的本质。坑的本质是拷贝语义被错误地赋予了转移所有权的副作用违反了人们读代码时默认的直觉。这种违反直觉的设计在面试里比一个纯粹的语法难点更容易被拿来做文章因为它考察的是工程判断力不只是背API。4.2 shared_ptr的引用计数与循环引用shared_ptr采用引用计数管理对象生命周期最后一个引用被释放时对象被删除。听起来很完美但它有一个著名的陷阱——循环引用。笔试的经典问法是struct Node { shared_ptrNode next; weak_ptrNode prev; };或者更直接的A和B互相持有shared_ptr。当A持有BB持有A时A和B的引用计数各为1它们互相引用谁也不会变为0于是永远不会被释放形成内存泄漏。解决办法是把其中一个方向的shared_ptr换成weak_ptrweak_ptr不会增加引用计数只提供弱引用需要时通过lock()获得shared_ptr来访问对象。我在纸上推导这个模型时才真正理解了引用的本质引用计数无法处理环因为环中的每个节点都觉得自己还被别人引用着但没人能从外面触达这个环。好比一个屋子里两个人互相扶着谁也不会倒下但整个屋子与外界已经失联了。weak_ptr就是给这个环开了一扇门不参与计数但关键时刻能确认对象是否还活着。还有一道延伸题不少面试官爱问shared_ptr的线程安全性。要点是引用计数本身的增减是原子操作线程安全但被管理的对象并不线程安全多个线程同时修改同一个对象依然需要同步。另外同一个shared_ptr对象在多个线程中被同时修改比如赋值也不是安全的因为shared_ptr内部有两个指针一个指向对象一个指向控制块对这两个指针的更新不是原子的。这个边界要分清。4.3 移动语义与右值引用C11引入了右值引用和移动语义这在2015年已经是笔试热点。搜狗那套题如果放在今天再出移动语义的考察比例只会更高。核心概念是右值引用用声明只能绑定到右值。移动构造函数和移动赋值运算符接收右值引用参数可以从参数中偷走资源而不是深拷贝。对vector、string这种持有堆内存的类型移动操作能避免大量内存拷贝。笔试常见问法是std::move是什么很多初学者以为std::move会移动什么其实它只是把一个左值强制转换为右值引用配合移动构造函数或移动赋值运算符触发移动语义。真正干活的是移动构造/赋值函数本身std::move只是帮编译器做类型转换。还有一个高频问题移动构造函数为什么通常要加noexcept。原因是标准库容器比如vector扩容时如果需要移动元素会优先使用移动构造但如果移动构造函数可能抛异常没有声明noexcept为了保证异常安全容器会退化使用拷贝构造函数。这是因为移动构造一旦抛出异常源对象的状态已经被修改无法回滚而拷贝构造不会破坏源对象可以安全回退。所以如果你的移动构造函数确实不抛异常就一定要标记noexcept否则容器做不到高效的移动搬运。这个细节笔试中能主动答出来的人不多但对工程性能影响却非常直接。5. 算法与数据结构题目从会写到写对5.1 经典题型的考场解法C工程师笔试题的算法部分难度通常不会到竞赛级别但很讲求基本功的扎实程度。2015年搜狗这类公司爱考的其实就集中在几类链表操作、字符串处理、二叉树遍历、排序与查找。链表题里有一个很经典的考察单链表反转。看起来简单但用手写代码实现时很多人在指针交接处会乱。常规思路是三个指针prev、cur、next每次把cur-next改成prev然后三个指针依次前移。我见过太多面试者把循环边界写错记住一个口诀先存next再改cur再移prev再走cur顺序不能乱。字符串题里一个高频考查是求最长公共前缀或者把字符串按指定规则拆分。这类题在C中真正考察的往往是边界处理和STL字符串API的熟练程度比如substr的第二个参数是长度而不是结束位置、find找不到时返回npos这类细节写错一个整个函数的行为就崩了。二叉树题通常是前序、中序、后序的递归与非递归写法和层序遍历。非递归遍历考察对栈用法的理解尤其是后序遍历需要一个额外的标记记录右子树是否已经访问过这个复杂度比前序和中序明显要高值得专门练一下。我是这么看待笔试算法题的代码写对是一回事能否快速定位代码里的bug是更重要的一层能力。面试官给你一段含有bug的排序代码让你指出问题其实比让你手写排序更能看出工程底子因为真实的代码世界里bug才是常态。5.2 排序算法在真实工程中的取舍笔试里最常被拿来考查的排序算法是快速排序和归并排序但如果你在复习时只盯着怎么写它们就遗漏了一个更关键的考点什么时候用哪一种。快速排序平均时间复杂度O(n log n)原地排序常数小是通用排序首选。但它有两个短板一是不稳定不能保证相等元素原始顺序二是对几乎有序或含有大量重复元素的序列如果基准选取不当会退化到O(n^2)虽然随机化或三数取中可以大幅缓解但数据分布极端时仍有风险。为了让快排在工程中更稳很多实现采用数据量大时用快排数据量小时切到插入排序的策略插入排序在接近有序的小数组上速度极快这个Hybrid思路本身就是一种工程优化。归并排序稳定时间复杂度稳定O(n log n)代价是需要额外O(n)内存。当你需要稳定排序或者要处理的数据不适合随机访问比如链表归并排序就是比快排更合理的选择。C标准库的stable_sort用的正是归并排序。堆排序的优势是原地且时间复杂度稳定O(n log n)但实际常数大、且访问内存的局部性差所以在通用排序中通常不是首选它的强项在于需要同时维护最大值/最小值的场景比如优先级队列。C里std::priority_queue底层就是堆。笔试如果问sort的底层实现是什么C标准并没有规定具体算法但主流实现都采用内省排序introsort它是快排、堆排、插入排序的混合体兼顾了快排的常规性能和堆排的最坏情况保证。这么设计的目的就是企业级代码对最坏性能的上界有要求不能接受一个排序在极端输入下把服务拖垮。5.3 面试官追问层的考察点算法题答完之后面试官通常还会加问两个维度时间复杂度的精确分析以及能否空间换时间。这其实是笔试之后你如果进入面试环节会遇到的情况但笔试本身也可能通过扩展题来考察这些点。比如题目要求O(n log n)排序你写出来了面试官会追问能不能O(n)如果数据有特殊分布比如成绩范围很小的确可以用计数排序做到O(nk)。这个追问考查的是你脑子里有没有特定条件下可以打破通用下界的意识。再比如查找的题目如果数据是有序数组二分查找是基本答案。面试官会继续问如果数据量太大内存放不下怎么办这时候就引出外部排序、索引、哈希分片等思维。虽然这已经超出笔试范围但笔试中经常会有如果数据规模扩大100倍你的解法还成立吗这样的思考题这类变体正是笔试判卷时区分度最高的部分。我个人经验是刷算法题不要只为过笔试要养成在脑子里过一遍这个算法的瓶颈是什么它能不能Scale的习惯。搜索公司对规模化问题非常敏感能展现出这种思考习惯的候选人通常更容易脱颖而出。6. 复盘2015从一套题看C面试的弦外之音6.1 当年出题风格的合理推断与变化趋势我没有办法百分百确认2015年搜狗那套卷子的每一道原题但从当年互联网公司C岗位笔试的整体风格以及搜狗作为搜索引擎公司的技术栈与业务特点可以合理推断其考察重点是明确的语言底层、内存管理、STL机制、经典算法加上少量C11新特性。为什么搜索引擎公司要这么考因为搜索引擎的后端服务处理的数据量非常大对延迟和内存效率极敏感。一个不了解内存模型、不熟悉容器底层结构的C程序员写出的代码可能在功能上完全正确但在性能上完全不可用。笔试的目的不是筛掉不会写代码的人而是尽量筛掉写代码时不知道机器在干什么的人。到了今天C笔试的出题风格已经有了一些变化。新标准的新特性比重上升了像constexpr、可变参数模板、折叠表达式、concept这些在2015年几乎不会考的概念现在会成为加分项。对并发编程的考察也比当年多std::thread、async、atomic、内存序这些词在笔试题中出现的频率明显提高。但虚函数、内存布局、RAII、STL选型、算法复杂度这些基本功依然占主导。6.2 今天复习C笔试我看重什么如果你现在正准备C工程师的笔试我的建议是先做一个自查能不能不查资料画出一个包含虚函数、虚继承的类的对象内存布局图。如果画不出来说明对象模型这块还需要补这恰恰是C笔试最大的分水岭。很多选择题、输出题、简答题本质上都在问这张图。复习时建议按这个顺序来先把语言基础过一遍重点是const在不同位置的含义、static成员的存储与访问方式、指针与引用的差异、构造析构赋值运算符的完整规则然后把对象模型吃透虚函数、多继承、dynamic_cast、对象切片最好每种情况都写一段小代码在本地跑一遍观察输出接着过STL六大组件容器选型、迭代器失效、仿函数与lambda、空间配置器可以不深究但要知道大概思路最后过一遍C11/14的新特性智能指针、移动语义、右值引用、std::function和std::bind这些在面试追问中几乎必出。算法部分把常用的排序、查找、链表、二叉树、字符串、动态规划基础题刷一遍就够了追求的是稳定正确不必死磕难题。工程实现上注意边界条件、空指针、内存泄漏这些细节代码风格是否清晰也在考官的观察范围内。我还有一个建议笔试复习最好直接拿一套老题完整做一遍限时两个半小时然后逐题复盘。复盘时不要只看正确答案要把每一道题牵连的知识点全部扒出来形成自己的笔记。一道看起来很简单的题牵引出来的知识网往往比你想象的大得多。比如一道关于析构函数的题可以拉出虚函数、动态绑定、RAII、智能指针、资源管理一整条线一道关于vector扩容的题可以拉出内存分配、迭代器失效、异常安全、性能优化一整条线。这样刷一套题比盲目地做十套题还管用。6.3 八股之外我理解的工程师考核本质很多人把这类笔试称为八股这个说法有道理但也不完全公平。笔试里的这些题目并不是为了刁难人而是用一套相对标准的方式快速测量一个工程师在特定语言上的建模能力。我见过代码写得很花哨、设计模式一套一套的候选人却不知道一个vector 在扩容时所有迭代器都会失效也见过写代码朴素简洁却能清楚地解释程序运行时的内存变化的人。后者到了服务端工程环境里通常能更快定位线上问题。C是一门允许你直接操作内存的语言写C的人如果脑子里没有内存模型迟早会在线上付出代价。从2015年到现在C标准在变工具链在变但面试的核心逻辑其实没有变它想确认你写的每一行C代码都能对应到机器实际会执行的指令和内存操作。这个本质是我复盘了这么多套笔试题之后最想分享给后来者的一句话。如果你正在准备C岗位的面试别怕这些老题也别只背答案。拿一台电脑写一小段代码跑一跑观察内存和输出亲手踩几个坑比什么都强。我当年就是这么干的走了很多弯路但那条弯路走到现在恰恰成了我最扎实的底子。
返回列表