为什么 scanf() 会导致此代码中的无限循环?

发布于 2024-08-11 01:07:08 字数 904 浏览 4 评论 0原文

我有一个小型 C 程序,它只从标准输入读取数字,每个循环周期读取一个数字。如果用户输入一些 NaN,则应将错误打印到控制台,并且输入提示应再次返回。输入“0”时,循环应结束,并且给定正/负值的数量应打印到控制台。程序如下:

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;

    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            printf("Err...\n");
            continue;
        }
        
        if (number > 0) p++;
        else if (number < 0) n++;
        else break; /* 0 given */
    }

    printf("Read %d positive and %d negative numbers\n", p, n);
    return 0;
}

我的问题是,在输入一些非数字(如“a”)时,这会导致无限循环,一遍又一遍地写入“-> Err ...”。我猜这是一个 scanf() 问题,我知道这个函数可以用更安全的函数代替,但这个例子是针对初学者的,只了解 printf/scanf、if-else 和循环。

我已经阅读了问题的答案scanf() 跳过 C 中的所有其他 while 循环 并浏览其他问题,但没有任何内容真正回答这个特定问题。

I've a small C-program which just reads numbers from stdin, one at each loop cycle. If the user inputs some NaN, an error should be printed to the console and the input prompt should return again. On input of "0", the loop should end and the number of given positive/negative values should be printed to the console. Here's the program:

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;

    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            printf("Err...\n");
            continue;
        }
        
        if (number > 0) p++;
        else if (number < 0) n++;
        else break; /* 0 given */
    }

    printf("Read %d positive and %d negative numbers\n", p, n);
    return 0;
}

My problem is, that on entering some non-number (like "a"), this results in an infinite loop writing "-> Err..." over and over. I guess it's a scanf() issue and I know this function could be replace by a safer one, but this example is for beginners, knowing just about printf/scanf, if-else and loops.

I've already read the answers to the questionscanf() skips every other while loop in C and skimmed through other questions, but nothing really answer this specific problem.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

新人笑 2024-08-18 01:07:08

scanf 仅消耗与格式字符串匹配的输入,返回消耗的字符数。任何与格式字符串不匹配的字符都会导致其停止扫描并将无效字符保留在缓冲区中。正如其他人所说,在继续之前,您仍然需要将无效字符从缓冲区中清除。这是一个相当肮脏的修复,但它会从输出中删除有问题的字符。

char c = '0';
if (scanf("%d", &number) == 0) {
  printf("Err. . .\n");
  do {
    c = getchar();
  }
  while (!isdigit(c));
  ungetc(c, stdin);
  //consume non-numeric chars from buffer
}

编辑:修复了代码以一次性删除所有非数字字符。不会再为每个非数字字符打印出多个“Errs”。

这里是对 scanf 的很好的概述。

scanf consumes only the input that matches the format string, returning the number of characters consumed. Any character that doesn't match the format string causes it to stop scanning and leaves the invalid character still in the buffer. As others said, you still need to flush the invalid character out of the buffer before you proceed. This is a pretty dirty fix, but it will remove the offending characters from the output.

char c = '0';
if (scanf("%d", &number) == 0) {
  printf("Err. . .\n");
  do {
    c = getchar();
  }
  while (!isdigit(c));
  ungetc(c, stdin);
  //consume non-numeric chars from buffer
}

edit: fixed the code to remove all non-numeric chars in one go. Won't print out multiple "Errs" for each non-numeric char anymore.

Here is a pretty good overview of scanf.

冷情妓 2024-08-18 01:07:08

scanf() 将“a”保留在输入缓冲区中以供下次使用。无论如何,您可能应该使用 getline() 读取一行,然后使用 strtol() 或类似的方法解析它。

(是的,getline() 是 GNU 特定的,而不是 POSIX。那又怎样?问题被标记为“gcc”和“linux”。getline() 也是唯一的阅读一行文本是明智的选择,除非您想手动完成所有操作。)

scanf() leaves the "a" still in the input buffer for next time. You should probably use getline() to read a line no matter what and then parse it with strtol() or similar instead.

(Yes, getline() is GNU-specific, not POSIX. So what? The question is tagged "gcc" and "linux". getline() is also the only sensible option to read a line of text unless you want to do it all by hand.)

慵挽 2024-08-18 01:07:08

我认为你只需要在继续循环之前刷新缓冲区即可。类似的东西可能会完成这项工作,尽管我无法测试我在这里写的内容:

