超详解 - 如何理解C语言中while(scanf(“%d“, &num) != EOF)这一表达式?

简介: 本文详细介绍了scanf 与 EOF 的含义与使用。

许多C语言初学者常常对scanf函数、表达式scanf("%d", &num) != EOF的含义与其使用情况有些疑惑。

本文通过一道牛客网例题,对该表达式进行说明和适当拓展;不需要引例的朋友可以直接跳转到讲解部分。

希望对诸位读者有所帮助。


一、引例 - 牛客网OJ题


为了更好地说明这个表达式,我们以一道牛客网的题目作引例。题目链接贴在这里:


牛客网习题-BC49 判断两个数的大小关系


https://www.nowcoder.com/practice/f05358b9e8164b27871c87d3097f4dab?tpId=107&&tqId=33330&rp=1&ru=/ta/beginner-programmers&qru=/ta/beginner-programmers/question-ranking


题干如下



这道题的题意非常简单,思路也非常明确,程序大体仅需简单的 if 分支即可实现。经过一番思考咱们自信地写下如下代码:


/*Wrong!*/
#include<stdio.h>
 
int main(){
    int x,y;
    scanf("%d %d",&x,&y);
    if(x > y)
        printf("%d>%d",x,y);
    else if (x = y)
        printf("%d=%d",x,y);
    else
        printf("%d<%d",x,y);
    return 0;
}

然而将该代码提交给牛客网OJ,却无法通过所有用例:



说明告诉我们,当用例连续输入多组(3组)时,上面的代码没法做到一口气输出这3组用例相应的答案,而仅仅是输出了第一个用例(1 1)的答案。显然,上面的程序 scanf 在读取了一组输入用例后就跑路了,无法做到题干要求的能输入 “多组输入数据” 。我们的答案程序被毙了。


也许循环可以解决上面的bug,于是咱们尝试以下代码:


/*Wrong!*/
#include<stdio.h>
 
int main(){
    int x,y;
    for(int i=0; i<3; i++){
        scanf("%d %d",&x,&y);
        if(x > y)
            printf("%d>%d\n",x,y);
        else if (x == y)
            printf("%d=%d\n",x,y);
        else
            printf("%d<%d\n",x,y);
    }
    return 0;
}

试图用 for 循环来达到 “输入多组用例” 的要求。 事实上也不可行,该程序的逻辑是:输入一组用例,执行这一组用例,再通过 for 循环再输入一组用例,再执行……并不是一次性输入多组用例且同时打印多组答案。


因而,本题能被牛客网OJ通过的关键是能做到多组输入。如何实现,用到的就是我们今天要讲解的重点:while(scanf("%d", &num) != EOF)


如下是该题的一个正确答案(事实上所有的正确答案都大同小异,且EOF这句表达式是必不可少的)


#include<stdio.h>
 
int main(){
    int a,b;
    while((scanf("%d%d",&a,&b)) != EOF){    //重点在此!
        if(a == b)
            printf("%d=%d\n",a,b);
        else if(a > b)
            printf("%d>%d\n",a,b);
        else
            printf("%d<%d\n",a,b);
        }
    return 0;
}

二、EOF 与 scanf 函数的关系


1. EOF (End Of File)  

名称是文件结束标志,定义为 -1(不是ASCII码值为-1),可以通过 Ctrl+Z 直接键入。


2. scanf 函数    

scanf函数是有返回值的。


一般来说,它的返回值是成功读取的元素个数。但当遭遇读取失败时,它的返回值便是 -1 (也就是它自己返回一个EOF) 。


而若是一个元素都还没成功读入的时候就遇到了读取失败或EOF,那它直接就会返回-1,不管后面再输入了什么。


Cplusplus官网对 scanf 函数的说明如下:




by the way,  getchar()函数读取失败时,也会返回EOF。

下面这个网站建议大家收藏,C语言中各种函数、关键字等等的用法都可以在其中查询到。


