PTA是不少高校C语言课上都会用到的在线判题平台,“习题2-1 求整数均值”这一题,往往是很多人第一次正经写代码提交时碰到的“开胃菜”。题目要求非常直白:从键盘读入4个整数,输出它们的和,以及保留一位小数的平均值。看起来不到十行代码就能搞定,但PTA上这道题的通过率其实没想象中高。原因不是大家不会算平均数,而是刚接触C语言时,还没有建立“整数除法会丢精度”“printf格式必须和变量类型严格匹配”“输出格式多一个空格都不行”这些意识。这篇文章就把这题的考点、坑点、几种实现方式以及我实际调试中踩过的坑一次说清楚,适合刚学C语言的初学者,也适合想快速搞懂PTA判题规则的人。
1. 题面拆解与考点清单
1.1 题目到底让你干什么
先别急着写代码,把题目翻译成人话。原题描述一般是这样:在一行中输入4个整数,整数之间以空格分隔,然后在一行中按照“Sum = 和; Average = 平均值”的格式输出结果,其中平均值保留一位小数。
拆开来看,题目其实只给了四个关键信息:
| 关键信息 | 具体内容 |
|---|---|
| 输入数据 | 4个整数,空格分隔 |
| 需要计算的量 | 4个数的总和Sum |
| 需要计算的量 | 4个数的平均值Average |
| 输出要求 | 平均值保留一位小数,格式必须完全匹配 |
注意,题目说“平均值保留一位小数”,这句话决定了很多事情。举个例子,如果输入是“1 2 3 4”,总和是10,平均值是2.5,输出“Average = 2.5”。但如果输入是“1 1 1 1”,平均值是1,那就要输出“Average = 1.0”,而不是“Average = 1”。很多第一次提交的同学就是栽在这个“1.0”上,以为保留一位小数只是“有小数就保留”,整数就不用加“.0”,结果被PTA判了格式错误。
1.2 题面背后的四个知识点
这道题之所以被放在“习题2-1”的位置,是因为它一个题就覆盖了C语言入门阶段最基础的四个考点。
第一个是scanf的格式匹配规则。题目说“以空格分隔”,那scanf里就应该写成scanf("%d %d %d %d", &a, &b, &c, &d),注意格式串中%d之间是空格。有同学会写成逗号分隔,比如scanf("%d,%d,%d,%d"),那输入的时候就必须带上逗号,平台给的测试数据里全是空格,自然就匹配不上。实际上C语言的scanf格式串里,普通空白字符(空格、换行、Tab)都表示“匹配任意数量的空白字符”,所以"%d %d"和"%d%d"都能处理空格分隔的输入,但逗号就不行。
第二个是整数和浮点数的类型差异。4个整数相加,结果依然是整数,这个是没问题的。但平均值就不一定了,两个整数相除的结果,在C语言里要分情况看。如果除号两边都是整数,那结果也是整数,小数部分直接被“砍掉”,这在C语言里叫整数除法截断。想要得到带小数的结果,必须让除号两边至少有一个是浮点数。
第三个是printf的格式控制符匹配。输出整数用%d,输出浮点数用%f配合精度控制,比如%.1f表示保留一位小数。一旦格式符和变量类型不匹配,轻则输出错误,重则程序直接解析出乱码。
第四个是输出格式的“严格匹配”思维。PTA判题采用的是输出全文比对,你输出的字符串必须和标准答案完全一致,包括等号两边的空格、分号、换行。这种“死板”的规则,恰恰是初学者需要尽早适应的。以后不管是做OJ题还是写工程代码,输出格式都是对外接口的一部分,马虎不得。
2. 三个最容易踩进去的坑
2.1 整数除法会把小数“吃掉”
这是这道题最核心的坑,没有之一。C语言里,/运算符的行为取决于两边的操作数类型。如果两边都是整型,它执行的是整数除法,结果直接向零取整。比如7 / 4,结果是1,不是1.75;10 / 4,结果是2,不是2.5。向零取整的意思就是,正数直接砍掉小数部分,负数则向0的方向截断,比如-7 / 4,结果是-1,而不是-2。
放到这道题里,如果你写出average = sum / 4;,而sum又被定义成int类型,那么不管输入什么,平均值都必然是整数,即使你没语法错误,答案也是错的。正确的做法有两种:一种是直接把average定义成float或double,然后用average = (float)sum / 4;强制把sum转成浮点数再做除法;另一种更简洁,写成average = sum * 1.0 / 4;,利用C语言的隐式类型转换规则,让乘法先得到浮点数结果。这两种方法我都试过,实测都没问题,个人更推荐第二种,因为不用记强转语法,也能让人一眼看出“我是故意想让除法变成浮点运算的”。
2.2 printf的格式符写错,输出会是灾难现场
很多初学者对printf("%d", average)这种写法习以为常,甚至没想过average如果是浮点数会发生什么。C语言中,printf的第一个参数是格式控制字符串,编译阶段编译器并不检查格式符和参数类型是否匹配,也就是说printf("%d", 2.5)这种代码,编译器最多给个警告,照样能编译通过。
但运行时就是另一回事了。printf会按照格式符去内存里读取数据,%d告诉它读一个int,%f告诉它读一个double。如果实际传入的是double,你却用%d去取,它就会把double的内存表示硬生生解析成整数,结果完全不可控,可能是0,可能是一大串奇怪的数字,甚至可能让后续参数的读取错位。这就是为什么Average的输出必须用%.1f,同时变量本身必须是浮点类型,不能拆东墙补西墙。
2.3 PTA的格式匹配比想象中严格
PTA判题时,会把你的程序输出和标准答案做逐字符比对。什么意思呢?标准答案是Sum = 10; Average = 2.5,你输出成Sum=10; Average=2.5,少了等号两边的空格,错;你输出成Sum = 10 Average = 2.5,把分号漏了,错;你多输出了一个空格,还是错。这种严格匹配对刚接触编程的人来说很受挫,但换个角度想,它其实在逼你养成“认真读题、精确实现”的习惯。
我见过很多同学在本地编译器里运行,结果看起来完全正确,一提交就是格式错误。原因往往是最后多打了一个换行,或者题目要求“在一行中输出”,你在输出Sum后又手动加了个\n导致换行顺序不对。所以每次提交前,一定把题目给的输出示例抄下来,和你的输出做一次逐字符比对,连空格都不要放过。
3. 三种写法对比,从入门到进阶
3.1 最直白的写法:四个变量逐个读入
这种写法最贴合题目描述,也最符合新手的第一直觉,就是定义4个int变量,分别读入,然后加起来。完整代码和注释如下。
#include <stdio.h> int main() { // 定义4个整数变量,用来存储输入的4个数 int num1, num2, num3, num4; // 从标准输入逐个读取,注意&符号不能丢 scanf("%d %d %d %d", &num1, &num2, &num3, &num4); // 求和 int sum = num1 + num2 + num3 + num4; // 关键点:用sum * 1.0把整数转换为浮点数,再除以4 double average = sum * 1.0 / 4; // 输出,%.1f表示保留一位小数 printf("Sum = %d; Average = %.1f\n", sum, average); return 0; }这段代码的逻辑非常清楚。输入1 2 3 4时,sum计算得到10,average通过10 * 1.0 / 4得到2.5,最终输出Sum = 10; Average = 2.5,和题目的示例完全一致。输入1 1 1 1时,sum为4,average为4 * 1.0 / 4 = 1.0,输出Sum = 4; Average = 1.0,注意这个.0不能丢。
这种写法唯一的缺点是,一旦题目改成“求10个整数的均值”,代码就会变得极其臃肿。所以它只适合刚学变量和运算的前几天,属于“能跑但不够优雅”的阶段。
3.2 用循环处理:代码更简洁,也更通用
稍微有点经验之后,你就应该能意识到,4个整数本质上是一组“同类型、多次处理”的数据,用循环来做才是正路。尤其当你学过数组之后,这种写法几乎是唯一选择。完整代码如下。
#include <stdio.h> int main() { // 用数组保存输入的4个数 int nums[4]; // 循环读入 for (int i = 0; i < 4; i++) { scanf("%d", &nums[i]); } // 循环求和 int sum = 0; for (int i = 0; i < 4; i++) { sum += nums[i]; } // 计算平均值,同样用 *1.0 转成浮点数 double average = sum * 1.0 / 4; printf("Sum = %d; Average = %.1f\n", sum, average); return 0; }为什么推荐这种写法?因为它把“处理一个数”的逻辑独立出来了。如果哪一天题目变成求10个数的均值,你只需要把数组长度4改成10,两个循环的判断条件改成i < 10,代码的核心结构完全不用动。这就叫可扩展性。在实际开发中,需求永远在变,写代码的时候多想一步,后面就能少改好多。
3.3 用数组加上一个变量来优化:不一定更好但要理解
有些教材会推荐一种“边读边累加”的写法,也就是不存数组,只用一个变量不断接收输入并累加。代码长这样:
#include <stdio.h> int main() { int num; int sum = 0; // 循环4次,每次读一个数并累加 for (int i = 0; i < 4; i++) { scanf("%d", &num); sum += num; } double average = sum * 1.0 / 4; printf("Sum = %d; Average = %.1f\n", sum, average); return 0; }这段代码在功能上和前面几种完全等价,也通过了PTA的测试点。它的优点是省掉了数组,代码更短;缺点是数据没有保存,如果题目要求“最后再按原顺序输出这些数”,这种写法就抓瞎了。所以我在教学时通常建议:如果题目没要求回头使用数据,用这种边读边算的写法最省事;如果后续要对数据进行二次处理,老老实实用数组存下来。
3.4 三种写法的对比与选择建议
| 写法 | 核心思路 | 代码行数 | 扩展性 | 适用阶段 |
|---|---|---|---|---|
| 四个变量 | 分别读入、直接相加 | 最短 | 差,改成10个数就要写10个变量 | 刚学变量 |
| 数组+循环 | 存储后统一处理 | 适中 | 好,改长度即可 | 学了数组之后 |
| 边读边累加 | 不存数据,只算结果 | 最短 | 中等,不能回放数据 | 只需要求和均值时 |
我的建议是:如果你是刚开始学C语言,先用第一种把变量和输出的机制跑通,然后立刻尝试第三种。等学到数组,再回头把第三种改造成第二种,体会不同数据结构带来的思维差异。这道题本身只有4个数,用哪种写法都能过,但背后体现的代码组织能力,才是真正值得你反复打磨的东西。
4. 调试实录与常见问题排查
4.1 我在PTA上提交时遇到过的三类报错
先说编译错误。最常见的原因是把#include <stdio.h>写漏了,或者漏了分号,还有同学会把main打错成mian。这类错误在本地编译器里一眼就能看到,PTA报错信息里也会明确告诉你“编译错误”,按照提示改就行。
然后是格式错误。我自己第一次提交的时候,输出写成了printf("Sum = %d Average = %.1f", sum, average);,把分号漏掉了,本地运行输出是Sum = 10 Average = 2.5,看起来好像也没毛病。但PTA直接判格式错误,我把题目示例拿出来一对照才发现,示例中间是有分号的。从那次以后我就养成了习惯:本地测试时,用眼睛逐字符比对,不只看数字对不对。
最后是答案错误。这个最有迷惑性,因为输出看起来完全正常,比如输入1 2 3 4,程序输出Sum = 10; Average = 2,少了个.5。问题就出在int sum / 4的整数除法上。遇到答案错误,先检查除法运算两边是不是都是整数,再把平均值变量定义为float或double,用* 1.0或强转处理,问题基本都能解决。
4.2 一份可以直接抄的排查清单
我把这道题出现过的典型错误整理成一个表格,方便你对照排查。
| 错误现象 | 可能原因 | 解决办法 |
|---|---|---|
| 编译错误 | 缺少头文件、缺少分号、函数名写错 | 检查语法细节,按编译器提示逐项修正 |
| 编译通过但输出为0 | 忘了用&取地址符,scanf没有写入变量 | scanf中变量名必须加& |
| 输出平均值缺少小数部分 | 整数除法把小数截断了 | 用sum * 1.0 / 4或(double)sum / 4 |
| 输出一串很奇怪的数字 | printf格式符和变量类型不匹配 | 浮点数用%f,整数用%d |
| 提示格式错误 | 等号空格、分号、.0缺失 | 复制题目的输出示例,逐字符比对 |
| 输入后程序卡住不结束 | scanf的格式串里写了逗号等额外字符 | 移除格式串中无关字符,只用%d和空格 |
4.3 本地自测的边界用例
做题时不仅要测题目给的标准例子,还要自己造边界用例。所谓边界用例,就是那些“极端但合法”的输入。比如全0的情况:输入0 0 0 0,期望输出Sum = 0; Average = 0.0。这个用例能测出0 * 1.0 / 4得到的是0.0,而不是0。再比如负数:输入-1 -2 -3 -4,sum是-10,average是-2.5,期望输出Sum = -10; Average = -2.5。C语言的整数除法向零取整,负数时尤其容易出错,这个用例一定要跑。
还有一组数值比较大的用例,比如输入1000000 2000000 3000000 4000000,总和是一千万,平均是250万,这组用例主要验证int类型能不能放下这个量级的数。虽然本题4个int相加几乎不可能溢出,但提前养成测试大数的习惯,以后遇到累乘、累和的题目就能多吃一份安心。
5. 从这道题延伸出去的三个习惯
5.1 读题先抓“数据流”
拿到任何一道编程题,不要急着敲代码,先在草稿纸上把输入、处理、输出三个环节写清楚。输入是什么类型的数据,几个变量;处理步骤是求和再求均值;输出格式是什么。这个过程叫“数据流梳理”。这道题的数据流非常短,但即使以后做到几百行的复杂题,这套思路也完全适用。输入数据决定变量的类型和个数,处理逻辑决定运算符和语句结构,输出格式决定printf怎么写。你在纸上理清楚了,代码就是按这个骨架填肉。
5.2 写代码前先想好变量的类型
很多新人习惯把所有变量都定义成int,因为输入是整数嘛。但平均值这一题恰恰是个极好的反例:输入整数不代表中间计算结果也是整数。在C语言里,把类型定义错了,后续的运算行为就会跟着错。选类型时要问自己一句:这个变量的所有取值可能是什么?如果可能有小数,就必须用浮点类型,同时运算时要保证表达式至少有一个浮点数参与。这个问题想清楚了,整道题的代码几乎不会翻车。
5.3 学会读报错,而不是瞎改
PTA的反馈分三种:编译错误、格式错误、答案错误。编译错误说明你的程序连“翻译”都没过,报错信息里会指到具体行号和原因,先看这个。格式错误说明程序能跑、结果也算对了,但输出和标准答案没对齐,这时候对照题面的输出示例,检查空格、标点、精度。答案错误说明你的计算逻辑有问题,要么整数除法丢精度,要么公式不对。报错信息读得准,改起来就快,不要一看到红叉就从头到尾重写一遍。这套排查方法,放在以后任何代码调试场景里都通用。
我个人在实际教学和刷题过程中看过太多类似代码,这道“习题2-1 求整数均值”看似简单,其实是把C语言里最容易忽略的三个点:整数除法、类型匹配、精确输出,全部浓缩在一个只有几行的程序里。如果你第一次提交就被判错,不用怀疑自己的智商,多半就是这三座大山的其中一座。把每种写法都亲手跑一遍,再对照排查清单过一遍,应该很快就能看到绿色的AC(Accepted)标志。过了这题,后面的路还长,但这个“先把类型和格式搞定再谈逻辑”的意识,会在你以后写每一行C代码时都帮到你。