MyGoddd 发表于 2022-9-12 12:01:25

getchar与for循环问题

#include <stdio.h>

int main()
{
      int n, i;

      printf("请输入字符的个数:");
      scanf("%d", &n);

      char a;

      printf("请开始输入字符:");
      getchar(); // 将标准输入流中剩下的 '\n' 扔掉
      for (i = 0; i < n; i++)
      {
                scanf("%c", &a);
      }
      a = '\0';
      printf("你输入的字符串是:%s\n", a);

      return 0;
}
getchar函数不是输入字符后按回车结束么,那么调用getchar函数后回车怎么直接执行循环了?而且注释解释的‘\n'扔掉怎么实现的。

小白一枚,描述可能不准确

ExiaGN001 发表于 2022-9-12 12:01:26

本帖最后由 ExiaGN001 于 2022-9-16 20:31 编辑

缓冲区中的不可见字符会转义处理(回车以\n表示,空格以[空格]表示)
以输入数据为例:
5
abcde

printf("请输入字符的个数: ");
输入缓冲区:请输入字符的个数:[空格]

scnaf("%d",&n);
输出缓冲区:\nabcde\n

printf("请开始输入字符: ");
输入缓冲区:请开始输入字符:[空格]

getchar();
输出缓冲区:abcde\n // \n被getchar()吸走了

for(i=0->i=6;)//伪代码,不能执行
scanf("%c",&a[ i]);
输出缓冲区:bcde\n

scanf("%c",&a[ i]);
输出缓冲区:cde\n

scanf("%c",&a[ i]);
输出缓冲区:de\n

scanf("%c",&a[ i]);
缓冲区:e\n

scanf("%c",&a[ i]);
缓冲区:\n

scanf("%c",&a);
因为\n不满足%c的条件,故此处会等待输入
缓冲区:\n

额外减小 发表于 2022-9-12 12:14:21

getchar()是从标准输入流里读走1个字符,你在输入字符的个数后缓冲区还有个字符’\n‘,然后调用getchar()就把你的'\n'读走了,然后进入循环

临时号 发表于 2022-9-12 12:15:56

你让第一次的scanf去读一个整数,但你在输入整数后不是会按下一个回车吗,但scanf只会去读一个整数,剩下的那一个回车就依旧被放在了标准输入流中,而getchar函数的作用是从标准输入流中读一个字符,所以就把那个回车从标准输入流中读了出来,达到了从标准输入流中删除回车的目的

gandixiwang 发表于 2022-9-12 13:16:15

本帖最后由 gandixiwang 于 2022-9-12 13:18 编辑

回车键也是有字符的
‘\n’ = 回车键
scanf函数是不会读取‘\n’这个回车键的,所以当你输入回车后 '\n' 这个字符会一直保留在标准输入流当中
所以要使用 getchar() 这个可以读取回车字符的函数,把准输入流里的 ‘\n’ 给读取掉
防止影响到后面的程序,导致发生意料之外的输出结果。

dolly_yos2 发表于 2022-9-12 13:55:11

其实这个问题很简单(不过也并不简单),二楼说的很对:之所以会显得奇怪,是因为 getchar 的工作既不是舍弃换行符也不是按回车键结束程序。就像它的名字(部分)说明的那样,它的作用是从标准输入流 stdin 中抽取下一个字符并将其作为返回值返回。
其能够在这里作为舍弃换行符是因为使用 scanf 函数进行读取时其会忽略并丢弃目标字符串之前出现的前导空白,但不会使用或丢弃已处理字符串后的任何无法匹配到目标字符串的字符。如对于读取整数的 scanf 调用如 scanf("%d", &n) ,假设输入的完整内容为 "   123 c" ,则返回后 n 的值为 123 而输入流中将剩余 " c" 。如果约定输入格式是一个整数后紧跟一个换行符(输入整数后按一下回车键),则在 scanf 后添加一个 getchar 就可以从标准输入流中抽取出被 scanf 忽略的这个换行符。
其能够起按回车键结束程序的作用是由于当当前标准输入流已经被读取完毕时继续进行 getchar 调用,在通常的配置下其将会等待到取得一个可用的字符进行抽取后返回,且这一可用字符只有当按下回车键后才会到来,因此行为上表现为按下回车键结束程序。
个人认为混用 scanf 一类的格式化输入函数与 getchar 一类的字节输入函数是相对困难的,需要对内部机制有一定的认识和正确的理解,否则容易出现各种细节问题。