https://cplusplus.com/


scanf 演示


代码


int main(){
  int a = 0;
  int b = 0;    //输入a和b
  int ret = scanf("%d %d",&a,&b);    //用ret接受scanf的返回值
  printf("ret = %d\n",ret);
  printf("a = %d\n",a);
  printf("b = %d\n",b);
  return 0;
}

运行结果


(1) 输入 a 为7,b 为8,二者被scanf成功读取,ret为2(成功读取的元素个数)。



 (2) 输入 a 为7,b为EOF(先键盘敲Ctrl+Z后再enter),ret变成了1,因为只有a是成功被读取的,b并没有被成功读入。



(3)输入a为EOF,b为8,ret变成了-1(即EOF).因为scanf在一个元素也没有读取的时候就遇到了EOF(就是我们输入的a,我们人为的输入了EOF),scanf直接返回-1,程序结束。



(4) 输入a或b为非数字,属于元素类型不匹配的情况。


第一个元素'A'并没有被读入,scanf()会停留在那,并把字符'A'放回缓冲区再又继续读取。下一次读取的时候,仍然是从'A'开始。事实上,scanf()一直无法越过'A'读到下一个字符,一直反复读入,并陷入了死循环。在如下程序中,scanf会直接认为读取结束了,跳出函数。


因而该程序终止后,一个元素也没有被读进去,但又不属于C语言定义下的读取失败,ret为0.


若将语句写成while(scanf("%d", &num) != EOF)这样,效果就是一直死循环。


(其实输完'A'按enter程序就会直接结束,而正常情况下敲enter仅仅是输入下一个数而已,这也是二者的一个差别。)




(5) 输入a为7,b为'A',a在b之前成功读取了,因而ret为1.




三、while(scanf("%d", &num) != EOF)的使用


当需要多组输入时,可以用该表达式控制循环入口。当人为的输入EOF时,结束循环。


代码演示



此时我一次键入第一行的6个数字:5 6 10 10 2 3,敲enter,同时显示这三组用例的运行结果。注意:此时我的程序并没有结束!末行光标仍然在跳动,事实上我还能再接着键入几组数字!



再又一次输入三组数并显示运行结果后,我敲下Ctrl+Z,此时控制台上显示了一个 ^Z ,说明成功输入了EOF,再按下enter,出现最下面的横线与小字,程序结束!!


我使用的IDE是小熊猫版的devc++,如果在vs 2019中,要输入三次Ctrl Z才行。这其实是vs的一个小bug 。


换成如下代码,也是可以的:


int main(){
  int a,b;
  while((scanf("%d%d",&a,&b)) == 2){
    if(a == b)
      printf("%d=%d\n",a,b);
    else if(a > b)
      printf("%d>%d\n",a,b);
    else
      printf("%d<%d\n",a,b);
  }
  return 0;
}


while((scanf("%d%d",&a,&b)) == 2) 与  while(scanf("%d", &num) != EOF) 本质上等价。


四、总结


本文详细介绍了scanf 与 EOF 的含义与使用。


1. 当需要多组输入时,使用while(scanf("%d", &num) != EOF)控制循环入口。


2. 初学阶段了解如何使用即可:可以将EOF直接理解为一个简单的标记。当我们在scanf函数中输入CTRL + Z时,就能“召唤”出这个标记,从而结束 scanf 函数,达到控制循环的目的。


3. while((scanf("%d%d",&a,&b)) == 2) 与  while(scanf("%d", &num) != EOF)效果完全相同。


4. 感谢大家支持!如果表述不当之处,欢迎各位斧正!



