如果你学过C语言,大概率听过“翁恺”这个名字。中国大学MOOC上浙江大学开的《C语言程序设计》,主讲人就是他。课讲得好是一回事,但真正让我对这门课的评价从“讲得不错”变成“强烈推荐”的,是配套的PAT习题集。
先说结论:只看视频不动手,等于没学。翁恺这套题不是课后作业那种应付式的几道题,而是直接对标浙大PAT考试体系的练习库。把这套题啃下来,你的C语言水平绝对比“看过一遍视频”的人高出一个明显的档次。
这篇文章我按自己的实际刷题经验来写,包括PAT体系到底考什么、刷题顺序怎么安排、哪些知识点最容易卡住、VSCode环境怎么配、以及这套“更新中”的题集怎么持续利用。全程干货,不讲虚的。
1. 翁恺这门课和PAT习题集,为什么能成为练C语言的标配
1.1 课程讲得好,但真正拉开差距的是配套练习
翁恺的课好在哪?好在“降维”。指针讲得清楚、结构体讲得明白,遇到容易懵的概念他会用特别朴素的例子给你掰开揉碎。很多初学者评价这门课“听的时候全懂,一写题全废”,问题恰恰出在练习量不够。
MOOC平台的课程视频本身是免费的,但《C语言程序设计》这门课的配套练习放在PTA平台(Programming Teaching Assistant,拼题A)。你在PTA上能搜到翁恺布置的作业题,也能找到PAT的历年真题。平常大家说的“翁恺C语言PAT习题”,通常指的就是这一整套题。
我自己的经验是:跟着视频学完数组和指针章节之后,直接去做PTA上的对应练习,正确率大概只有三成。很多题不是不会,而是写得出来但过不了测试点,或者根本不知道从哪下手。这是因为视频教的是语法和概念,而PAT题考的是“你用这些语法解决一个具体问题的能力”。这是两种完全不同的思维模式。
1.2 PAT题集和普通课后题的根本区别
普通课后题一般长这样:“请用指针实现两个数的交换。”这种题是围绕着知识点出的,你知道这章讲指针,基本就能写个大概。
PAT题不一样。它更像“需求描述”。题目不再提示你用哪个知识点,而是给一个完整的场景,比如“计算某年某月某日是这一年的第几天”“将一串英文单词按字典序输出”。你需要自己去判断:这题该用数组还是指针?该用冒泡还是该用别的方法?要不要处理边界条件?
这个“自己判断”的过程,恰恰是编程能力真正的分水岭。很多人看题就会、做题就废,就是因为缺少这种从“学知识点”到“解决问题”的转化训练。翁恺这套习题集,就是把转化训练做到了极致。
1.3 我实测下来的刷题收益
我去年从头到尾刷了大概80道翁恺课程配套的PTA题,再加30道PAT乙级真题,直观感受有三个:
第一,代码量明显上升。刚开始一道题写三五十行就头疼,刷到后面,一百行的程序也能耐心调完。第二,调试能力变强了。以前遇到报错就看不懂,现在看到“段错误”基本能猜到是指针越界还是数组访问越界。第三,对考试和面试的适应力变强。后来参加某厂的机试,题目风格跟PAT乙级非常接近,因为练过类似的题,上手特别快。
所以我的结论很直接:想学好C语言,翁恺的视频负责带你入门,PAT题集负责逼你上手。两者配合,才是完整的学习闭环。
2. PAT到底考什么——先从考试维度反推练习方向
2.1 PAT乙级和甲级,翁恺题集对应的层级
很多人搞不清PAT、PTA、乙级、甲级这些概念,先捋一下。
PAT是浙江大学主办的计算机程序设计能力考试(Programming Ability Test),分乙级、甲级、顶级三种难度。PTA是练习平台,上面除了课程作业,还有大量历年真题可以刷。翁恺的《C语言程序设计》习题集,难度基本对应PAT乙级到甲级入门之间的区间。
PAT乙级考什么?核心是“C/C++基础语法 + 基础数据结构的简单应用 + 常用算法”。字面上看着不难,但实际题目很活。我列一下乙级的高频考点:
- 基础语法:循环、分支、数组、函数、指针、结构体、文件读写
- 字符串处理:逆序、统计、查找、替换(乙级的重头戏)
- 基础排序:冒泡、选择、qsort使用
- 简单数学问题:最大公约数、最小公倍数、素数判断、分数化简
- 模拟题:按照题目描述一步步模拟过程,比如“打印沙漏”“数素数”这类题
甲级则在此基础上增加了数据结构(堆、栈、树、图)和STL(vector、map、set等)以及更复杂的算法。翁恺的C语言课基本不涉及STL(那是C++的内容),所以他的习题集主要训练的还是乙级这个层次。
2.2 评分机制:为什么你写了“正确答案”却不一定得分
PAT系题目全部采用“测试点判定”——你的程序会输入若干组测试数据,系统拿这些数据跑你的代码,和标准答案的输出做比对。全对给满分,只过一部分给部分分。
这个机制非常直接地暴露你代码的健壮性。举个例子,“求最大公约数”这种题,如果测试数据里包含0和负数,你没考虑,那就只能拿到“部分正确”。还有更常见的情况是“段错误”,多半是数组开小了或者指针没判空。
所以做题的时候不要只看“我这代码逻辑好像是对的”,而是要追着每个测试点想:这个测试点可能输入什么边界情况?我的程序能不能扛住?这一步想明白了,代码质量会立刻提高一个档次。
2.3 “部分正确”和“段错误”,是你最该认真面对的两个词
我刷翁恺习题集时,最怕看到的结果是“答案错误”,但后来明白了,最该认真研究的反而是“部分正确”和“段错误”。
“部分正确”说明你的思路大体对了,但某几个测试点没过。这时候要怀疑的不是整个逻辑,而是某个特殊情况没处理:数组越界了?循环边界没控制好?整数溢出了?输入格式跟题目要求不一致?这些问题是真实项目里最常见的bug来源。
“段错误”在C语言里几乎等于“你的指针或数组访问了不该访问的内存”。排查思路很直接:先定位是哪一行崩溃,再看是不是数组下标越界,或者指针没有初始化就使用。我见过太多初学者一看到段错误就懵,其实只要你愿意printf大法一行行打日志,几分钟就能定位到。
3. 刷题路线:从变量到指针,怎么安排顺序才不劝退
3.1 别按题号刷,按知识点刷
PTA上的题是按难度编号的,不一定适合所有人的学习进度。我的建议是跟着课程章节走,每学完一个知识模块就刷对应题。我按翁恺课程的知识点推进顺序整理了一张刷题优先级表:
| 阶段 | 知识点 | 刷题重点 | 目标 |
|---|---|---|---|
| 第一阶段 | 变量、表达式、输入输出 | 简单计算、格式输出、单位换算 | 熟练基本语法,看懂英文式输入输出要求 |
| 第二阶段 | 分支、循环 | 数字处理、图形打印、穷举类题目 | 掌握循环边界控制,敢写嵌套循环 |
| 第三阶段 | 数组 | 排序、查找、统计、矩阵操作 | 建立“用数组存数据”的直觉 |
| 第四阶段 | 字符串 | 单词统计、字符串逆序、字符替换 | 吃透字符串处理,这是乙级的核心 |
| 第五阶段 | 函数 | 封装功能模块、递归入门 | 养成函数化思维,别把所有代码塞main |
| 第六阶段 | 指针 | 数组与指针的关系、指针传参 | 理解指针的“地址”本质,克服恐惧 |
| 第七阶段 | 结构体、文件 | 学生成绩管理、文件读写 | 把多个数据打包处理,贴近真实项目 |
按照这个顺序刷,不会出现“这章还没学就碰到超纲题”的挫败感。
3.2 一道题至少刷三遍,效果远超连刷十道新题
很多人刷题图快,一道题看懂了就下一道,结果一周后回头看,啥也不会。我自己的经验是:一道题至少刷三遍。
第一遍:独立完成。不看任何题解,顶多翻教材确认语法。能写多少写多少,哪怕只能过一半测试点。第二遍:看优秀题解,对照自己的代码,找出差距。这个差距通常不是“他用了高级语法”,而是“他对边界情况的思考比你周全”。第三遍:隔一周,在不看自己原来代码的情况下重写一遍。能独立写出来,这道题才真正属于你。
这个过程看起来很费时间,但它是把“见过这道题”变成“会做这类题”的唯一路径。我刷了80道题,实际涉及到的代码量大概是240遍。量变到质变,就是这么来的。
3.3 典型题拆解:从“厘米换算英尺英寸”到“字符串逆序”
拿两道翁恺题集里的经典题目举例。
“厘米换算英尺英寸”。题目很简单:输入厘米数,输出对应的英尺和英寸。但很多人第一次做会卡在“不知道英寸是12进制”。这道题考的不是除法,而是你对“不同单位制之间换算”的理解——先算出总英寸数,再整除12得英尺,取余得英寸。这道题的关键是学会把实际问题的数学模型建对。
“字符串逆序”。看起来就是把字符串倒过来输出,但如果你直接用gets读入,可能遇到缓冲区问题;如果你用scanf("%s")读入,遇到空格就断了。这道题真正的考点是:如何处理带空格的整行字符串,以及逆序后是否要保留空格。能把这题十几个测试点全过,你对字符数组和指针的理解基本就到位了。
我想强调一下:这些题看着都不难,但测试点很刁钻。你能想到“输入只有一个空格”这种边界吗?你能想到字符串长度最大是多少吗?数组开多大才不会越界?这些才是PAT题真正训练你的地方。
4. 高频翻车点:字符串、指针、文件读写的排错实战
4.1 字符串:一半以上的“答案错误”都跟它有关
字符串处理是C语言的精髓,也是PAT乙级的绝对核心。翻车率最高的几个地方,我一个个说。
gets/scanf混用问题。gets会读取到换行符为止,但scanf("%s")读到空格就停。如果你先scanf再gets,gets会直接读到scanf留在缓冲区里的换行符,导致读不到内容。解决方案是读完之后用getchar()把换行符吃掉,或者统一用fgets。
strcpy越界问题。strcpy不会检查目标数组是否装得下源字符串。源字符串有100个字符,目标数组只开了50个,strcpy照样把100个字符写进去,在内存里直接踩过界。这个是最典型的段错误来源。用strncpy或者手动循环复制,比裸strcpy安全得多。
字符串末尾的'\0'。C语言字符串必须手动维护结尾的'\0'。很多初学者写循环拼接字符串,忘了在最后加'\0',printf输出的时候就一直往后读内存读到崩溃。这个坑几乎每个人都会踩一次。
4.2 指针:从“看着明白”到“用着不崩”,中间隔着一堆段错误
翁恺在课里说,指针就是“存储地址的变量”。这句话很好理解,但一写代码就崩。我总结了三类高频翻车场景。
第一个,声明了指针但不初始化就使用。int *p; *p = 5; ——p没有指向任何合法内存,你往一个随机地址写数据,必然段错误。正确做法是int arr[10]; int *p = arr;让指针指向一个真实存在的空间。
第二个,指针作为函数参数时忘记改内容。你想写个函数交换两个变量的值,结果传的是值不是地址:swap(a, b),函数内部换得开心,外面的a和b纹丝不动。C语言里想在函数内部修改外部变量,必须传指针,即swap(&a, &b)。这个考点在翁恺的习题里出现频率极高。
第三个,数组名和指针的关系没理清。数组名在很多场合会“退化”成指向首元素的指针,所以int *p = arr;完全合法。但arr本身不能整体赋值,p可以改变指向。很多人把这两者混为一谈,导致一些诡异编译错误。
4.3 文件读写:没判空就操作,等于在钢丝上走路
翁恺习题集里有一部分文件读写题目,比如从文件读入学生成绩、统计后输出到新文件。这块翻车点非常集中。
第一是fopen之后不判空。文件不存在、路径不对、权限不足,fopen都可能返回NULL。你不检查就直接fprintf或者fscanf,程序直接崩溃。正确写法是if ((fp = fopen("in.txt", "r")) == NULL) { printf("can't open file\n"); return 1; }。
第二是fscanf和fprintf的格式串写得不对。fscanf(fp, "%d", &n)这行里,n前面必须加&,因为fscanf要往n里写值。相反,fprintf(fp, "%d", n)里n不加&。很多人一个&用到底,编译都不报错但运行结果全乱。
第三是fclose遗漏。程序结束不关文件,数据可能没落盘,文件句柄也可能泄漏。在OJ系统里这不算大问题,但到真实项目里就是隐患。养成写fclose的习惯,不需要任何理由。
4.4 排查链路:一次“答案错误”到“满分解法”的完整过程
拿我自己刷过的“数字统计”类题为例,讲一下完整的排错链路:
- 第一次提交:答案错误。我检查了主逻辑,发现统计公式没写错。然后在本地测试了几组正常数据,都过了。
- 第二次思考:可能是边界情况。我把n=0、n=1、n为负数的情况都测了一遍,发现n=0时循环根本没进去,但应该输出0而不是什么都不输出。
- 第三次修复:给循环加上了对n=0的特殊处理。提交后变成“部分正确”。
- 第四次再查:发现题目要求的是“统计0的次数”,但我只统计了正整数的每一位,忘了处理整数0本身。修正后通过全部测试点。
整个过程大概花了40分钟,但收获特别大。这种东西是看任何题解都学不来的,只能靠你自己在“错误->定位->修改->再验证”的循环里积累。
5. 环境配置与提效技巧:在本地调试到满意,再上OJ提交
5.1 VSCode + MinGW-w64的安装,一次配好不折腾
刷PAT题不一定非得用在线编辑器。我强烈建议在VSCode里配置好本地C语言环境,原因很简单:能在本地打断点、看变量值、一步步调试,定位问题比纯看代码快得多。
在Windows上我推荐MinGW-w64编译器,配好之后VSCode里装C/C++扩展。配置的关键是两个JSON文件:
- tasks.json:定义编译命令。我用的是gcc -g 源文件.c -o 可执行文件名。-g这个参数一定要带上,它是生成调试信息用的,没有它断点功能基本失灵。
- launch.json:定义调试配置。把program指向刚才生成的可执行文件,externalConsole设为false,这样调试时输出会集中在VSCode的终端面板里,不会弹一个难看的老式命令行窗口。
配好之后按F5就能进入调试模式。左侧的“变量”面板可以直接看到每个变量的当前值,“监视”面板可以输入表达式实时计算结果。指针的值、数组的内容、结构体的字段,全都能看。配合断点,排查段错误的能力直接上升一个档次。
5.2 联机调试与本地测试的差异,务必注意
OJ平台的编译器版本、运行环境和你本地不一定完全一致。这就导致一个很经典的坑:本地跑得好好的,一提交就“编译错误”或者“运行超时”。
常见的元凶有几个:
- 用了非标准函数。比如gets在C11标准里已经被移除,本地MinGW-gcc可能还兼容,但OJ用的gcc版本一旦开了高版本标准,直接报错。改用fgets更稳妥。
- 数组开得太大且定义在main内部。大数组(比如int a[1000000])在main里声明会占用栈空间,可能造成栈溢出,程序运行到一半就崩。解决办法是定义成全局变量,全局变量放在静态区,空间比栈大得多。
- 输出格式问题。题目要求“每个结果占一行”,你输出时漏了换行,或者多了个空格,OJ照样判错。这种错误本地很难发现,因为本地你只测一组数据。解决办法就是仔细读题,把输出格式要求原封不动复刻。
5.3 刷题效率的两个小习惯
第一,准备一个常用的模板文件。框架包含stdio.h、stdlib.h、string.h的引入,main函数的固定写法,以及对fopen返回值检查的简化宏。每次做题在这个模板上改,省去反复敲头文件的时间。
第二,建一个自己的函数库。比如最大公约数、快速幂、冒泡排序、字符串逆序这些高频函数,独立封装好,多道题都能复用。写题的时候直接调用,速度飞快。更重要的是,封装的这个过程本身就在训练函数化思维,对提升代码整洁度帮助巨大。
6. “更新中”的题集,怎么持续利用才不浪费
6.1 “更新中”意味着什么:题库在持续扩容,你的刷法也要动态调整
翁恺这套题集标注“更新中”,不只是一句空话。PAT考试本身每年都有新题,PTA平台会不断把新题补充进练习库。这就意味着这个习题集不是“刷完就结束”的静态题库,而是一个长期需要维护的学习资源。
我把“更新中”理解成一个动态提醒:每隔一段时间回来看一眼新题,能帮你保持对C语言的敏感度。我已经工作一段时间了,还是会偶尔回PTA刷一两道新题练手感。每次刷完,都会发现自己对某个知识点的理解还有提升空间。
6.2 建立错题本的正确姿势:按错误类型归纳,而不是按题号抄一遍
很多人建错题本就是把错误代码和正确答案贴上去,完事。这种错题本除了自我感动没什么用。
更有效的方法是按“错误类型”归纳。比如我自己的错题本分四类:
- 边界条件遗漏:数组越界、循环边界、空输入、最大值最小值情况
- 字符串处理错误:缓冲区、'\0'、空格、大小写转换
- 指针使用错误:未初始化、野指针、数组名与指针混淆
- 格式错误:空格、换行、小数点位数、中英文标点
每次错题归入对应类型,并在旁边用一句话总结教训。一段时间后回看,你会发现自己的错误高度集中。针对性地练那几类题,效率比无差别刷题高太多了。
6.3 把PAT习题集的成果迁移到更多场景
这套题集的价值不仅限于应付翁恺的课程作业,它可以迁移到很多实际场景:
期末考试的C语言上机题,风格和PAT基础题高度重合,平时刷过题的人考试就是降维打击。考研复试的机试环节,很多学校直接用PTA或类似平台的题,练过翁恺这套题等于提前准备了。找工作时的在线笔试,尤其是嵌入式、后台开发、算法岗的方向,C语言题和PAT乙级风格相似度极高。我甚至在做自动化脚本、写Shell辅助工具时,都能感觉到当年刷PAT培养起来的“边界思维”在起作用。
所以说,这套题集其实是打底子用的。底子打好了,后续无论走哪个方向,都不会虚。
6.4 关于“怎么坚持下去”的一点实在建议
聊到这里必须说句实在话:刷题的过程一定会有卡住的时候,一道题盯着屏幕两个小时没有任何进展,这种体验谁都有过。
我自己的心得是:不会做的题,不要死磕超过30分钟。30分钟没思路,跳过去,看别人的解法,看懂之后隔两天自己独立重写一遍。看起来“逃避”了,实际上你在用“输入->理解->重组->输出”的方式真正消化这道题。
还有,每一次提交通过,哪怕只是“部分正确”,也要给自己一点正反馈。错题不是失败,是系统在告诉你“你的代码还有哪个角落没照顾到”。想明白这个道理,刷题就不再是痛苦的事,而是一个不断发现漏洞并修复漏洞的游戏。
如果你还在犹豫要不要刷,我的建议很简单:打开中国大学MOOC,把翁恺的课程加入学习计划;打开PTA,把那套C语言习题集调出来。不用想太多,先写第一题。剩下的,等你过了第一个全绿的测试点,自然会继续下去。