使用scanf()读取多行输入

7
相关代码片段:
char input [1024];

printf("Enter text. Press enter on blank line to exit.\n");
scanf("%[^\n]", input);

这将读取整行直到用户按下[Enter]键,防止用户输入第二行(如果他们想要的话)。

要退出,他们按下[Enter],然后再次按下[Enter]。因此,我尝试了各种while循环、for循环和if语句来控制scanf()函数与新行转义序列的交互,但似乎都没有起作用。

有什么想法吗?


2
scanf() 很难使用,特别是对于这种情况。相反,使用 fgets() 会更简单。 - Greg Hewgill
1
@David请不要将gets提及为任何可行的选项。它太不安全了(并幸运地被从语言中移除)。 - Daniel Fischer
@tmyklebu,“简单”和“scanf”不应该在同一个句子中出现(除非还伴随着“易错”的描述)。 - jamesdlin
@jamesdlin:阅读手册页。这不是火箭外科手术。 - tmyklebu
@tmyklebu 查看 man 手册并不能帮助你处理 scanf 的许多陷阱。实际上,最好完全避免使用它。http://www.c-faq.com/stdio/scanfprobs.html - jamesdlin
显示剩余10条评论
5个回答

5

试试这个:

while (1 == scanf("%[^\n]%*c", input)) { /* process input */ }

2
为了保证安全,将输入缓冲区的大小减1后加入格式说明符。 - Carey Gregory
1
这似乎是有效的。你能解释一下吗?%*c是什么意思,为什么它们加起来等于1? - user688604
1
@Carey:但是这样就不再起作用了,因为被吃掉的字符可能是一行中的最后一个字符。user688604:%*c吃掉一个字符。scanf在完成或失败之前返回它填充的参数数量。如果它看到的第一个字符是换行符,它将无法匹配%[^\n]。一旦失败,下一个要从stdin读取的字符将是换行符。 - tmyklebu
1
@tmyklebu:当然可以用(我刚测试过)。唯一可能存在的问题是,如果用户输入的字符超过了输入缓冲区的容量,循环将终止。但这比缓冲区溢出的问题要好得多。在这个例子中,格式说明符应该是“%1023 [^ \ n]%* c”。 - Carey Gregory
1
@tmyklebu: 这就是我说的: "唯一可能出现的问题是,如果用户输入的字符超过了输入缓冲区的容量,则循环将终止。" 所以我测试得很好。我宁愿简单地处理输入过多的错误,而不是试图强制我的数据缓冲区驻留在内存中的特殊位置。 - Carey Gregory
显示剩余4条评论

1

正如之前所指出的,fgets()scanf()更适合这里。

您可以使用fgets(input, 1024, stdin);读取整行输入,其中stdin是与标准输入(键盘)相关联的文件。
函数fgets()从键盘读取每个字符,直到第一个换行符:'\n'(当然是在按下ENTER键后获得...)。
重要提示:字符'\n'将成为数组input的一部分。

现在,您的下一步是验证数组input中从第一个字符到'\n'是否都为空格。
此外,请注意,在input中第一个'\n'之后的所有字符都是垃圾,因此您不必检查它们。

您的程序可能如下所示:

char input[1024];
printf("Enter text. Press enter on blank line to exit.\n");
while (1) {
   if (fgets(input, 1024, stdin) == NULL)
     printf("Input Error...\n");
   else {
     /* Here we suppose the fgets() has reached a '\n' character... */
     for (char* s = input; (*s != '\n') && isspace(*s); s++)
        ; /* skipping blanks */
     if (*s == '\n')
        break; /* Blank line */
     else
        printf("%s\n", input); /* The input was not a blank line */
   }
}

这段代码必须写在你的main()块内,更重要的是,在所有代码之前必须包含头文件<ctype.h>,因为使用了isspace()函数。
代码很简单:while循环永远执行,用户在每次迭代中输入一行,if语句检查是否发生了错误。
如果一切正常,那么就会执行一个for(;;)语句,它会探索数组input,看看那里是否只有空格...或者不是。
for迭代会一直持续到找到第一个换行符'\n',或者出现非空白字符为止。
for终止时,意味着最后分析的字符(保存在*s中)是一个换行符(这意味着所有早期的字符都是空格),或者不是(这意味着至少有一些非空白字符在input[]中,所以input是一个普通文本)。

“永恒”的while(1)只有在读入空行时才会被中断(请查看第11行的break语句)。

"while (1)" 是一个无限循环,当文件结束时会停止。 - chux - Reinstate Monica

1

楼主说:“要退出,他们按[回车],然后再按一次[回车]”

unsigned ConsecutiveEnterCount = 0;
for (;;) {
  char buffer[1024];
  if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
    break;  // handle error or EOF
  }
  if (buffer[0] == '\n') {
    ConsecutiveEnterCount++;
    if (ConsecutiveEnterCount >= 2 /* or 1, not clear on OP intent */) {
      break;
    }
  }
  else ConsecutiveEnterCount = 0;
  // Do stuff with buffer;
}

1
...我尝试了各种while循环、for循环和if语句,围绕着scanf()和新行转义序列,但似乎什么都不起作用。
看来你试过了所有不该试的东西,在阅读之前!C程序员应该“阅读手册”,否则就会遇到像你经历的这样的“未定义行为”带来的头痛。更具体地说,你不能像学Java那样“猜测”来学习C。
把这当做你的教训。停止“猜测”,开始“阅读”(fscanf手册)
根据该手册:
[匹配一组预期字节(扫描集)中的一个或多个非空字节。

重点在于这里。你所描述的是一个字节序列,这意味着匹配失败了。手册上对于匹配失败有什么说法?

成功完成后,这些函数将返回成功匹配并分配的输入项数;如果出现早期匹配失败,则此数字可以为零。如果输入在第一次转换(如果有)完成之前结束,并且没有发生匹配失败,则将返回EOF。如果在第一次转换(如果有)完成之前发生错误,并且没有发生匹配失败,则将返回EOF...

同样,重点在于这里... 这告诉你像大多数其他C标准函数一样,你需要检查返回值例如,当你调用fopen时,你需要编写类似于if (fp == NULL) { /* 处理错误 */ }的惯用语。

你的错误处理在哪里?请注意,返回值不仅仅是一个二进制选择;当执行n次转换时,在范围内有n+2个可能的返回值: EOF0 .. n。在尝试使用fscanf之前,您应该了解每个返回值的含义。


顺便提一下,如果你懒得阅读手册,那我不知道你来这里干嘛... 事实上,阅读手册比问问题要更省力 - autistic

1
#include <stdio.h>
int main(){
    char arr[40];
    int i;
    for( i = 0; i < sizeof(arr); i +=2 ){
        scanf("%c%c",&arr[i],&arr[i+1]);
        if( arr[i] == '\n' && arr[i+1] == '\n' )
            break;
    }
    printf("%s", arr);
    return 0;    
}

如果输入在 '\n' 之前有奇数个字符怎么办?试试 "a<enter>":arr[i] 将包含 'a',而 arr[i + 1] 将得到 '\n'。你将不得不键入 <enter> 三次(与像 "ab<enter>" 这样的偶数字符相比需要键入两次)。这就是错误的。 - Bruno

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接