字符串比预期的要长,并被视为多个输入

String is longer than expected, and is treated as multiple inputs

这是我第一次 post 来这里,我对 C 语言还比较陌生(这只是我在 uni 上的第二个单元)。

基本上,我正在尝试编写一段代码,询问用户是否希望继续。如果他们写 "yes",代码将继续循环。如果他们写否,代码将终止程序。如果他们写了其他任何东西,它只会再次询问,直到有可识别的输入。我正在使用 scanf 和 printf,并尝试仅使用它们来创建此代码。

char userInput[4];
userInput[0] = '[=10=]';

while ((strcmp(userInput, "yes") != 0) && (strcmp(userInput, "no") != 0))
{
    printf("Do you want to continue (yes/no) :");
    scanf("%3s", userInput);
}

为了简单起见,我没有包含其余代码。

比如我的输入是

xxx

输出是

Do you want to continue (yes/no) :

很好。但是,如果我输入:

xxxx

输出为:

Do you want to continue (yes/no) :Do you want to continue (yes/no) :

如果我输入

xxxxxxx

我明白了

Do you want to continue (yes/no) :Do you want to continue (yes/no) :Do you want to continue (yes/no) :

看起来它几乎是在预期长度之后保存其余字符,并立即将它们发送到输入或其他什么?我想针对太长的字符串建立保护,但我认为这并不理想。

如果问题结构不当,我很抱歉,欢迎任何建设性的批评。我在任何地方都找不到这个确切的问题,所以我想问问自己。

欢迎使用 scanf 进行用户输入的陷阱。 fgets 或 POSIX getline 是更好的选择,因为它们避免了 scanf 固有的许多 ("What's left in the input buffer?") 问题。也就是说,知道如何正确使用 scanf 很重要。

使用scanf无非就是计算(1)实际读取了哪些字符? (2) 输入缓冲区中哪些字符未读?为了处理第 2 个问题,您需要一些方法来清空在调用 scanf 之间输入缓冲区中剩余的所有字符,以确保您不会被剩余的任何字符咬住。您还必须知道至少要保留一个字符,否则您将只是阻塞等待输入被清除。清空 stdin 的标准方法是循环遍历剩余的任何字符,直到您读取结尾的 "\n'(用户按下 "Enter" 的结果)或 EOF。例如,

void empty_stdin (void)
{
    int c = getchar();
    while (c != '\n' && c != EOF)
        c = getchar();
}

(您也可以将其写成单个 for 语句,例如 for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {},但 while 通常更具可读性。)

不只是正确处理输入的问题。由于要强制用户输入"yes"或"no",一般的做法是不断循环,直到满足输入条件。你在正确的轨道上,但没有完全到达那里。相反,你可以这样做:

char buffer[1024] = "";         /* don't skimp on buffer size */

for (;;) {                      /* loop continually */
    int rtn;                    /* value to hold return from scanf */
    printf ("Do you want to continue (yes/no): ");  /* prompt */
    rtn = scanf ("%1023[^\n]", buffer);             /* read input */
    if (rtn == EOF) {           /* user canceled input, bail */
        fprintf (stderr, "user canceled input.\n");
        exit (EXIT_FAILURE);
    }
    else if (rtn == 1) {        /* value stored in buffer */
        /* check for input meeting criteria */
        if (strcmp (buffer, "yes") == 0 || strcmp (buffer, "no") == 0) {
            empty_stdin();  /* don't leave characters in stdin */
            break;          /* success, break input loop */
        }
    }
    /* handle wrong input */
    empty_stdin();  /* don't leave characters in stdin */
    fprintf (stderr, "error: invalid input.\n\n");
}

如果您以这种方式进行读取,您可以控制输入缓冲区中保留的内容,不仅针对循环中的每次读取,而且可以确保在您离开时用户输入的缓冲区中没有字符。

如上所述,正确使用 scanf 只是一个计算问题,一旦您知道每个 格式说明符 的行为方式,以及 input or matching failures 在失败点停止读取,你可以回答以下问题(1)实际读取了哪些字符?和 (2) 输入缓冲区中哪些字符未读?

一个简短的例子可能会有所帮助:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main (void) {

    char buffer[1024] = "";         /* don't skimp on buffer size */

    for (;;) {                      /* process loop - doing stuff */
        printf ("\nprogram processing....\n\n");
        for (;;) {                      /* input - loop continually */
            int rtn;                    /* value to hold return from scanf */
            printf ("Do you want to continue (yes/no): ");  /* prompt */
            rtn = scanf ("%1023[^\n]", buffer);             /* read input */
            if (rtn == EOF) {           /* user canceled input, bail */
                fprintf (stderr, "user canceled input.\n");
                exit (EXIT_FAILURE);
            }
            else if (rtn == 1) {        /* value stored in buffer */
                /* check for input meeting criteria */
                if (strcmp (buffer, "yes") == 0) {
                    empty_stdin();  /* don't leave characters in stdin */
                    break;          /* success, break input loop */
                }
                else if (strcmp (buffer, "no") == 0)
                    goto alldone;
            }
            /* handle wrong input */
            empty_stdin();  /* don't leave characters in stdin */
            fprintf (stderr, "error: invalid input.\n\n");
        }
    }
    alldone:;

    printf ("that's all folks...\n");
}

(注意: 以上 "yes""no" 响应分别进行比较,以允许在 "yes" 或 [=29 上继续处理=] 在 "no")

例子Use/Output

$ ./bin/scanf_yes_no

program processing....

Do you want to continue (yes/no): what?
error: invalid input.

Do you want to continue (yes/no): obnoxious long line because cat stepped on kbd
error: invalid input.

Do you want to continue (yes/no): yes

program processing....

Do you want to continue (yes/no): yes

program processing....

Do you want to continue (yes/no):
error: invalid input.

Do you want to continue (yes/no): no
that's all folks...

当您使用 scanf("%3s", userInput) 时,它将只读取 3'[=13=]' 结尾的字符到 userInput 缓冲区。但是,如果您输入的字符多于 3 个字符,其余字符仍存在于输入缓冲区中等待 scanf 读取。 您可以在每次 scanf 之后清空缓冲区以避免这种意外。

#include <stdio.h>
#include <string.h>
int main(void)
{
   char userInput[4];
   userInput[0] = '[=10=]';
   int c;

   while ((strcmp(userInput, "yes") != 0) && (strcmp(userInput, "no") != 0))
   {
        printf("Do you want to continue (yes/no) :");
        scanf("%3s", userInput);

        while(1) // drain the input
        {
            c = getchar ();
            if(c=='\n') break;
            if(c==EOF)  return -1;
        }
    }      
    return 0;
}

It seems like it's almost saving the rest of the characters after the expected length, and sending them to the input straight away or something?

是的,这正是发生的事情。

那些字符就在那里,坐在输入缓冲区中,等待被消耗,就像前三个字符在某个时间点一样。

您通过操作系统将字符从键盘发送到终端。根据您编写的内容,您的程序将接受文本直到宇宙尽头(尽管一次三个字符)。只要有更多的可用字符,它就会吃掉它们,而不管您的击键在时间上分布多远都没有关系。 C++ 无法知道您希望它执行任何其他操作。

也就是说,通过调整您的语义(如其他答案所示),对于像这样的 call/response 提示,可以获得更直观的行为。