jackz007 发表于 2022-9-12 13:57:11

本帖最后由 jackz007 于 2022-9-12 14:22 编辑

       执行过 scanf() 后,在键盘缓冲区中一定会滞留一个 '\n',这个时候,如果继续使用 scanf() 进行输入,scanf() 会过滤掉这个 '\n' 字符,从而,并不会对后续输入造成任何的影响,但是,如果在调用过 scanf() 后,使用 getchar() 读取键盘输入,那么,读到的第一个字符一定是滞留在键盘缓冲区中的 '\n' ,所以,在 scanf() 后接 getchar() 的情况下,必须先清空键盘缓冲区。

       解决这个问题,使用 fflush(stdin) 胜过 getchar(),前者会直接清空键盘缓冲区,如果执行前,键盘缓冲区是空的,那么,fflush(stdin) 会什么都不做,直接返回,而 getchar() 却真的会等待键盘输入,会让人莫名其妙。
#include <stdio.h>

int main(void)
{
      int n , i                                  ;
      printf("请输入字符的个数:")               ;
      scanf("%d" , & n)                        ;
      char a                              ;
      printf("请开始输入字符:");
      fflush(stdin)                              ; // 这一句清空键盘缓冲区,用于清除因使用 scanf() 函数而滞留在键盘缓冲区中的 '\n' 字符
      for(i = 0 ; i < n ; i ++) a = getchar() ; // 用 getchar() 胜过用 scanf("%c" , . . .),因为,scanf() 会滤除 ' '、'\n' 等空白字符。而 getchar() 不会
      a = '\0'                              ;
      printf("你输入的字符串是:%s\n", a)      ;
}

dolly_yos2 发表于 2022-9-12 14:21:14

jackz007 发表于 2022-9-12 13:57


这种实现方式有一定问题。
fflush 以除输出、更新流之外的流作为参数进行调用时行为是未定义的,在我的测试中也未能清除留存的 '\n' 字符。
getchar 或许在性能上略微优于 scanf (未研究,存疑),但会滤除空白字符绝不是 getchar 更适用的原因:在处理 %c 时, scanf 不会过滤空白字符。

人造人 发表于 2022-9-12 14:30:53

dolly_yos2 发表于 2022-9-12 14:21
这种实现方式有一定问题。
fflush 以除输出、更新流之外的流作为参数进行调用时行为是未定义的,在我的 ...

是的,我这边也无法刷stdin
sh-5.1$ cat main.c
#include <stdio.h>

int main(void) {
    char ch;
    scanf("%c", &ch);
    printf("%c", ch);
    fflush(stdin);
    scanf("%c", &ch);
    printf("%c", ch);
    printf("\n");
    return 0;
}
sh-5.1$ gcc -g -Wall -o main main.c
sh-5.1$ ./main
abcd
ab
sh-5.1$

MyGoddd 发表于 2022-9-13 15:33:31

额外减小 发表于 2022-9-12 12:14
getchar()是从标准输入流里读走1个字符,你在输入字符的个数后缓冲区还有个字符’\n‘,然后调用getchar() ...

我输入的数字不算是字符么?你的意思是我输入数字后回车就相当于回车(字符)调用了getchar么?

gandixiwang 发表于 2022-9-13 15:42:55

本帖最后由 gandixiwang 于 2022-9-13 15:50 编辑

