
如果把某MOOC平台上那门工科院校的C语言程序设计课程比作一场闯关游戏第10周的编程题在线测试绝对算得上你遇上的第一道硬核关卡。前面几周你可能已经经历了循环、数组、函数感觉“好像都听懂了”但真到了第10周题目一出来很多人会发现自己连题目都读不顺更别说下手写代码。这篇文章不打算剧透任何具体题目只想把这一周的考试规律、在线判题系统的脾气以及我用过的做题和排错策略完整整理出来。无论你是正在跟这门课的学生还是自学C语言想检验进度的人看完这篇应该能少踩几个坑少熬几个夜。1. 第10周编程题到底在考什么1.1 从课程进度看第10周的知识坐标大部分高校的C语言课程都安排在16周左右第10周正好处在一个很微妙的位置语法层面顺序、分支、循环、数组、函数这些基础已经讲完字符串也露过脸了接下来要接触的往往是指针、结构体或者一个综合性的项目练习。换句话说第10周不是“新知识刚讲完”的状态而是“旧知识开始串联”的阶段。这和编程题在线测试的出题思路是对应的。前面几周的题目通常只考一个点比如“用函数求阶乘”“用循环打印图形”你只要死死盯住一个知识点就能过。到了第10周题目开始玩组合拳一道字符串题可能同时要求你处理字符数组、调用标准库函数、注意指针越界一道结构体题可能既要你用typedef定义类型又要你写排序逻辑还得考虑交换结构体时会不会把数据弄丢。我在跟这门课的过程中明显感觉到第10周是很多人第一次出现“听课能听懂做题没思路”的断层。这个断层背后有个很重要的原因前几周大家对“C语言的语法”有意识但对“C语言的数据在内存里长什么样”没概念。到了第10周一旦涉及字符数组、结构体、指针内存布局就成了决定代码对错的关键。你不理解字符串是以\0结尾的一串连续空间你写出来的字符串遍历就可能多走一位或漏掉一位不理解结构体是一次性分配了一块连续内存你在交换两个结构体变量时就可能写出一个字段一个字段赋值的笨办法。所以第10周的知识坐标可以简单概括成一句话从“学语句”转向“用内存”。理解了这个转变后面的所有题型分析才有了立足点。1.2 在线测试常见题目类型根据我周围同学反馈和我自己在刷题过程中遇到的情况第10周的在线测试题大致集中在这么几类题目类型考察核心常见失分点字符串/字符处理字符数组、指针遍历、库函数忘记换行符、\0处理不当一维数组操作插入、删除、逆序、查找越界、覆盖未处理的元素结构体定义与排序typedef、结构体成员访问类型名与变量名混淆指针传参与修改实参地址传递、间接访问没有传地址实参没改变多组数据输入处理while循环配合scanf不会处理输入结束变量未重置这五类并不是平均分布的。字符串处理出现频率最高因为字符数组在C语言里有太多边界细节可以挖。比如一道“统计一行字符串中单词个数”的题看起来简单吧但空格连续的字符串、只有空格的字符串、行首行尾空格这三种情况就能筛掉一大批人。结构体排序出现的频率也很高因为第10周往往是结构体的第一次正式亮相出题人想看看你有没有真正理解“结构体是一种自定义类型”。数组插入和删除这类题也需要注意它和“内存搬运”有关。最典型的错误是删除一个元素时后面的元素往前移动但最后一个位置没处理导致输出多个垃圾值。指针传参类题目更像一个“笔试陷阱”很多同学在函数里改了参数出来一打印原变量纹丝不动这就是没搞懂C语言是值传递。1.3 第10周和前几周出题逻辑的差异如果说前几周的题是“背模板就能过”那第10周就是“理解了才能过”。我举个例子前几周出“用函数计算两点距离”你只要记住公式把参数传对就完事。但第10周的题目描述会明显变长经常给你一个场景比如“某班级若干学生的成绩信息输出按总分从高到低排序后的名单”然后附带一组样例输入和样例输出。很多新手拿到这种题第一反应是直接对着样例写死逻辑。比如样例里只有3个学生他就写了三个变量。这种做法在第10周几乎必挂因为判题系统会准备很多组数据你的程序必须能处理任意数量的输入而不是只处理样例。第10周开始你必须养成一个习惯把题目当作“需求说明书”来读而不是把它当作“固定的输入”。题目的描述里哪些是固定值哪些是可变范围哪些是边界条件这些都要在读题时圈出来。这一步做不好后面所有代码都白写。2. 在线判题系统真正的评分员不是人2.1 自动判题是怎么工作的先把这个机制讲透。你提交的C源码会被在线测试平台放到一台服务器上用指定的编译器常见是GCC编译成可执行文件然后平台会运行你的程序依次给它发送多组输入数据并把你的程序的输出和平台预先存储的“标准输出”做比对。比对通过这道题就算ACAccepted通过比对不一致就会返回一个错误类型比如Wrong Answer、Presentation Error这些。这个机制最要命的一点是比对结果不是“人眼扫一眼差不多就行”而是逐字节比对。你多打了一个空格少打了一个换行哪怕结果数字完全一致判题系统也只会冷冷地给你一个Wrong Answer。很多初学C语言的同学第一次栽跟头就栽在这里。还有一个很多人没意识到的问题判题系统是在“干净环境”里跑你的程序。它不会关心你本机的编译器是不是有点“宽容”它对标准的要求是严格按照语言规范来的。你的代码里写了一个没有初始化的局部变量你本机跑可能每次恰好都是0所以输出碰巧对了但到了服务器上那个变量在内存里的初始值可能是随机的垃圾值你的程序直接输出一堆莫名其妙的大数字。这类问题在第10周特别容易出现因为字符数组、指针、结构体这些数据结构的定义本身就容易让人忽略初始化。2.2 输入输出里的隐形陷阱刚才说了逐字节比对那输入输出里到底有哪些坑我一个个说。第一个坑是scanf和字符输入的缓冲区问题。很多题要求你读取单个字符比如按学生性别输入F或M。如果你在前一个scanf里读入了一个整数缓冲区里还留着一个换行符紧接着你用scanf(%c, ch)去读读到的就不是你想要的字符而是那个残留的换行符。这种情况很经典解决方案也简单在%c前面加一个空格写成scanf( %c, ch)这个空格会自动跳过空白字符。这个知识点教科书里可能只是一笔带过但如果不自己踩一遍真的很难记住。第二个坑是多组数据的输出格式。很多第10周题目都会要求你处理“多组测试数据直到文件结束”然后每组数据之间要么用一个空行隔开要么每组数据前都输出“Case 1:”这样的前缀。输出前缀的时候最容易犯的错误就是把前缀写在循环外或者把换行符的位置写错。你要记住一条判断标准如果题目的输出样例里有空行那一定意味着你的程序也要输出空行如果样例里没有那你的程序多一个空行也会被判错。第三个坑是浮点数精度。第10周有些题会涉及平均数、比例之类的计算要求保留两位小数。用printf(%.2f, ...)是最基本的。但要注意判题系统比对的是文本而浮点运算在不同机器上可能有极微小的差异加上你用的可能是double而用来做除法的整数变量被写成了int结果就悲剧了。尽量避免用float统一用double整数除法场景记得强转。第四个坑是用gets还是fgets。很多老教材喜欢用gets读一行字符串但C11标准已经把它移除了在线判题平台的编译器很多默认就是按较新的标准编译的你写gets可能直接编译错误。更稳妥的写法是用fgets它能指定读取的最大长度避免缓冲区溢出。第10周字符串题多这个点必须提前改过来。2.3 本机通过但提交不过的典型情况我在第10周前后身边至少有五六个同学遇到过同一个问题在IDE里运行样例输出完全正确一提交就被判错整个人懵在原地。我把最常见的几种原因列个清单编译器差异本机可能默认支持C99甚至更新的标准而平台用的是C89/C90标准。比如在C90里变量声明必须放在代码块开头你中间突然冒出一个int i;就可能编译错。未初始化的变量这是最大的隐性杀手。特别是指针、数组你不初始化就使用本机可能运气好没出事服务器上直接段错误。数组开小了题目说字符串最长1000你开了个char a[100];样例里字符串短所以没暴露判题系统的构造数据里出现长串就直接越界轻则数据错乱重则程序崩溃。int溢出乘积、累加时如果数值范围超过int上限约21亿你本机用小数据测没事大数据直接变成负数。main函数返回值或参数写错比如写了一堆花哨写法平台要求int main()你写void main()或者不写return有些编译器能过有些直接报错。这些情况加在一起给第10周的学生造成的心理冲击是巨大的。因为你会觉得代码明明“逻辑对了”怎么会错。我的建议是提交之前自己想一遍你的代码里有哪些地方依赖“本机运气”比如有没有变量没初始化、数组长度是否足够覆盖题目给的边界、循环里有没有可能越界。把这些隐患主动找出来比反复编译运行样例要有效得多。3. 三道典型题的完整拆解我说过不会剧透具体考试题但围绕第10周的核心知识点我整理了三道“高度仿真”的典型练习题。每一道都对应着在线测试的高频失分点代码写完以后我会特意标注几个容易被忽略的细节。建议你输入IDE里自己敲一遍而不是复制粘贴。3.1 字符串单词统计一道能卡住半数人的基础题题目描述通常是这样的输入一行字符统计其中单词的个数单词之间可能包含多个空格。这题考的是字符数组遍历和状态切换。#include stdio.h #include string.h #include ctype.h #define MAX_LEN 1024 int main() { char line[MAX_LEN]; while (fgets(line, sizeof(line), stdin) ! NULL) { int count 0; int in_word 0; int i 0; while (line[i] ! \0 line[i] ! \n) { if (!isspace((unsigned char)line[i])) { if (!in_word) { count; in_word 1; } } else { in_word 0; } i; } printf(%d\n, count); } return 0; }这里面的关键点有三个。第一个为什么用fgets而不用scanf因为scanf(%s, ...)遇到空格就停止根本读不了整行。第二个为什么遍历时还要判断line[i] ! \n因为fgets会把输入末尾的换行符一并读进来如果这一行只有一个换行符你不加判断那个换行符会被当成一个字符处理。第三个为什么用isspace而不用line[i] 因为输入里可能混着制表符Tab制表符也是空白字符用isspace一次性覆盖。很多人这题会栽在“行首有空格”或者“行尾有多个空格”这组测试上。如果不用in_word这个状态变量而是看到空格就判断前一个字符是不是空格代码会变得非常啰嗦而且容易漏掉边界。用一个状态标志位这是字符串处理里的经典套路也是第10周之后每次做题几乎都会用到的思维。3.2 学生成绩表排序结构体与排序的第一次正面交锋套路化的题目是输入若干学生的姓名和三科成绩按总分降序输出姓名和总分如果总分相同按姓名字典序升序输出。这个题一出现基本就是第10周在线测试的压轴题了。#include stdio.h #include string.h #define MAX_STU 100 #define NAME_LEN 64 typedef struct { char name[NAME_LEN]; int score1; int score2; int score3; int total; } Student; int main() { int n; Student stu[MAX_STU]; while (scanf(%d, n) ! EOF) { for (int i 0; i n; i) { scanf(%s %d %d %d, stu[i].name, stu[i].score1, stu[i].score2, stu[i].score3); stu[i].total stu[i].score1 stu[i].score2 stu[i].score3; } for (int i 0; i n - 1; i) { for (int j 0; j n - 1 - i; j) { if (stu[j].total stu[j 1].total) { Student tmp; tmp stu[j]; stu[j] stu[j 1]; stu[j 1] tmp; } else if (stu[j].total stu[j 1].total) { if (strcmp(stu[j].name, stu[j 1].name) 0) { Student tmp; tmp stu[j]; stu[j] stu[j 1]; stu[j 1] tmp; } } } } for (int i 0; i n; i) { printf(%s %d\n, stu[i].name, stu[i].total); } } return 0; }这题值得讲的细节非常多。第一结构体变量可以直接用等号赋值。很多新手会一个一个成员地去复制写出一屏幕代码其实tmp stu[j]这种写法在C语言里是合法的前提是左右两边类型一致。第二排序时用冒泡排序n很小时完全够用别一上来就惦记着快速排序。在线测试考的是正确性不是炫技冒泡排序的可读性对新手更友好。第三字符串比较必须用strcmp不能直接写if (stu[j].name stu[j 1].name)因为数组名是指针常量你比较的是地址而不是内容。这个错误我见过太多人犯几乎成了结构体排序题的“标配陷阱”。还有个隐蔽问题scanf读了%d之后马上跟着%s读字符串这中间是安全的因为%s天然会跳过前导空白。但如果题目改成先读一个整数再用fgets读一行字符串那你就必须在读整数之后手动清掉缓冲区里的换行符否则fgets直接读到一个空行。这是多组数据输入时的经典衔接问题。3.3 指针实现数组逆序理解传址的第一课第10周很多题目开始引入指针最常见的一个练手方向就是用指针来实现数组逆序。很多同学会在“为什么直接在函数里交换数组元素主函数里却没变”这个问题上卡住。#include stdio.h #define MAX_N 100 void reverseArray(int *arr, int n) { int *left arr; int *right arr n - 1; while (left right) { int tmp *left; *left *right; *right tmp; left; right--; } } int main() { int n; int arr[MAX_N]; scanf(%d, n); for (int i 0; i n; i) { scanf(%d, arr[i]); } reverseArray(arr, n); for (int i 0; i n; i) { if (i 0) { printf( ); } printf(%d, arr[i]); } printf(\n); return 0; }关键点在于函数参数写成int *arr实际上数组名传入函数时“退化”成了指向首元素的指针。你在函数里通过*left和*right修改的是这块内存里的值所以主函数里的数组会跟着变。理解了这一点你就知道为什么在函数里不能直接写arr来改变主函数里的“数组名”——数组名本身不是变量它是一个常量地址。最容易被忽视的边界条件是n1甚至n0的情况。当n为0或者1时left和right要么相同要么right已经越界。题目如果没有明确说n一定大于1你就得自己判断。很多提交失败的代码不是主逻辑错而是没有处理这种极端小数据。这一点对在线测试特别重要判题系统一定会构造边界数据来测你。4. 常见报错与排查速查手册4.1 判题结果英文单词到底什么意思在线测试平台上你提交以后会看到一个英文缩写的结果。第一次遇到时谁都懵赶紧看下表报错标识含义优先排查思路ACAccepted通过恭喜下一题WAWrong Answer答案错误逻辑问题或输出格式问题用边界数据自测PEPresentation Error格式错误输出多了/少了空格、换行逐字对齐样例TLETime Limit Exceeded超时是否有死循环算法是否太慢RERuntime Error运行错误数组越界、野指针、除零重点查边界CECompile Error编译错误看平台给出的编译日志通常是语法或标准问题MLEMemory Limit Exceeded超内存数组开得过大或递归层数过多很多人会把PE和WA混为一谈。实际上PE是格式问题比如你在每行输出后面多了一个空格或者少了一个换行WA则是你的输出内容和标准答案在内容上不一致。但要注意有些平台对PE的判罚会直接当成WA处理所以你不能指望系统帮你区分只能在写代码时就严格要求输出格式别给自己留“差不多就行”的后路。4.2 我自己最常用的三招排查法第一招加打印但提交前一定删掉。当你不确定某个循环里变量到底变成什么值了就在关键位置插入printf。这个方法看起来笨却是定位逻辑错误最直接的方式。不过你要养成习惯调试代码提交前必须清理干净否则输出里多了一行调试信息直接WA。第二招用“刁钻输入”自测。样例能过不算数你得自己造几组数据最大数据量、最小数据量、空数据、重复数据、字符串特别长、特殊字符混在里面。拿数组逆序那题举例你至少要测n0、n1、n100这三组。很多题目卡住的不是常规情况而是边界。第三招重读题目而不是重读代码。这个经验价值非常大。你盯着代码看了半小时找不出bug十有八九是你理解错了题目要求可能是输出格式、排序顺序、比较规则甚至只是一个词的差别。这时候把题目重新读一遍逐句对照你的输出逻辑往往比死磕代码高效得多。我见过不少同学花一个小时改代码最后发现是题目要求先输出平均数再输出最大值他做反了。4.3 一周刷题的时间安排建议第10周的在线测试通常不是只考一次它可能包含多道题限期一周左右完成。我的建议是别把所有题目留到截止前一天的晚上。那种状态下你连报错信息都来不及思考更不用说归纳经验。正常节奏应该是这样的周一、周二先复习本周的课堂代码和课件里的例子把指针和结构体的语法细节过一遍周三、周四每天做1到2道题每道题留出足够时间调试周五把做过的题再复盘一遍重点看那些一次就过但你不完全理解的代码把它们删掉重新写一遍直到能不查资料默写出来周六、周日再集中解决没做出来的题并且把常见的模板整理到一个文档里。到了真正测试的时候你手上有现成的“读入模板”“排序模板”“字符串处理模板”上手速度会快很多。这里还要一个记忆小技巧每次遇到一条新报错或新坑用一两句话记下来。比如“scanf读字符前加空格跳过换行符”“结构体比较名字要用strcmp”“fgets会读入换行符”等等。积累到十几条以后你就拥有了一本自己的“踩坑字典”下次做题先翻一遍很多问题都能提前避免。这个方法我后来推荐给周围很多同学反馈都很好。我个人在刷到第10周时最大的体会是编程题在线测试从来不只是考验你“会不会写C语言”它同时考验你的严谨程度、读题能力和耐性。你写的代码不是给人看的是给机器看的机器不关心你的思路多巧妙只关心输出对不对。我之前也经历过一道题反复提交七八次WA删了重新写再提交还是WA最后发现只是输出末尾少了一个空行。那种感觉确实折磨人但恰恰是这么一次次的打磨才让我真正开始理解C语言里那些让人头疼的内存、指针和边界问题。最后再分享一个小技巧当你卡在某道题超过30分钟时果断把代码最小化先写一个最简单的、只处理一个测试用例的主干逻辑跑通以后再逐步加功能。很多第10周的题目功能并不复杂复杂的是你一口气把输入、处理、输出全写完一旦出错到处都在冒烟反而无法定位问题。从最小可运行版本开始迭代这套思路不仅适用于这门课你以后写任何程序都用得上。