相关文章
|
1月前
|
C语言
初识C语言:与计算机的交流之输入与输出(scanf和printf)
初识C语言:与计算机的交流之输入与输出(scanf和printf)
167 0
|
2月前
|
C语言
【C语言基础考研向】05 scanf读取标准输入超详解
本文详细解析了C语言中`scanf`函数的工作原理及常见问题。首先介绍了`scanf`如何处理标准输入,并通过示例说明了为何有时会出现阻塞现象及其解决办法。接着探讨了当输入包含多种数据类型时,特别是字符型数据的处理方式,强调了格式控制的重要性,并给出了正确的输入格式示例。通过正确配置,可以避免因空格和换行符导致的问题,确保数据准确读取。
72 10
|
2月前
|
程序员 C语言
【C语言基础考研向】06运算符与表达式
本文介绍了C语言中的运算符分类、算术运算符及表达式、关系运算符与表达式以及运算符优先级等内容。首先概述了13种运算符类型,接着详细说明了算术运算符的优先级与使用规则,以及关系运算符和表达式的真假值表示,并给出了C语言运算符优先级表。最后附有课后习题帮助巩固理解。
104 10
|
2月前
|
存储 C语言
【C语言基础考研向】10 字符数组初始化及传递和scanf 读取字符串
本文介绍了C语言中字符数组的初始化方法及其在函数间传递的注意事项。字符数组初始化有两种方式:逐个字符赋值或整体初始化字符串。实际工作中常用后者,如`char c[10]=&quot;hello&quot;`。示例代码展示了如何初始化及传递字符数组,并解释了为何未正确添加结束符`\0`会导致乱码。此外,还讨论了`scanf`函数读取字符串时忽略空格和回车的特点。
|
2月前
|
C语言
C语言程序设计核心详解 第三章:顺序结构,printf(),scanf()详解
本章介绍顺序结构的基本框架及C语言的标准输入输出。程序从`main()`开始依次执行,框架包括输入、计算和输出三部分。重点讲解了`printf()`与`scanf()`函数:`printf()`用于格式化输出,支持多种占位符;`scanf()`用于格式化输入,需注意普通字符与占位符的区别。此外还介绍了`putchar()`和`getchar()`函数,分别用于输出和接收单个字符。
|
2月前
|
C语言
C语言程序设计核心详解 第二章:数据与数据类型 4种常量详解 常见表达式详解
本文详细介绍了C语言中的数据与数据类型,包括常量、变量、表达式和函数等内容。常量分为整型、实型、字符型和字符串常量,其中整型常量有十进制、八进制和十六进制三种形式;实型常量包括小数和指数形式;字符型常量涵盖常规字符、转义字符及八进制、十六进制形式;字符串常量由双引号括起。变量遵循先定义后使用的规则,并需遵守命名规范。函数分为标准函数和自定义函数,如`sqrt()`和`abs()`。表达式涉及算术、赋值、自增自减和逗号运算符等,需注意运算符的优先级和结合性。文章还介绍了强制类型转换及隐式转换的概念。
|
3月前
|
C语言
C语言------运算符与表达式
这篇文章是C语言运算符与表达式的实训教程,通过多个示例程序展示了如何使用算术运算符、关系运算符、逻辑运算符以及条件语句来解决实际问题,并介绍了如何通过函数库简化复杂数学运算。
C语言------运算符与表达式
|
3月前
|
存储 缓存 编译器
【C语言篇】scanf和printf万字超详细介绍(基本加拓展用法)(下篇)
scanf处理⽤⼾输⼊的原理是,⽤⼾的输⼊先放⼊缓存,等到按下回⻋键后,按照占位符对缓存进⾏解读。 解读⽤⼾输⼊时,会从上⼀次解读遗留的第⼀个字符开始,直到读完缓存,或者遇到第⼀个不符合条件的字符为⽌。
162 2
|
3月前
|
存储 C语言
【C语言篇】scanf和printf万字超详细介绍(基本加拓展用法)(上篇)
printf 的作⽤是将参数⽂本输出到屏幕。它名字⾥⾯的 f 代表 format (格式化),表⽰可以定制输出⽂本的格式。
75 1
|
3月前
|
存储 安全 编译器
C语言中的scanf函数
C语言中的scanf函数