ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C++入门笔试题合集(二):绕开const、指针与数组的高频陷阱

C++入门笔试题合集(二):绕开const、指针与数组的高频陷阱 一份C入门级笔试题合集能写出来的原因往往是出题人把最基础的知识点翻来覆去考出花来。说句实在话我在帮团队筛简历和出笔试题的过程中见过太多候选人简历上写着“熟悉C”结果连最基础的const用法、指针和数组的关系都讲不清楚。所以这篇合集二我打算换一种思路不再像传统题集那样简单堆题目而是把每一类考点背后的考察意图、常见翻车现场、以及你拿到题之后应该怎么分析全部拆开来讲。适合正在准备C入门级笔试的在校学生、刚转行的朋友以及那些想自查一下C基本功到底扎不扎实的开发者。看完之后你至少能摸清出题人的套路知道哪些知识点是必须拿分的。1. 基础语法与输入输出全场最容易被轻视的失分点1.1 变量初始化与默认值一条三分的送命题入门级笔试几乎必考一道关于“全局变量和局部变量默认值”的题。很多教程讲过int、double这些内置类型的变量不初始化就会得到随机值但笔试往往更阴他会给你一个全局数组问你里面元素默认是什么。全局变量和静态局部变量会被编译器零初始化这没问题。但只要变量定义在函数内部比如int main() { int arr[10]; for (int i 0; i 10; i) { cout arr[i] ; } return 0; }这里的arr就是栈上的“脏数据”里面的值完全取决于程序运行前的栈内容可能凑巧是0但大多数时候是随机值。我在实际帮人Review笔试答案时发现很多人真的会写“默认为0”这题一旦出成选择题是标准的扣分项。正确做法是写题时养成习惯局部变量一律显式初始化哪怕你只想赋个0。笔试不是写生产代码但把初始化写清楚阅卷人对你的第一印象就是“稳”。还有一种问法是“局部变量和全局变量同名时会怎么样”。这就涉及到名字遮挡的问题。C里局部变量会隐藏全局变量你想访问全局变量就得用作用域运算符::。曾经有笔试直接给这段代码int x 10; int main() { int x 20; cout x ::x endl; return 0; }答案是20 10。不要笑我见过不少人在这里翻车主要原因是平时写代码很少用到::x这种写法考场上看到陌生语法就懵了。这种题本质上考察的是作用域规则你只要记住“局部优先::强制全局”这个口诀基本不会错。1.2 cin读入与换行符纠缠getline的隐藏坑入门级笔试爱考输入输出特别是“读一整行”这类要求。很多新手会被空格卡住因为cin s遇到空白字符就停了根本读不了带空格的字符串。解决方法是getline(cin, s)但这里有个特别典型的坑用cin n;之后紧接着getline(cin, str)你会发现str是空的。原因很简单cin n读到数字后末尾的换行符还留在输入缓冲区里getline一上来就碰到这个换行符直接返回空串。处理方案是在两者之间加一句cin.ignore()把那个多余换行符清掉。笔试题经常把这一点包装成“下面程序输出什么”的选择题如果你不知道这个机制答案会差很远。我自己在准备这类内容时反复强调过一件事入门级笔试其实不考多难的算法反而是在这些IO细节上疯狂扣分。因为面试官心里清楚一个连标准输入输出都处理不干净的候选人写业务代码时大概率会埋更多雷。别问我为什么知道我刚带新人时光是教他们用getline正确读取混合输入就花了不少功夫。这套题合集二里第一组题核心就是帮你把这些细节全部打通。2. 字符串、数组与指针的经典综合区2.1 字符串数组初始化char数组、char指针、string的纠葛C笔试对字符串初始化的考察频率极高我必须把它单独拎出来讲。常见题型是让你区分这三条语句char s1[] hello; char* s2 hello; string s3 hello;s1是字符数组会在栈上分配6个字节包含结尾的\0可以修改里面的内容。s2是指向字符串字面量的指针问题在于字符串字面量通常存放在只读数据区你如果试图s2[0] H很可能会崩溃或者产生未定义行为。很多入门教材会建议你把s2写成const char* s2就是为了防止这种修改。s3是标准库std::string对象动态管理内存用起来最安全笔试里如果允许你自由选择直接用string就完了。有一个相关考点是sizeof和strlen的区别。sizeof(s1)返回的是整个数组的大小也就是6因为包括末尾的\0strlen(s1)返回的是字符串长度5不算\0。但如果用char* s2 hello;sizeof(s2)在64位系统上返回的是8那是指针本身占的字节数和字符串长度没关系。这个区别在笔试中会以“求输出值”的形式出现答错率超高。2.2 数组名和指针名看似相等实际差一个维度入门级笔试必考的一个逻辑题int a[5];那么a和a是什么关系在这条题上翻车的人非常多原因是a代表数组首元素的地址类型是int*而a是整个数组的地址类型是int(*)[5]。虽然两者在数值上相等但在指针运算上是两码事int a[5] {1, 2, 3, 4, 5}; cout (a 1) endl; // 地址偏移 4 字节 cout (a 1) endl; // 地址偏移 20 字节换句话说a 1指向a[1]a 1直接越过了整个数组。题目可能会进一步问你*(a 1) - a的值这种计算题其实就是考指针步长。我建议你们在准备笔试时把“数组名是首元素地址数组名是整个数组地址”这句话写在笔记最显眼的位置因为从它身上可以衍生出一系列送分题和陷阱题。2.3 字符串转数字与数字转字符串手写实现是重头戏C笔试不会只让你调用库函数比如atoi和stoi更多时候会要求手写一个转换函数。为什么因为手写字符串转数字能考察你对字符编码、边界处理、正负符号的理解。标准写法思路如下int myAtoi(const char* str) { int sign 1; long long result 0; while (*str ) str; if (*str - || *str ) { if (*str -) sign -1; str; } while (*str 0 *str 9) { result result * 10 (*str - 0); if (result INT_MAX) { return sign 1 ? INT_MAX : INT_MIN; } str; } return (int)(sign * result); }注意几个细节跳过空格、处理正负号、判断溢出。我见过太多人在写这道题时忘了考虑溢出导致结果一大了就变成垃圾值这在面试官眼里非常扣分。数字转字符串相对简单核心就是不停地取模和除10最后反转结果。你可以在笔试前把这两个函数多抄几遍因为它能一次性覆盖字符处理、整数运算、边界保护三块基础能力性价比极高。3. 排序与基础算法入门级笔试的拿分高地3.1 冒泡排序和选择排序的差异别只会背代码入门级笔试的常客是“请手写冒泡排序”偶尔会让你写选择排序。这两者的区别在于交换次数和稳定性。冒泡排序是相邻元素两两比较如果顺序不对就交换一轮下来最大元素浮到最后平均时间复杂度O(n²)但它是稳定排序。选择排序则是每一轮找到最小元素的下标然后跟当前位置交换交换次数少很多但不稳定因为相同元素相对顺序可能被破坏。我建议你不但要会默写还要能讲清楚“冒泡排序第k轮结束后最后k个元素已经有序”这个性质。笔试如果考“排序多少轮后数组变成什么样”你得能肉眼模拟。这里有个小技巧手写冒泡排序时可以在内层循环加一个flag如果某一轮没有发生任何交换说明数组已经有序就可以提前终止。这不算什么高级优化但能体现你确实理解了排序过程而不是死记硬背。3.2 快速幂算法看起来高级写起来很简单快速幂几乎是每套C笔试基础题里必涉及的考点原因是它能考察递归/迭代、二进制思维和取模运算。快速幂本质是把幂指数拆成二进制比如计算a^1313的二进制是1101所以a^13 a^8 * a^4 * a^1。用迭代写法如下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; }这道题在笔试中一般不直接写“求a的n次方”而是包装成一个数论问题比如“计算n个整数的最小公倍数结果对某个大数取模”。因为求最小公倍数需要先算两个数的最大公约数最大公约数用欧几里得算法辗转相除法int gcd(int a, int b) { return b 0 ? a : gcd(b, a % b); }然后lcm(a, b) a / gcd(a, b) * b。这里先除后乘能避免中间结果溢出笔试的隐藏考点也在这。很多人在纸上写a * b / gcd(a, b)虽然数学正确但在数比较大的情况下容易整型溢出丢分。作为入门级考生最好一开始就养成“先除后乘”的习惯。这套题合集二里我会把快速幂和gcd结合起来因为你一旦掌握这两个工具后面的数论题基本就都能做了。3.3 递归与斐波那契必须知道“慢”在哪里入门级笔试让你写斐波那契数列的递归版本绝对是钓陷阱。很多人直接写int fib(int n) { if (n 1) return n; return fib(n - 1) fib(n - 2); }答案本身没错但时间复杂度是O(2^n)因为大量的子问题被重复计算。如果笔试题问你“n40时为什么跑得极慢”你得能解释。更稳的写法是用迭代动态规划把中间结果存起来long long fib(int n) { if (n 1) return n; long long a 0, b 1, c; for (int i 2; i n; i) { c a b; a b; b c; } return b; }这题目其实在考你对递归和迭代的理解而不只是背诵。我见过太多人把递归版写得飞起但笔试要求“尽可能高效”时他们还是写递归这就是典型的没吃透原理。4. C特性与面试加分项从入门到进阶的过渡4.1 设计模式入门单例模式为何高频出现虽然是入门级笔试题但设计模式偶尔会以最简单的形式出现尤其是单例模式。为什么笔试喜欢考单例因为它能同时考察构造函数访问权限、静态成员变量、多线程安全等多个C基础点。经典懒汉式单例可以写成class Singleton { public: static Singleton getInstance() { static Singleton instance; return instance; } Singleton(const Singleton) delete; Singleton operator(const Singleton) delete; private: Singleton() {} };这种写法利用C11之后局部静态变量的线程安全初始化机制代码简单又安全。笔试中你如果能写出这种“Meyers Singleton”面试官通常会眼前一亮。但你要能解释为什么把拷贝构造和赋值操作删除否则可能被追问到哑口无言如果不删除外部就能拷贝出第二个实例单例就不“单”了。4.2 多线程与C并发基础atomic、ABA问题与线程创建入门级笔试对多线程的考察往往不会太深但经常考std::thread的基本使用、std::mutex加锁以及std::atomic的概念。比如下面的代码#include iostream #include thread #include atomic using namespace std; atomicint counter(0); void add() { for (int i 0; i 10000; i) { counter; } } int main() { thread t1(add); thread t2(add); t1.join(); t2.join(); cout counter endl; return 0; }如果用普通int counter结果大概率不是20000因为多线程同时读写会产生数据竞争。用atomicint可以保证自增操作是原子的输出才稳定是20000。笔试题会从这里延伸出“ABA问题”也就是CAS比较交换时一个变量从A变成B再变回ACAS会误认为它没变过。我记得热词里也有“aba问题c”说明很多人都在搜这个点它确实是并发编程中一个高频考词。入门级笔试不一定让你手写ABA案例但概念题完全可能出。你至少得知道这是无锁数据结构里的经典问题通常用增加版本号来解决。4.3 constexpr是哪个C版本引入的送分题也要有底气热词里很多人搜“constexpr哪个c版本引入的”这个问题相当直白答案是C11。constexpr修饰的函数或变量会在编译期就能计算出结果而不是运行时。它和const的区别要分清const表达“只读”不一定在编译期确定constexpr表达“常量表达式”必须能在编译期求值。笔试选择题可能会给你一段代码问哪个变量是编译期常量。下面这是标准例子constexpr int square(int x) { return x * x; } int main() { constexpr int n square(5); // 编译期就算出25 int arr[n] {0}; // 可用于数组大小 return 0; }如果你把constexpr记成const可能也能通过但理解层面完全不同。想拿稳这道送分题就记住一条C11引入constexpr它是“强化的const”强调编译期可求值。5. 完整模拟卷精讲把这些题目串成一套真题5.1 一套5道题的限时模拟从读题到写码的思路拆解这里我直接给你一套我设计的入门级真题覆盖上面讲过的所有考点。你可以先自己写一遍再看我下面的精讲。题目一输入一个整数n输出斐波那契数列第n项要求时间复杂度O(n)不递归。题目二给定一个字符串统计每个字符出现的次数并输出出现次数最多的字符。如果并列输出最先出现的那个。题目三实现函数bool isPalindrome(int x)判断一个整数是否是回文数。要求不能使用字符串转换。题目四手写单链表节点结构并实现一个函数反转链表。题目五给定N个整数求它们的最小公倍数结果对1000000007取模。题目一的核心是迭代求斐波那契。注意n可能很大需要使用long long甚至考虑取模。题目二可以借用std::unordered_map或者固定大小的字符数组注意“最先出现”这个边界千万别只统计次数而忽略顺序。题目三不能转字符串那就反转整数的一半比较前一半和后一半。题目四的链表反转是经典题目每个入门学习者都应该手写三遍以上。题目五会用到前文讲的gcd和快速幂取模因为最小公倍数很容易超过整型范围必须一边算一边取模。5.2 模拟题精讲题目一与题目三的完整实现题目一的参考代码long long fib_iter(int n) { if (n 1) return n; long long a 0, b 1, c; for (int i 2; i n; i) { c a b; a b; b c; } return b; }这题能考察你是否会控制变量边界条件是n0时返回0n1时返回1。如果有人写出递归就算能给正确答案也会因为效率太低被扣分。题目三的回文数判断思路是只反转后半部分bool isPalindrome(int x) { if (x 0 || (x % 10 0 x ! 0)) { return false; } int revertedHalf 0; while (x revertedHalf) { revertedHalf revertedHalf * 10 x % 10; x / 10; } return x revertedHalf || x revertedHalf / 10; }负数直接false个位为0且不是0本身的数也直接false因为这些数以0结尾不可能构成回文。这题考点是整数拆位和反转技巧很能拉开基础差距。5.3 模拟题精讲题目四链表反转的手写模板链表反转是入门级笔试的热门题写法有很多这里我推荐一个比较容易理解的三指针迭代法struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; ListNode* reverseList(ListNode* head) { ListNode* prev nullptr; ListNode* cur head; while (cur ! nullptr) { ListNode* nextNode cur-next; cur-next prev; prev cur; cur nextNode; } return prev; }核心在于先保存当前节点的下一个节点再把当前节点的next指向前一个节点最后整体向后移动。我见过太多人在这里琢磨不清“先改next再移动”的顺序导致链表断掉。建议你在草稿纸上画三个节点一步一步模拟笔试时如果实在紧张画图是最稳的。6. 常见问题与排查技巧实录笔试题里那些“毁所有”的细节6.1 新手在笔试中最常见的7个翻车现场我总结这些年批改笔试题的经验发现新手最容易在下面这些地方翻车使用未初始化的局部变量题目要求输出时直接得到垃圾值。忘记字符串结尾的\0会导致越界访问。递归函数没有写终止条件导致栈溢出。数组下标从1开始而不是从0开始或者越界访问arr[n]。使用cin和getline混用时没有处理缓冲区残留换行符。返回值类型写错比如该返回long long却写了int导致溢出。手写单例模式时忘记把拷贝构造删除留下隐患。对照这个清单去检查自己的笔试答案往往能多捡回好几分。尤其是最后一条我见过太多次代码看起来没问题但一旦追问“你的单例能被拷贝吗”现场就卡壳了。6.2 笔试实战里的时间分配与检查小技巧入门级笔试题一般不会给特别长时间我建议按“先易后难、先接口后实现”的顺序做。先浏览所有题目把能秒杀的题快速写完比如常量表达式、数组大小、字符串初始化这类概念题。再去做字符串转数字、排序、链表反转这类代码题。最后如果还有时间回过头检查边界条件。答题时养成一个习惯先写函数签名和注释再补实现。这样即使没写完阅卷人也能看到你的思路。变量命名虽然不要求完美但至少要语义化不要出现a、b、c满天飞。代码风格在笔试中占的隐形成分很高。另外手写代码的时间复杂度最好标注在注释里面试官一眼就能看出你是否有算法意识。比如你在冒泡排序旁边写“时间复杂度O(n²)可加flag提前结束”这就能显示出你不是单纯背代码。6.3 工具与调试建议从笔试到上机的平滑切换有些笔试是直接在线上平台写代码运行所以你在平时准备时就要熟悉一套好用的环境。很多人搜“vscode配置c/c环境”我建议在本地至少配置好g编译器和调试器。VSCode里装好C/C扩展写好tasks.json和launch.json按F5就能断点调试这对排查指针和数组越界问题帮助很大。不过入门级笔试现场往往没有IDE更考验“裸写”能力所以我会刻意在平时练习时用最简单文本编辑器加命令行手动编译运行这样上了考场不会慌。如果遇到“C怎么只能加代码的情况下减少运行时间”这类优化题常见技巧包括把endl换成\n减少缓冲区刷新把cin的同步关闭加上ios::sync_with_stdio(false); cin.tie(nullptr);。这属于很实用的压榨输入输出速度的办法。但注意很多笔试题要求你保留#include iostream这些优化不会影响逻辑只会提速。7. 进阶扩展从入门笔试到实战开发的三个补强方向7.1 智能指针与RAII笔试加分项更是代码卫生习惯入门级笔试偶尔会问“C里如何安全地管理动态内存”。标准答案是智能指针尤其是std::unique_ptr和std::shared_ptr。unique_ptr表示独占所有权不能拷贝只能移动shared_ptr表示共享所有权内部使用引用计数最后一个持有者析构时释放内存。笔试写代码时如果必须使用动态内存优先用智能指针这会让阅卷人觉得你有现代C意识。RAII是“资源获取即初始化”的缩写核心思想是把资源的生命周期绑定到对象的生命周期上。这个概念听起来抽象但其实就是利用构造函数和析构函数自动管理资源。比如文件句柄、互斥锁、动态内存都可以封装到对象里出了作用域就自动释放。你在笔试里哪怕只是简单提到这个思路都能和其他候选人的水平拉开距离。7.2 回调函数与函数指针理解“把函数当参数传”热词里高频出现“c回调函数例子”说明这也是一大痛点。回调函数的本质就是“把一段逻辑作为参数传给另一段代码在合适的时机调用它”。入门级笔试可能不会直接考复杂回调但会考函数指针或std::function。一个最简单的例子#include iostream #include functional using namespace std; void process(int x, functionvoid(int) callback) { cout processing x endl; callback(x * 2); } int main() { process(21, [](int result) { cout callback result: result endl; }); return 0; }这个例子把lambda表达式作为回调传给process函数回调在计算完成后执行。考场上你会写std::function或者函数指针其中之一就算合格。理解回调不仅能帮你应付笔试也是以后写事件驱动代码的基础所以我建议把这段代码吃透。7.3 单调栈和常用数据结构速览为后续提升埋个伏笔虽然入门级笔试不一定考单调栈但既然热搜词里有“单调栈算法c”说明很多人在备考时遇到它了。这里我不深挖原理只告诉你它适合解决“找下一个更大(更小)元素”一类问题。笔试如果突然出现这种题你至少能认出来它属于栈相关问题的变种。入门考生可以先确保自己会栈和队列的基本操作比如用std::stack和std::queue模拟进出顺序这也是很多笔试题的前置知识。从数据结构角度入门级笔试最常考的是数组、链表、栈、队列、哈希表。哈希表对应std::unordered_map写题时非常方便。比如统计字符串字符频率一个unordered_mapchar, int就解决了。这类容器题只要会用接口基本没有难度千万别在这种题上因为忘记map[key]的用法而失分。根据我个人经验来看C入门级笔试的题目难度并不高真正让大多数人翻车的不是不会做而是不仔细、边界不清、思路混乱。这套题合集二虽然叫“笔试题”但更大的价值是帮你在准备阶段把所有基础点串成一张网。你按照这套框架把每个考点过一遍再去刷任何平台的入门级C笔试题都会轻松很多。最后再送你一个小技巧平时刷题时把每道题的易错点和思路写在代码注释里隔天重新做一遍比盲目刷十道新题都管用。
返回列表