ARTICLE DETAIL

建站实战干货

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

腾讯2016研发工程师笔试题解析:C++与网络核心考点复盘

2026/8/30 21:49:32 拓冰建站 浏览量
腾讯2016研发工程师笔试题解析:C++与网络核心考点复盘 前段时间整理旧电脑里的资料翻出来一份腾讯2016年的研发工程师笔试题二。说实话我刚看到这份题的时候还有点恍惚毕竟快十年过去了但一眼扫过去里面很多考点放到今天依旧是大厂笔试面试的高频点数组和指针的关系、二叉树遍历的还原、死锁的必要条件、TCP的TIME_WAIT……几乎每一道题都能在现在的牛客网面经里找到变体。这套题对于准备校招、社招尤其是瞄准腾讯、或者其他以C/后台研发为主的技术岗的同学来说是一份性价比很高的练习材料。它不像LeetCode那样只考算法而是把C/C语言基础、数据结构、操作系统、计算机网络、甚至一点概率思维全都揉在一张卷子里。这种综合性的考察方式恰恰是很多同学容易翻车的地方——算法题刷得很溜但一遇到指针的sizeof、结构体内存对齐就懵了。这篇文章我就用“做题 复盘”的方式来拆解这套题。我会把每道题的考点、解题思路、容易踩的坑全部展开讲一遍并结合我这些年实际写代码、做面试官的经验聊聊为什么当年腾讯要这么出题以及这些知识点在真实工程里到底有什么用。无论你是正在准备笔试的应届生还是想巩固基础的初级工程师这篇文章都值得你花半小时认真读完。1. 试卷整体设计与考察重点拆解1.1 这套题到底在考什么腾讯2016年的研发工程师笔试题二整体上属于“技术综合卷”覆盖范围非常典型C/C基础、数据结构与算法、操作系统、计算机网络外加少量逻辑/概率题。从题目结构来看选择题占大头而且是多项选择与单项选择混合这种设计在当年的大厂笔试里非常常见。为什么腾讯要这样设计很简单研发工程师在腾讯的业务体系里分布很广有做QQ、微信这类IM产品的有做游戏后台的也有做云计算、大数据基础设施的。这些方向虽然业务差异大但底层都离不开几个核心能力对内存和指针的理解C/C是腾讯很多核心系统的基石对数据结构和算法复杂度的敏感度海量用户场景下性能差一个量级就是事故对操作系统进程/线程、死锁、内存管理的认知后台服务并发模型的基础对网络协议的理解IM、游戏、云服务全是网络密集型应用所以这套题表面上看是一张笔试卷实际上是在模拟“一个合格的后台研发工程师必须具备的底层知识体系”。它不是单纯考记忆而是考你在大学课堂里有没有真正吃透那些基础概念。1.2 为什么现在拿出来做还有价值一个很现实的问题是2016年的题放到现在还有参考价值吗我的答案是有而且价值不小。原因有三。第一IT行业的基础知识迭代是极慢的。C语言指针的规则、TCP三次握手四次挥手、二叉树遍历的递归思想这几十年几乎没变过。你现在去问一个工作了五年的后端工程师他照样得靠这些知识吃饭。第二大厂笔试的出题思路有很强的延续性。现在的腾讯笔试题虽然加了更多场景题和编程题但底层考点和2016年基本一脉相承。你把这份老题吃透了再去做新题会感觉很多东西似曾相识。第三这套题的难度梯度设置得比较好既有送分的基础题也有拉开差距的硬骨头非常适合用来做自我检测——如果你能稳定拿到80%以上的正确率那说明基础这块基本过关了。2. 高频考点精讲C语言指针与数组的底层逻辑2.1 一维数组的sizeof还是那个经典陷阱指针与数组的关系历来是C/C笔试的“兵家必争之地”。腾讯这套题里相关的题目也很多其中最有代表性的就是sizeof与strlen的区别以及对“数组名退化”的理解。我先出一道几乎必考的经典题大家可以在心里先做一下32位环境下有以下代码char str[] Tencent; char *p str; printf(%d %d %d\n, sizeof(str), sizeof(p), strlen(str));输出是什么答案是7 4 6。为什么sizeof(str)str是字符数组大小由初始化字符串决定。字符串Tencent是7个字符但末尾有一个隐式的\0所以数组实际占7个字节。sizeof(p)p是一个指针在32位环境下指针占4个字节和它指向的内容没有任何关系。strlen(str)这个函数数的是可见字符个数遇到\0就停Tencent一共6个字母所以返回6。这道题本身不难但它背后有一个很多人容易忽略的关键点数组名在多数表达式中会“退化”为指向首元素的指针但在sizeof运算符中不会退化。也就是说sizeof(str)拿到的是整个数组的字节数而sizeof(p)拿到的是指针本身的字节数。我在实际面试中见过不少候选人LeetCode刷了几百题但问到这一步就卡住了。原因很简单刷题的时候你很少直接用sizeof去处理数组而笔试恰恰喜欢考这种“你觉得自己会但其实没完全会”的细节。2.2 二维数组与指针步长a、a[0]、a到底差在哪如果说一维数组的sizeof是开胃菜那二维数组的指针运算就是真正的主菜了。腾讯这套题里有一道非常典型的多选考的就是这个。假设有定义int a[3][4];以下哪些表达式在语义上是正确的a 1*(a 1)a 1a[1] 1*(a[1] 1)这道题的难度一下就上来了因为涉及多层指针的“步长”概念。先说结论a的类型是int (*)[4]也就是“指向含4个int元素的一维数组的指针”。所以a 1移动的是4个int的跨度指向第二行。*(a 1)拿到的是第二行的首地址类型是int *指向的是a[1][0]。a的类型是int (*)[3][4]它指向整个二维数组。所以a 1直接跳过整个3行4列的数组这在指针运算里是合法的只是很少用。a[1]的类型是int *指向第二行第一个元素。a[1] 1移动一个int的跨度指向a[1][1]。*(a[1] 1)就是解引用结果是a[1][1]的值。如果你觉得有点绕用一个生活的类比来理解把二维数组想象成一栋楼a是楼栋地址a 1是隔壁那栋楼整栋楼为单位a[1]是某一层的入口a[1] 1是在这层楼里走一个房间。这个知识点在笔试里出现的频率极高不只是腾讯华为、字节、阿里的C/C卷子里都爱考。原因也很实际真正做后台开发时二维数组、数组指针、指针数组之间的转换是家常便饭搞不清楚这些写出来的代码很容易出现内存越界或者访问错位。2.3 内存对齐结构体大小为什么不是成员之和指针之后C语言笔试的另一个心头好就是内存对齐。腾讯这套题里有一道结构体大小的题目我印象很深因为它不是简单地让加一下成员大小而是考了你对齐规则的真正理解。假设32位环境下有以下结构体struct Test { char a; int b; char c; };问sizeof(struct Test)是多少如果你的答案是1 4 1 6那就掉坑里了。正确答案是12。原因在于结构体的每个成员在内存中的地址需要满足“对齐要求”。在这个例子中a占1个字节从偏移0开始。b是int类型要求4字节对齐所以不能紧跟在偏移1的位置必须在偏移4的位置开始前面3个字节被填充padding。c占1个字节从偏移8开始。最后结构体的总大小必须是最大对齐数这里是4的整数倍所以9要补到12。所以实际内存布局是a(1字节) padding(3字节) b(4字节) c(1字节) padding(3字节) 12字节。这个知识点看起来是“死记硬背”的规则但在真实工程里非常重要。比如你在写网络协议解析的时候如果直接把一个结构体指针强制转换成网络包的数据指针来读取内存对齐和字节序的坑会直接导致数据解析错误。另外缓存行的对齐也直接影响了高并发场景下的性能。腾讯很多底层系统对性能要求极高所以这个考点其实非常实用。注意在涉及网络协议或者文件格式时不要依赖结构体的默认内存布局应该使用#pragma pack或者显式的序列化/反序列化代码。工程里图省事的后果往往是线上出了一堆诡异的问题。3. 算法与数据结构真题解析从二叉树到链表3.1 已知前序和中序还原二叉树数据结构部分的题目腾讯这套题里最经典的一道是已知某二叉树的前序遍历序列和中序遍历序列要求还原二叉树并推导出后序遍历。举个例子前序遍历A B D E C F中序遍历D B E A C F这道题的解法其实有个很固定的套路前序遍历的第一个节点一定是根节点然后拿着这个根节点去中序序列里定位它左边是左子树右边是右子树再递归处理左右两部分。我拆给大家看前序第一个是A所以A是根节点。在中序序列中找到AA左边是D B E这是左子树的中序序列A右边是C F这是右子树的中序序列。回到前序序列A后面跟着的B D E三个节点属于左子树其中B是左子树的根因为前序中B排在左子树序列的第一个。重复上述过程左子树中序是D B E根是BB左边是D右边是E所以B的左孩子是D右孩子是E。右子树同理前序中的C F里C是根中序中C左边没有节点右边是F所以C只有右孩子F。最终得到的二叉树结构是A / \ B C / \ \ D E F后序遍历结果就是D E B F C A。这道题在笔试里考的是“递归分治”的思维但如果你去做腾讯的在线编程题它往往会让你直接写代码实现。核心代码其实非常短用递归就可以搞定TreeNode* buildTree(vectorchar preorder, vectorchar inorder, int preLeft, int preRight, int inLeft, int inRight, unordered_mapchar, int indexMap) { if (preLeft preRight || inLeft inRight) return nullptr; TreeNode* root new TreeNode(preorder[preLeft]); int rootIndex indexMap[root-val]; int leftSize rootIndex - inLeft; root-left buildTree(preorder, inorder, preLeft 1, preLeft leftSize, inLeft, rootIndex - 1, indexMap); root-right buildTree(preorder, inorder, preLeft leftSize 1, preRight, rootIndex 1, inRight, indexMap); return root; }这里我用了哈希表来快速定位根节点在中序序列中的位置避免每次递归都去线性查找时间复杂度从O(n^2)降到了O(n)。这种优化在笔试里是很加分的点。3.2 链表反转与快慢指针高频算法题的底层逻辑除了二叉树链表的题目在腾讯这套题里也占了不少比重。比如“反转一个单链表”这道题放到现在依然是各大厂笔试的热门。我当年第一次做这道题的时候也卡了一阵后来想明白了就很简单——其实就是把每个节点的next指针指向前一个节点。这里我给出手写的迭代版代码struct ListNode { int val; ListNode* 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; }关键点在于在修改cur-next之前必须先把原来的next保存下来否则链表就断了。这个思路特别像你在纸上把一条项链的珠子一个个换个方向每次换之前都要先抓住下一个珠子不然整个链子就散了。还有一道题是“找出链表的中间节点”最优解法是快慢指针快指针每次走两步慢指针每次走一步当快指针到末尾时慢指针正好在中间。这个技巧在判断链表是否有环、找倒数第K个节点等问题中都可以复用属于必须掌握的套路。3.3 复杂度分析与排序稳定性对照表腾讯这套题的算法部分还考了一些需要“背结论”的知识点其中最典型的就是各大排序算法的复杂度与稳定性。这些内容单独看不难但放在多选题里特别容易选错。我先整理一个表这是笔试前一定要刻进脑子里的排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性冒泡排序O(n^2)O(n^2)O(1)稳定选择排序O(n^2)O(n^2)O(1)不稳定插入排序O(n^2)O(n^2)O(1)稳定希尔排序O(n^1.3)O(n^2)O(1)不稳定归并排序O(nlogn)O(nlogn)O(n)稳定快速排序O(nlogn)O(n^2)O(logn)不稳定堆排序O(nlogn)O(nlogn)O(1)不稳定这里我特别提醒一下很多同学会把快速排序记成稳定的这其实是个误区。快排的分区交换操作本质上会打乱相同元素的相对顺序所以它是不稳定的。而堆排序由于建堆和调整过程中元素的跳跃式比较同样不具备稳定性。只有归并排序在实现时通过“左半部分优先”的策略才能保持稳定性这也是它在处理大规模数据时需要额外O(n)空间的原因。4. 操作系统与计算机网络重难点剖析4.1 进程、线程与协程大厂笔试的必备考点操作系统方面的题腾讯这套卷子里最核心的考点就是进程与线程的区别。这个知识点在大学课本里讲了无数遍但笔试题目会把它们揉到具体的场景里考而不是简单问你概念。核心区别就几条进程是系统资源分配的基本单位线程是CPU调度的基本单位。每个进程有独立的地址空间一个进程崩溃不会直接影响其他进程同一进程的多个线程共享地址空间一个线程非法访问内存整个进程都会挂掉。进程切换的开销大于线程切换因为进程切换要切换地址空间、页表等线程切换只需要保存和恢复寄存器上下文。进程间通信方式包括管道、消息队列、共享内存、信号量、Socket等线程间通信可以直接通过共享全局变量但需要加锁。笔试题里经常会有一道“多选”列出一堆关于进程和线程的描述让你选出正确的。这种题最大的坑在于选项里经常用一些非常绝对化的词比如“线程切换一定比进程切换快”“同一进程的线程可以共享所有资源”这些说法其实都过于绝对。以“线程切换一定比进程切换快”为例这个说法不够严谨因为线程切换如果涉及不同的CPU核心、缓存失效等问题开销也可能很大。但从操作系统教材的角度来说线程切换比进程切换开销小是常识性结论所以到底算不算对要看题目的语境。腾讯当年的出题风格是偏向教材结论的选“对”的概率更大但如果选项里加了“一定”这种词就要多留一个心眼。4.2 死锁的四个必要条件与银行家算法死锁是操作系统模块的必考内容腾讯这套题也不例外。死锁的四个必要条件是互斥条件、持有并等待条件、不可剥夺条件、循环等待条件。这四个条件必须同时满足系统才会发生死锁。笔试题的常见考法是给你一个场景问你破坏了哪个条件就能避免死锁。比如“允许进程强行抢占其他进程已持有的资源”这破坏的就是不可剥夺条件“要求进程在进入临界区前一次性申请所有需要的资源”这破坏的是持有并等待条件。另外银行家算法也经常出现在多选题里。它本质上是一种避免死锁的资源分配策略每次分配资源前系统先进行安全性检查如果分配后系统仍处于安全状态才真正分配否则进程必须等待。这种算法的缺点也很明显它要求进程预先声明最大资源需求而且计算复杂度比较高所以在实际操作系统里用得并不多更多是作为教学案例出现。但这里我要多说一句死锁的知识在实际工程中非常有用。尤其是写多线程程序的时候两个线程分别持有一把锁然后互相等待对方释放锁这就是典型的死锁。排查这种问题最实用的手段是pstack或者gdb查看线程堆栈看每个线程到底卡在哪个锁上。我自己曾经排查过一个线上服务卡死的故障最后定位到就是两个线程加锁顺序不一致导致的死锁解决方法是约定全局统一的加锁顺序用std::lock或者按固定地址顺序加锁。4.3 TIME_WAIT与TCP断开连接的坑计算机网络这块腾讯考得最多的是TCP协议。说实话这也是有原因的腾讯的QQ消息、微信聊天、游戏对战全部依赖TCP或者类TCP的可靠传输机制。所以笔试题目特别喜欢考TCP的连接管理和状态转换尤其是TIME_WAIT。TIME_WAIT是TCP四次挥手中主动关闭连接的一方在收到对方的FIN并发出最后一个ACK之后进入的状态持续时间是2MSL最大报文段生存时间通常是2分钟。很多人不理解为什么要等这么久这道题在笔试题里通常是以“多选”形式出现的以下关于TIME_WAIT的说法正确的有哪些保证最后一个ACK能够到达对方。防止旧连接的报文段在新连接中造成干扰。主动关闭连接的一方会进入TIME_WAIT状态。等2MSL是为了让网络上所有旧的报文段都自然消亡。正确答案是全选。理解的关键在于TCP连接是四元组源IP、源端口、目的IP、目的端口唯一确定的。如果主动关闭方不等待2MSL就立刻用相同的端口建立新连接网络上可能还有旧连接残留的迟到报文这些报文会被新连接错误地接收导致数据错乱。在实际的后台开发中TIME_WAIT太多是一个很常见的故障源。如果你的服务器上有大量短连接就会积压大量的TIME_WAIT连接占用本地端口导致“Cannot assign requested address”之类的报错。解决方案包括开启net.ipv4.tcp_tw_reuse用于客户端场景、调整tcp_max_tw_buckets上限、或者让客户端复用长连接。提醒一下网上关于tcp_tw_reuse和tcp_tw_recycle的讨论很多我个人的建议是Linux内核4.12之后tcp_tw_recycle已经被移除了因为它会导致NAT环境下连接异常。如果还在老内核上使用这个参数遇到奇怪的连接失败问题优先关掉它排查一下。5. 笔试实战中的经验教训与避坑指南5.1 当年最容易丢分的三个地方我根据自己和身边同事当年的经验总结出这套题最容易丢分的三个地方大家在做的时候可以重点注意第一多项选择题漏选、多选。腾讯的笔试多选题占了很大比例而且很多是多选。很多人的策略是“拿不准的就不选”但在计分规则里少选通常只能拿部分分或者拿不到分所以还不如大胆选。当然前提是你对知识点真的有把握否则宁可不选。第二指针运算的边界条件。前面提到的二维数组指针、a 1这种操作很多人会忽略“步长”的概念。建议做题时把每个表达式的类型写清楚再判断运算结果不要凭感觉。第三操作系统和网络的概念混淆。比如“进程和线程的关系”“TCP和UDP的适用场景”这类题平时背得滚瓜烂熟但在多选题的干扰项里出题老师会用一些“看似对但其实是混淆概念”的选项。遇到这种题我建议每个选项都回去对照定义仔细看不要看到一个熟悉的词就选了。5.2 刷这套题的高效方法很多人刷题是一套一套地刷刷完对答案就完事了。这种效率其实很低。我推荐的方法是“三遍刷题法”。第一遍不限时完整做一遍把拿不准的题都标记出来。做完对答案把错题和蒙对的题全部整理到错题本上每道题都写清楚“考点是什么、我错在哪、正确思路是什么”。这一遍的目的是查漏。第二遍限时做模拟真实笔试的节奏。腾讯当年的笔试时间是60到90分钟题量大约在20到30道。你应该给自己掐表逼自己在时间压力下快速判断。这一遍的目的是练速度。第三遍只看错题本不看原题。如果一道错题你能够不看答案完整地把解题思路讲给另一个人听那才算真正掌握了。这一遍的目的是巩固。这个方法看似笨但效率其实很高。因为很多人的问题不在于“不会”而在于“以为自己会了”。错题本的作用就是打破这种错觉。5.3 一个提高正确率的小技巧手写代码前的注释先行这道题虽然主要是选择题但里面有些题目会要求手写代码或者描述算法思路。如果你在面试环节碰到类似的问题我强烈建议你在写代码之前先用注释把思路写下来。比如让你实现一个快排你可以先在代码区写三行注释// 1. 选择pivot // 2. 将小于pivot的放左边大于pivot的放右边 // 3. 递归处理左右两个子区间然后再开始写代码。这样做有三个好处第一面试官可以通过注释一眼看出你的思路是否清晰哪怕代码有一些小bug思路对了也能拿大部分分第二注释是你的“提词器”写代码的过程中不容易写一半忘了下一步要干什么第三当代码出问题时你可以对照注释快速定位是哪个环节逻辑不对。这个方法不仅适用于面试也适用于日常开发中写复杂的函数。我自己的习惯是凡是超过20行的逻辑都会先写注释再写代码能显著减少返工次数。6. 从2016到现在的变化这套题还值得做吗6.1 题型演变的几个方向说实话如果你现在去参加腾讯的笔试会发现题型已经发生了挺大的变化。2016年的时候笔试还是以选择题为主编程题是线下用纸写或者简单的在线判题。而现在的大厂笔试几乎清一色是ACM风格的在线编程题选择题占比大幅下降。但这并不意味着这套老题没用了。恰恰相反现在的编程题本质上在考察的能力和2016年是一致的对数据结构的理解、对算法的敏感度、对边界条件的处理、对复杂度的分析。只不过考察形式从“告诉你了知识点让你判断对错”变成了“给定一个场景让你自己判断该用什么知识点然后动手实现”。前者是“知道”后者是“会用”而“会用”的前提还是“知道”。所以我的建议是这套老题适合用来打基础、查漏补缺。如果你能把这套题涉及的知识点全部掌握再去做现在的LeetCode或者牛客网的题目会明显感觉底子厚实很多。6.2 面向大厂笔试的准备建议结合我这些年带校招生的经验关于大厂笔试的准备我给出三个比较核心的建议第一基础知识点要形成体系而不是零散记忆。很多同学今天看了指针明天看了二叉树后天看了TCP但脑子里没有一个整体的框架。我建议你画一张知识图谱把C/C、数据结构、操作系统、网络四大部分覆盖的核心考点全部列出来然后逐个打勾。这样到考前你就能清楚知道自己还有哪些盲区。第二编程题要动手练而且要有时间意识。很多人是“眼高手低”看题都会一写就废。解决的办法只有一个每天坚持写2到3道编程题限时30分钟写不完也直接看题解然后自己重新默写一遍。坚持一个月代码的手感会有质的提升。第三重视错题的复盘。很多人刷题刷了一百道但错误的类型永远是那几类比如边界条件没考虑、数组越界、递归出口写错。复盘的目的就是把这些重复犯的错误找出来从根上改掉。至于语言的选择我多说一句腾讯很多部门以C为主但如果你用Java或者Go只要算法能力和基础扎实笔试一般不会卡语言。真正重要的不是语言本身而是你能不能把思路用代码清晰地表达出来。我个人在实际操作中的体会是这套2016年的腾讯笔试题二大概是所有流传出来的大厂真题里基础考点覆盖得最完整的一套之一。它不像现在的题那么卷算法但非常考“内功”。如果你能把这套题吃透再去看那些热衷于考“LRU Cache”“手写线程池”的题目心态会稳很多。最后再分享一个小技巧做题的时候把每一道错题都当成一次和出题老师的对话——想想他为什么要这么出他想考你什么他设置了什么陷阱。想通了这一层你会发现所谓的笔试题其实并没有那么可怕。