这两道题来自我学习 C 时做的牛客入门练习:BC49 判断两个数的大小、BC95 最高分与最低分之差。题目本身不复杂,但多组输入和初始化值得单独说清楚。
1. 判断两个数:我的原解法为什么是 == 2
每组读两个整数,输出它们的大小关系。我的原解法核心保持不变:
#include<stdio.h>intmain(void){inta,b;while(scanf("%d %d",&a,&b)==2){charrelation;if(a>b)relation='>';elseif(a==b)relation='=';elserelation='<';printf("%d%c%d\n",a,relation,b);}return0;}测试输入:
3 2 2 2 -1 5输出:
3>2 2=2 -1<5我的出发点是“一组有两个数”,因此循环应当在两个转换都成功赋值时进入。scanf的返回值不是读到的字符数,也不是简单的成功或失败开关。
四种输入,四种情况
| 输入情况 | %d %d的返回值 | 能否处理完整一组 |
|---|---|---|
3 2 | 2 | 能 |
3 x | 1 | 不能,第二次转换匹配失败 |
x | 0 | 不能,第一次转换就匹配失败 |
| 第一次赋值之前就遇到输入结束 | EOF | 不能 |
如果先读到一个整数再遇到文件结束,返回 1,不是 EOF。EOF 是一个负的整型常量,本机值为 -1,但代码不该把某个实现值当成所有实现的规定。
为什么 != EOF 和按位取反容易误导
while (scanf("%d", &n) != EOF)在输入x时可能继续进入循环:返回 0,不等于 EOF。错误字符没有被这个%d转换消耗,再次尝试还可能失败。如果没安排恢复逻辑,就会反复卡在同一个位置。单个整数也应该用== 1检查一次完整转换。
我原来写的“~2是 0”是错误的。本机 GCC 环境中:
~2 = -3 ~0 = -1 ~(-1) = 0~是按位取反,不是“判断输入成功”。即使在常见的 EOF=-1 环境,~scanf(...)仍然不能识别返回 0 的匹配失败,也不能保证两个数都读到了。
用有界字符串输入复现,不把测试程序写成死循环
#include<stdio.h>intmain(void){constchar*cases[]={"3 2","3 x","x",""};for(size_ti=0;i<sizeofcases/sizeofcases[0];++i){inta=0,b=0;intresult=sscanf(cases[i],"%d %d",&a,&b);if(result==EOF)printf("EOF\n");elseprintf("matched:%d\n",result);}return0;}输出:
matched:2 matched:1 matched:0 EOF这个实验验证的是转换和返回值,不是完整的交互式输入恢复器。评测题保证合法输入时,遇到不完整组直接退出就够用;真实交互程序还要定义怎么报告错误、清理输入并重试。scanf也不能自动解决超出整数类型范围的外部输入问题,严肃文本解析可以进一步学习fgets与strtol。
2. 成绩差:我为什么初始化成 max=0、min=100
原题成绩范围是 0 到 100。我的想法是:先给最大值一个不大于任何合法成绩的初值,最小值给一个不小于任何合法成绩的初值。
max = 0 min = 100这个思路在题目范围内成立,不需要因为代码简单就否定它。问题在于:如果以后遇到负数、温度或任意整数,这两个初值不能直接照搬。
原程序还先保存数组,再分别扫描最大值和最小值。它能做这件事,但没有必要存下所有成绩。每读一个数,就可以同时更新两项统计量。
更通用的写法:用第一个数据初始化
#include<stdio.h>intmain(void){intn,value;if(scanf("%d",&n)!=1||n<=0)return1;if(scanf("%d",&value)!=1)return1;intmin_value=value,max_value=value;for(inti=1;i<n;++i){if(scanf("%d",&value)!=1)return1;if(value<min_value)min_value=value;if(value>max_value)max_value=value;}printf("%lld\n",(longlong)max_value-(longlong)min_value);return0;}这里先保证有第一个值,再让最大值和最小值都等于它。原题的差值最多 100,int足够;为了在本机常见的 32 位 int 环境扩展测试,我在减法之前转成long long。这不代表所有 C 实现的任意 int 差值都必然能装进 long long。
输入5 60 80 100 0 75,输出100;输入1 88,输出0;扩展测试3 -8 -2 -5,输出6。后一个不属于成绩题范围,只用来说明初始化方式可复用。
为什么这次能省掉数组
读完前 i 个值时,我维护的两个量始终分别是这 i 个值的最小值和最大值。第 i+1 个值只可能扩大边界,不需要回看旧数据。因此时间 O(n),额外空间 O(1)。
这不是“数组不好”,而是输出只依赖两个统计量。如果下一步还要排序或找中位数,需求就变了。
原代码里还有两个小地方:
int arr[10000] = {-1};只有第一个元素是 -1,其余是零,不是全部 -1。scanf("%d ", &value)的尾部空白可能让交互输入继续等待后续非空白字符;这里只需要"%d"。
3. 验证比“看着对”多一步
本次比较程序测试了大于、等于、小于、负数,以及错误首项、错误第二项和空输入;成绩程序测试了单值、重复值、0/100 边界和扩展负数,并与独立排序结果对照。
两题真正可带走的不是两段答案,而是两个习惯:一组需要几次赋值,就检查几次;统计初值依赖题目范围,想复用就把这个前提拿掉。