int c;
while((c = getchar()) != '\n' && c != EOF);

I think you just have to flush the buffer before you continue with the loop. Something like that would probably do the job, although I can't test what I am writing from here:

int c;
while((c = getchar()) != '\n' && c != EOF);
凉月流沐 2024-08-18 01:07:08

由于其他答案指出的 scanf 问题,您确实应该考虑使用另一种方法。我一直发现 scanf 对于任何严肃的输入读取和处理来说都太有限了。更好的主意是使用 fgets 读取整行,然后使用 strtokstrtol 等函数来处理它们(顺便说一句,它们会正确解析)整数并告诉您无效字符的确切开始位置)。

Due to the problems with scanf pointed out by the other answers, you should really consider using another approach. I've always found scanf way too limited for any serious input reading and processing. It's a better idea to just read whole lines in with fgets and then working on them with functions like strtok and strtol (which BTW will correctly parse integers and tell you exactly where the invalid characters begin).

梦断已成空 2024-08-18 01:07:08

不要使用 scanf() 并且必须处理具有无效字符的缓冲区,而是使用 fgets()sscanf()

/* ... */
    printf("0 to quit -> ");
    fflush(stdout);
    while (fgets(buf, sizeof buf, stdin)) {
      if (sscanf(buf, "%d", &number) != 1) {
        fprintf(stderr, "Err...\n");
      } else {
        work(number);
      }
      printf("0 to quit -> ");
      fflush(stdout);
    }
/* ... */

Rather than using scanf() and have to deal with the buffer having invalid character, use fgets() and sscanf().

/* ... */
    printf("0 to quit -> ");
    fflush(stdout);
    while (fgets(buf, sizeof buf, stdin)) {
      if (sscanf(buf, "%d", &number) != 1) {
        fprintf(stderr, "Err...\n");
      } else {
        work(number);
      }
      printf("0 to quit -> ");
      fflush(stdout);
    }