MyGoddd 发表于 2022-9-13 15:33
我输入的数字不算是字符么?你的意思是我输入数字后回车就相当于回车(字符)调用了getchar么?

对,差不多就是这样

应该说是getchar 读取了回车字符,
如果你设置一个char p 令 p=getchar(),再把p打印出来,printf("%c",p),你就会发现输出那里多空出来了一行
这个操作就是getchar()读取了你的回车字符,把回车字符赋给p,然后打印出来

如果你不设置一个p去接收回车字符
那就相当于getchar() 读取了回车字符之后把他给流放了

MyGoddd 发表于 2022-9-13 15:50:17

dolly_yos2 发表于 2022-9-12 14:21
这种实现方式有一定问题。
fflush 以除输出、更新流之外的流作为参数进行调用时行为是未定义的,在我的 ...

这其中的过滤指的是执行循环时调用scanf不用回车么?
但他是如何实现的?各位大佬解释的我云里雾里,听不太懂,小白一个。
我才开始理解的是输入字符个数后回车相当于调用了getchar(我也想问一下,数字不会调用getchar么?)之后开始执行for循环(这里面我发现for循环执行时调用scanf时并不用回车执行了)。而且程序不是一行一行执行么,执行到for循环怎么再去调用getchar呢?

MyGoddd 发表于 2022-9-13 15:50:47

jackz007 发表于 2022-9-12 13:57
执行过 scanf() 后,在键盘缓冲区中一定会滞留一个 '\n',这个时候,如果继续使用 scanf() 进行输入 ...

这其中的过滤指的是执行循环时调用scanf不用回车么?
但他是如何实现的?各位大佬解释的我云里雾里,听不太懂,小白一个。
我才开始理解的是输入字符个数后回车相当于调用了getchar(我也想问一下,数字不会调用getchar么?)之后开始执行for循环(这里面我发现for循环执行时调用scanf时并不用回车执行了)。而且程序不是一行一行执行么,执行到for循环怎么再去调用getchar呢?

jackz007 发表于 2022-9-13 16:08:23

本帖最后由 jackz007 于 2022-9-13 16:12 编辑

MyGoddd 发表于 2022-9-13 15:50
这其中的过滤指的是执行循环时调用scanf不用回车么?
但他是如何实现的?各位大佬解释的我云里雾里,听 ...

      getchar() 和 scanf() 都是带有缓冲的键盘输入函数,所谓 "带有缓冲" 是指程序从键盘获取输入并不是实时的,就是说,在按下回车键之前,用户可以对输入内容进行任意编辑,只有在按下回车键的时候,键盘输入才会被确认,这两个函数才会从键盘缓冲区获取输入,所以,当使用 scanf() 读取数据过后,会有一个 '\n' 字符滞留在键盘缓冲区,这个时候,如果调用 getchar() 会直接读取到这个 '\n' 字符。当然,如果只是多次调用 scanf() 获取输入,那就不必在意这个滞留的 '\n' 字符。因为这个字符对于正常获取输入不会产生任何影响。

dolly_yos2 发表于 2022-9-13 16:11:30

MyGoddd 发表于 2022-9-13 15:50
这其中的过滤指的是执行循环时调用scanf不用回车么?
但他是如何实现的?各位大佬解释的我云里雾里,听 ...

我搜索了一下这一页里出现的“过滤”这个词,它出现的形式是
如果继续使用 scanf() 进行输入,scanf() 会过滤掉这个 '\n' 字符
在处理 %c 时, scanf 不会过滤空白字符
这里的“过滤”指的是 scanf 的工作方式:对于一个 conversion specification (赋值指示?转换规范?就是 scanf 的第一个参数字符串中的 %d, %s, %c 等), scanf 在处理时首先会跳过输入流中未处理的空白字符直到遇到第一个非空白字符,除非这个 conversion specification 中包含 '[', 'c' 或 'n' 。就像是在我之前的回复中提到的,如果输入流中未处理的字符是 "   123" 而使用 scanf("%d", &n) 输入,则未处理的字符中最开头的空格将首先被跳过, n 最终根据 "123" 被赋值为 123。
您所谓的“调用”的理解是错误的,不存在回车键“调用” getchar 的情况,也许一个更好的说法是回车键“唤醒” getchar 。通常设置下,可以粗略的认为按下回车键后才会把从键盘输入的这“一行”字节发送给程序, getchar 被调用后会等待可用的数据,而当回车键按下一些新的字节被发送给程序, getchar 也就等到了它需要的数据,因此“醒来”继续工作。