/* ... */
倾`听者〃 2024-08-18 01:07:08

我有类似的问题。我只用scanf就解决了。

输入“abc123看看它是如何工作的。

#include <stdio.h>
int n, num_ok;
char c;
main() {
    while (1) {
        printf("Input Number: ");
        num_ok = scanf("%d", &n);
        if (num_ok != 1) {
            scanf("%c", &c);
            printf("That wasn't a number: %c\n", c);
        } else {
            printf("The number is: %d\n", n);
        }
    }
}

I had similar problem. I solved by only using scanf.

Input "abc123<Enter>" to see how it works.

#include <stdio.h>
int n, num_ok;
char c;
main() {
    while (1) {
        printf("Input Number: ");
        num_ok = scanf("%d", &n);
        if (num_ok != 1) {
            scanf("%c", &c);
            printf("That wasn't a number: %c\n", c);
        } else {
            printf("The number is: %d\n", n);
        }
    }
}
爱殇璃 2024-08-18 01:07:08

在某些平台(尤其是 Windows 和 Linux)上,您可以使用 fflush(stdin); :

#include <stdio.h>

int main(void)
{
  int number, p = 0, n = 0;

  while (1) {
    printf("-> ");
    if (scanf("%d", &number) == 0) {
        fflush(stdin);
        printf("Err...\n");
        continue;
    }
    fflush(stdin);
    if (number > 0) p++;
    else if (number < 0) n++;
    else break; /* 0 given */
  }

  printf("Read %d positive and %d negative numbers\n", p, n);
  return 0;
}

On some platforms (especially Windows and Linux) you can use fflush(stdin);:

#include <stdio.h>

int main(void)
{
  int number, p = 0, n = 0;

  while (1) {
    printf("-> ");
    if (scanf("%d", &number) == 0) {
        fflush(stdin);
        printf("Err...\n");
        continue;
    }
    fflush(stdin);
    if (number > 0) p++;
    else if (number < 0) n++;
    else break; /* 0 given */
  }

  printf("Read %d positive and %d negative numbers\n", p, n);
  return 0;
}
浮云落日 2024-08-18 01:07:08

解决方案:scanf返回0时,需要添加fflush(stdin);

原因:遇到错误时,它似乎会将输入字符留在缓冲区中,因此每次调用 scanf 时,它都会继续尝试处理无效字符,但是永远不要将其从缓冲区中删除。当您调用 fflush 时,输入缓冲区(stdin)将被清除,因此无效字符将不再被重复处理。

您的程序已修改:以下是您的程序经过所需更改后的修改。

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;

    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            fflush(stdin);
            printf("Err...\n");
            continue;
        }

        if (number > 0) p++;
        else if (number < 0) n++;
        else break; /* 0 given */
    }

    printf("Read %d positive and %d negative numbers\n", p, n);
    return 0;
}

The Solution: You need to add fflush(stdin); when 0 is returned from scanf.

The Reason: It appears to be leaving the input char in the buffer when an error is encountered, so every time scanf is called it just keeps trying to handle the invalid character but never removing it form the buffer. When you call fflush, the input buffer(stdin) will be cleared so the invalid character will no longer be handled repeatably.

You Program Modified: Below is your program modified with the needed change.

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;

    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            fflush(stdin);
            printf("Err...\n");
            continue;
        }

        if (number > 0) p++;
        else if (number < 0) n++;
        else break; /* 0 given */
    }

    printf("Read %d positive and %d negative numbers\n", p, n);
    return 0;
}
陈甜 2024-08-18 01:07:08

尝试使用这个:

if (scanf("%d", &number) == 0) {
        printf("Err...\n");
        break;
    }

这对我来说效果很好...试试这个..
继续语句不合适,因为Err..应该只执行一次。所以,尝试我测试过的break...这对你来说效果很好...我测试过...

try using this:

if (scanf("%d", &number) == 0) {
        printf("Err...\n");
        break;
    }

this worked fine for me... try this..
the continue statement is not appropiate as the Err.. should only execute once. so, try break which I tested... this worked fine for you.. i tested....

煞人兵器 2024-08-18 01:07:08

当输入非数字时,会发生错误,并且非数字仍保留在输入缓冲区中。你应该跳过它。此外,即使是这种符号组合,例如 1a 也会首先被读取为数字 1,我认为您也应该跳过此类输入。

该程序可以如下所示。

#include <stdio.h>
#include <ctype.h>

int main(void) 
{
    int p = 0, n = 0;

    while (1)
    {
        char c;
        int number;
        int success;

        printf("-> ");

        success = scanf("%d%c", &number, &c);

        if ( success != EOF )
        {
            success = success == 2 && isspace( ( unsigned char )c );
        }

        if ( ( success == EOF ) || ( success && number == 0 ) ) break;

        if ( !success )
        {
            scanf("%*[^ \t\n]");
            clearerr(stdin);
        }
        else if ( number > 0 )
        {
            ++p;
        }
        else if ( number < n )
        {
            ++n;
        }
    }

    printf( "\nRead %d positive and %d negative numbers\n", p, n );

    return 0;
}

程序输出可能类似于

-> 1
-> -1
-> 2
-> -2
-> 0a
-> -0a
-> a0
-> -a0
-> 3
-> -3
-> 0

Read 3 positive and 3 negative numbers

When a non-number is entered an error occurs and the non-number is still kept in the input buffer. You should skip it. Also even this combination of symbols as for example 1a will be read at first as number 1 I think you should also skip such input.

The program can look the following way.

#include <stdio.h>
#include <ctype.h>

int main(void) 
{
    int p = 0, n = 0;

    while (1)
    {
        char c;
        int number;
        int success;

        printf("-> ");

        success = scanf("%d%c", &number, &c);

        if ( success != EOF )
        {
            success = success == 2 && isspace( ( unsigned char )c );
        }

        if ( ( success == EOF ) || ( success && number == 0 ) ) break;

        if ( !success )
        {
            scanf("%*[^ \t\n]");
            clearerr(stdin);
        }
        else if ( number > 0 )
        {
            ++p;
        }
        else if ( number < n )
        {
            ++n;
        }
    }

    printf( "\nRead %d positive and %d negative numbers\n", p, n );

    return 0;
}

The program output might look like

-> 1
-> -1
-> 2
-> -2
-> 0a
-> -0a
-> a0
-> -a0
-> 3
-> -3
-> 0

Read 3 positive and 3 negative numbers
残龙傲雪 2024-08-18 01:07:08

我有同样的问题,我找到了一个有点古怪的解决方案。我使用 fgets() 读取输入,然后将其提供给 sscanf()。对于无限循环问题来说,这并不是一个糟糕的解决方案,通过一个简单的 for 循环,我告诉 C 搜索任何非数字字符。下面的代码不允许输入像 123abc 这样的输入。

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

int main(int argc, const char * argv[]) {

    char line[10];
    int loop, arrayLength, number, nan;
    arrayLength = sizeof(line) / sizeof(char);
    do {
        nan = 0;
        printf("Please enter a number:\n");
        fgets(line, arrayLength, stdin);
        for(loop = 0; loop < arrayLength; loop++) { // search for any none numeric charcter inisde the line array
            if(line[loop] == '\n') { // stop the search if there is a carrage return
                break;
            }
            if((line[0] == '-' || line[0] == '+') && loop == 0) { // Exculude the sign charcters infront of numbers so the program can accept both negative and positive numbers
                continue;
            }
            if(!isdigit(line[loop])) { // if there is a none numeric character then add one to nan and break the loop
                nan++;
                break;
            }
        }
    } while(nan || strlen(line) == 1); // check if there is any NaN or the user has just hit enter
    sscanf(line, "%d", &number);
    printf("You enterd number %d\n", number);
    return 0;
}

I had the same problem, and I found a somewhat hacky solution. I use fgets() to read the input and then feed that to sscanf(). This is not a bad fix for the infinite loop problem, and with a simple for loop I tell C to search for any none numeric character. The code below won't allow inputs like 123abc.

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

int main(int argc, const char * argv[]) {

    char line[10];
    int loop, arrayLength, number, nan;
    arrayLength = sizeof(line) / sizeof(char);
    do {
        nan = 0;
        printf("Please enter a number:\n");
        fgets(line, arrayLength, stdin);
        for(loop = 0; loop < arrayLength; loop++) { // search for any none numeric charcter inisde the line array
            if(line[loop] == '\n') { // stop the search if there is a carrage return
                break;
            }
            if((line[0] == '-' || line[0] == '+') && loop == 0) { // Exculude the sign charcters infront of numbers so the program can accept both negative and positive numbers
                continue;
            }
            if(!isdigit(line[loop])) { // if there is a none numeric character then add one to nan and break the loop
                nan++;
                break;
            }
        }
    } while(nan || strlen(line) == 1); // check if there is any NaN or the user has just hit enter
    sscanf(line, "%d", &number);
    printf("You enterd number %d\n", number);
    return 0;
}
所谓喜欢 2024-08-18 01:07:08

为了部分解决您的问题,我只需在 scanf 之后添加这一行:

fgetc(stdin); /* to delete '\n' character */

下面是您的代码,其中包含以下行:

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;

    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            fgetc(stdin); /* to delete '\n' character */
            printf("Err...\n");
            continue;
        }

        if (number > 0) p++;
        else if (number < 0) n++;
        else break; /* 0 given */
    }

    printf("Read %d positive and %d negative numbers\n", p, n);
    return 0;
}

但如果您输入多个字符,程序将继续一个接一个的字符,直到“\n”。

所以我在这里找到了一个解决方案: How to limit input length with scanf

你可以使用这一行:

int c;
while ((c = fgetc(stdin)) != '\n' && c != EOF);

To solve partilly your problem I just add this line after the scanf:

fgetc(stdin); /* to delete '\n' character */

Below, your code with the line:

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;

    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            fgetc(stdin); /* to delete '\n' character */
            printf("Err...\n");
            continue;
        }

        if (number > 0) p++;
        else if (number < 0) n++;
        else break; /* 0 given */
    }

    printf("Read %d positive and %d negative numbers\n", p, n);
    return 0;
}

But if you enter more than one character, the program continues one by one character until the "\n".

So I found a solution here: How to limit input length with scanf

You can use this line:

int c;
while ((c = fgetc(stdin)) != '\n' && c != EOF);
鸠魁 2024-08-18 01:07:08
// all you need is to clear the buffer!

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;
    char clearBuf[256]; //JG:
    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            fgets(stdin, 256, clearBuf); //JG:
            printf("Err...\n");
            continue;
        }

        if (number > 0) p++;
        else if (number < 0) n++;
        else break; /* 0 given */
    }

    printf("Read %d positive and %d negative numbers\n", p, n);
    return 0;
}
// all you need is to clear the buffer!

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;
    char clearBuf[256]; //JG:
    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            fgets(stdin, 256, clearBuf); //JG:
            printf("Err...\n");
            continue;
        }

        if (number > 0) p++;
        else if (number < 0) n++;
        else break; /* 0 given */
    }

    printf("Read %d positive and %d negative numbers\n", p, n);
    return 0;
}
挽梦忆笙歌 2024-08-18 01:07:08

在扫描之前刷新输入缓冲区:

while(getchar() != EOF) continue;
if (scanf("%d", &number) == 0) {
    ...

我本来建议 fflush(stdin),但显然这会导致 未定义行为

为了回应您的评论,如果您希望显示提示,则必须刷新输出缓冲区。默认情况下,只有在打印换行符时才会发生这种情况。喜欢:

while (1) {
    printf("-> ");
    fflush(stdout);
    while(getchar() != EOF) continue;
    if (scanf("%d", &number) == 0) {
    ...

Flush the input buffer before you scan:

while(getchar() != EOF) continue;
if (scanf("%d", &number) == 0) {
    ...

I was going to suggest fflush(stdin), but apparently that results in undefined behavior.

In response to your comment, if you'd like the prompt to show up, you have to flush the output buffer. By default, that only happens when you print a newline. Like:

while (1) {
    printf("-> ");
    fflush(stdout);
    while(getchar() != EOF) continue;
    if (scanf("%d", &number) == 0) {
    ...
送君千里 2024-08-18 01:07:08

嗨,我知道这是一个旧线程,但我刚刚完成一项学校作业,遇到了同样的问题。
我的解决方案是使用 gets() 来获取 scanf() 留下的内容。

这是稍微重写的OP代码;可能对他没有用,但也许对其他人有帮助。

#include <stdio.h>

    int main()
    {
        int number, p = 0, n = 0;
        char unwantedCharacters[40];  //created array to catch unwanted input
        unwantedCharacters[0] = 0;    //initialzed first byte of array to zero

        while (1)
        {
            printf("-> ");
            scanf("%d", &number);
            gets(unwantedCharacters);        //collect what scanf() wouldn't from the input stream
            if (unwantedCharacters[0] == 0)  //if unwantedCharacters array is empty (the user's input is valid)
            {
                if (number > 0) p++;
                else if (number < 0) n++;
                else break; /* 0 given */
            }
            else
                printf("Err...\n");
        }
        printf("Read %d positive and %d negative numbers\n", p, n);
        return 0;
    }

Hi I know this is an old thread but I just finished a school assignment where I ran into this same problem.
My solution is that I used gets() to pick up what scanf() left behind.

Here is OP code slightly re-written; probably no use to him but perhaps it will help someone else out there.

#include <stdio.h>

    int main()
    {
        int number, p = 0, n = 0;
        char unwantedCharacters[40];  //created array to catch unwanted input
        unwantedCharacters[0] = 0;    //initialzed first byte of array to zero

        while (1)
        {
            printf("-> ");
            scanf("%d", &number);
            gets(unwantedCharacters);        //collect what scanf() wouldn't from the input stream
            if (unwantedCharacters[0] == 0)  //if unwantedCharacters array is empty (the user's input is valid)
            {
                if (number > 0) p++;
                else if (number < 0) n++;
                else break; /* 0 given */
            }
            else
                printf("Err...\n");
        }
        printf("Read %d positive and %d negative numbers\n", p, n);
        return 0;
    }
烧了回忆取暖 2024-08-18 01:07:08

我最近遇到了同样的问题,我找到了一个可能对很多人有帮助的解决方案。函数“scanf”在内存中留下一个缓冲区......这就是导致无限循环的原因。因此,如果您的初始 scanf 包含“null”值,您实际上必须将此缓冲区“存储”到另一个变量。这就是我的意思:

#include <stdio.h>
int n;
char c[5];
int main() {
    while (1) {
        printf("Input Number: ");
        if (scanf("%d", &n)==0) {  //if you type char scanf gets null value
            scanf("%s", &c);      //the abovementioned char stored in 'c'
            printf("That wasn't a number: %s\n", c);
        }
        else printf("The number is: %d\n", n);
    }
}

I've recently been through the same problem, and I found a solution that might help a lot of people. The function "scanf" leaves a buffer in memory ... and that's why the infinite loop is caused. So you actually have to "store" this buffer to another variable IF your initial scanf contains the "null" value. Here's what I mean:

#include <stdio.h>
int n;
char c[5];
int main() {
    while (1) {
        printf("Input Number: ");
        if (scanf("%d", &n)==0) {  //if you type char scanf gets null value
            scanf("%s", &c);      //the abovementioned char stored in 'c'
            printf("That wasn't a number: %s\n", c);
        }
        else printf("The number is: %d\n", n);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文