MyGoddd 发表于 2022-9-13 16:14:50

jackz007 发表于 2022-9-13 16:08
getchar() 和 scanf() 都是带有缓冲的键盘输入函数,所谓 "带有缓冲" 是指程序从键盘获取输入并 ...

在这个程序中,打印完请开始输入字符,此时不是开始执行getchar么,我输入字符进去之后回车,这时不是才执行for么,既然执行for循环了,如何再去调用getchar呢?

gandixiwang 发表于 2022-9-13 17:15:32

本帖最后由 gandixiwang 于 2022-9-13 17:20 编辑

MyGoddd 发表于 2022-9-13 16:14
在这个程序中,打印完请开始输入字符,此时不是开始执行getchar么,我输入字符进去之后回车,这时不是才 ...

嗯。。。其实可以把getchar()换个位置

#include <stdio.h>

int main(void)
{
        int n, i;
       
        printf("请输入字符的个数:");
        scanf("%d", &n);
        getchar(); // 将标准输入流中剩下的 '\n' 扔掉
        char a;
       
        printf("请开始输入字符:");
       
        for (i = 0; i < n; i++)
        {
                scanf("%c", &a);
        }
        a = '\0';
        printf("你输入的字符串是:%s\n", a);
       
        return 0;
}

换到上面去,结果是一样的

MyGoddd 发表于 2022-9-15 09:35:44

ExiaGN001 发表于 2022-9-14 20:59
缓冲区中的不可见字符会转义处理(回车以\n表示)
以输入数据为例:



我好像明白了,你看我理解的对么。
scanf,getchar。都与缓冲区有关。getcahr将数据存入缓冲区,scanf可以从缓冲区获取。所以输入字符回车后for循环直接执行。
当执行printf后已经“唤醒”getchar,我输入的字符已经在缓冲区了。此时缓冲区abcde\n,此时开始for循环,从缓冲区开始获取。执行scaf。
对么,这里的关键在于知道scanf是从缓冲区获取。

jackz007 发表于 2022-9-15 13:55:58

MyGoddd 发表于 2022-9-15 09:35
我好像明白了,你看我理解的对么。
scanf,getchar。都与缓冲区有关。getcahr将数据存入缓冲区,scanf可 ...

         是操作系统把键盘输入存入键盘缓冲区,只有在按下回车键后,操作系统才会把键盘缓冲区开放给 getchar()、scanf() 等函数,这个时候,这两个函数才可以从键盘缓冲区提取输入的内容,这批内容如果不能一次取完,可以多次提取,期间,函数均不会等待,直接返回,如果在这批内容被取空的时候,继续调用这两个函数,就会进入等待状态,函数不会立即返回,直到按下回车键为止,下一轮的键入内容提取才会开始。

ExiaGN001 发表于 2022-9-15 17:30:00

MyGoddd 发表于 2022-9-15 09:35
我好像明白了,你看我理解的对么。
scanf,getchar。都与缓冲区有关。getcahr将数据存入缓冲区,scanf可 ...

是的
scanf一定是从缓冲区
(或者文件缓冲区等,在程序中默认是标准io
获取东西
只有缓冲区是空或者没有符合条件的数据(例:\n对scanf("%c"))时才会等待你输入
页: [1] 2
查看完整版本: getchar与for循环问题