如何使用scanf仅读取整数?

31

我希望代码能一直运行,直到用户输入一个整数值。

这个代码适用于 charchar 数组。

我已经完成了以下内容:

#include <stdio.h>

int main()
{
    int n;
    printf("Please enter an integer: ");
    while(scanf("%d",&n) != 1)
    {
        printf("Please enter an integer: ");
        while(getchar() != '\n');
    }
    printf("You entered: %d\n",n);
    return 0;
}

问题在于,如果用户输入了一个浮点数值,scanf 会接受它。
Please enter an integer: abcd
Please enter an integer: a
Please enter an integer: 5.9
You entered: 5

如何避免这种情况发生?


这个类似吗?http://stackoverflow.com/questions/23610457/how-can-i-distinguish-between-integer-and-character-when-using-scanf - Suvarna Pattayil
5
不是“扫描浮点数”,而是在第一个非数字处停止。对于输入“5xyzNotAFloatNumber”,它也会这样做。 - Jongware
所以不能使用 scanf 来实现吗?我们总是要使用 fgets 吗? - ani627
1
@1336087 这不是必须的,但最好这样做。是的,可以使用 scanf() 完成,但在一般情况下,scanf() 是可怕且不安全的。 - The Paramagnetic Croissant
@TheParamagneticCroissant:感谢您的建议。 :) 我知道可以使用 fgetsstrtol 来完成这个任务,但我想知道是否可以使用 scanf() 来实现。 - ani627
1
请参考 https://dev59.com/nWYq5IYBdhLWcg3w5ktm#36704392 - Yonggoo Noh
9个回答

45
  1. 使用 scanf()
  2. 把它扔进垃圾桶。
  3. 使用 fgets() 获取整行内容。
  4. 使用 strtol() 将行解析为整数,并检查是否消耗了整行的内容。
char *end;
char buf[LINE_MAX];

do {
     if (!fgets(buf, sizeof buf, stdin))
        break;

     // remove \n
     buf[strlen(buf) - 1] = 0;

     int n = strtol(buf, &end, 10);
} while (end != buf + strlen(buf));

2
嗯,看起来如果用户只输入 '\n',循环将会退出。不确定这对 OP 是否重要。 - chux - Reinstate Monica
很遗憾,scanf太大了,无法适应我的bin文件。 :( - James Ashwood
3
使用 buf[strlen(buf) - 1] = 0; 去“删除换行符”是一种极其糟糕的方法。它会删除最后一个字符,无论它是不是换行符。(如果它开始的字符串为空,则还会进入未定义的领域。) - Steve Summit
1
我会使用 buffer[strcspn(buffer, "\n")] = '\0'; 来移除 \n - Andy Sukowski-Bang
除了上面提到的问题,这个解决方案还有几个问题:(1)它没有检查用户输入是否过长而无法适应缓冲区。(2)它将接受太大而不能表示为“int”的输入。(3)它不一致,因为它接受前导空格字符,但拒绝尾随的空格字符。请参阅我的答案,以获得解决这三个问题的解决方案。 - Andreas Wenzel

15

使用 fgetsstrtol 函数。

如果 s 中的整数表示后面跟着其它字符,p 指向该整数表示的末尾后第一个字符,并将该字符存储在指针所指对象中。如果 *p 不是 \n 字符,则表示输入格式有误。

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

int main(void) 
{
    char *p, s[100];
    long n;

    while (fgets(s, sizeof(s), stdin)) {
        n = strtol(s, &p, 10);
        if (p == s || *p != '\n') {
            printf("Please enter an integer: ");
        } else break;
    }
    printf("You entered: %ld\n", n);
    return 0;
}

这个代码直到用户按下回车键才会打印输出,对吗?我错过了什么吗? - Tanner
@Tanner 直到用户输入所需的整数值,程序才会运行。 - David Ranieri
1
好的,太棒了。我正在解决一个类似的问题,但需要在开始时提示,并希望确保我理解这里的逻辑 :) 它运行得很好! - Tanner

9
如果您坚持使用scanf,可以按照以下方式进行操作:
int val;
char follow;  
int read = scanf( "%d%c", &val, &follow );

if ( read == 2 )
{
  if ( isspace( follow ) )
  {
    // input is an integer followed by whitespace, accept
  }
  else
  {
    // input is an integer followed by non-whitespace, reject
  }
}
else if ( read == 1 )
{
  // input is an integer followed by EOF, accept
}
else
{
  // input is not an integer, reject
}

6
尝试在scanf中使用以下模式,它会一直读取直到行末:
scanf("%d\n", &n)

在循环内部不需要使用getchar(),因为scanf会读取整行。浮点数不符合scanf的模式,提示将再次要求输入整数。


1
如果输入的是一个int,则scanf("%d\n",&n)将读取整行(以及更多内容)。 如果没有输入int,则输入将保留在stdin中。 使用"\n"不仅会消耗'\n',而且会扫描任意数量的空格,直到输入非空格字符。 然后将该char放回到stdin中。 因此,当输入'\n'时,scanf()不会立即返回。 - chux - Reinstate Monica
1
参见scanf()和格式字符串中的尾随空格——如果你珍惜用户的理智,就不要这么做。 - Jonathan Leffler

4

一种可能的解决方案是反向思考:接受浮点数作为输入,如果浮点数不是整数,则拒绝输入:

int n;
float f;
printf("Please enter an integer: ");
while(scanf("%f",&f)!=1 || (int)f != f)
{
    ...
}
n = f;

尽管如此,这允许用户输入像12.0或12e0这样的内容。

1
注意:当输入为“abcd”时,代码仍保留在stdin中。同时,尝试使用输入“2147483647”或“0.9999999999”。 - chux - Reinstate Monica

4
我知道可以使用fgetsstrtol来完成此操作,但我想知道是否可以使用scanf()来完成(如果可能的话)。
正如其他答案所说,scanf并不适合这个问题,fgetsstrtol是另一种选择(尽管fgets的缺点在于很难检测到输入中的0字节,并且在0字节后无法确定已输入什么内容)。
为了完整起见(假设有效输入是一个整数加上一个换行符):
while(scanf("%d%1[\n]", &n, (char [2]){ 0 }) < 2)

或者,使用赋值抑制的%*1[\n]前后加上%n。不过需要注意(来自Debian手册):
这不是一种转换,尽管可以用赋值抑制字符*来抑制它。C标准说:“执行%n指令不会增加完成执行时返回的分配计数”,但勘误表似乎与此相矛盾。最好不要对%n转换对返回值的影响做任何假设。

3

使用 fgets() 更好。

为了解决仅使用 scanf() 输入的问题,扫描一个 int 和接下来的 char

int ReadUntilEOL(void) {
  char ch;
  int count;
  while ((count = scanf("%c", &ch)) == 1 && ch != '\n')
    ; // Consume char until \n or EOF or IO error
  return count;
}

#include<stdio.h>
int main(void) {
  int n;

  for (;;) {
    printf("Please enter an integer: ");
    char NextChar = '\n';
    int count = scanf("%d%c", &n, &NextChar);
    if (count >= 1 && NextChar == '\n') 
      break;
    if (ReadUntilEOL() == EOF) 
      return 1;  // No valid input ever found
  }
  printf("You entered: %d\n", n);
  return 0;
}

该方法不会重新提示用户,即使用户只输入空格,例如只有Enter键。

在循环中的每一次迭代中,将 NextChar 设置为 '\0';然而,在某些操作系统中,'\n' 是一个多字符项,因此定义为 'int NextChar = 0',这样当没有输入字符时,它将重新提示。 - user3629249
@user3629249 感谢您的想法 - 我认为这不会起作用。当没有输入任何 char 时(例如:只有 '\n'),scanf() 将不会返回,直到输入一些非空白字符。"%d" 会导致 scanf() 消耗空格,直到 EOF 或非空白字符。因此,将 NextChar 设置为这个或那个都没有区别。 - chux - Reinstate Monica
@chux 如果只接受正整数,你会如何修改它?第一个if的修改if (count >= 1 && n > 0 && NextChar == '\n')似乎不起作用。如果我们输入例如-125,标准输出不会有反应,但我们必须再次输入才能显示消息。 - BugShotGG
@Geo Papas if (count >= 1 && NextChar == '\n') 用于检测是否输入了一个数字而没有其他的 char。在这个 if()body 中,应该添加额外的测试来进一步确定 nif (count >= 1 && NextChar == '\n') if (!(n > 0)) continue; break; - chux - Reinstate Monica

2

虽然通常不建议使用scanf进行基于行的用户输入,但我将首先介绍一种使用scanf的解决方案。

这个解决方案通过检查未被scanf消耗的行上剩余字符来工作。通常,这应该只是换行符。然而,由于使用scanf%d格式说明符接受前导空格字符,如果程序也接受尾随空格字符,则会更加一致。

这是我的使用scanf的解决方案:

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

int main( void )
{
    int n;

    //repeat until input is valid
    for (;;) //infinite loop, equivalent to while(1)
    {
        int c;

        //prompt user for input
        printf( "Please enter an integer: " );

        //attempt to read and convert input
        if ( scanf( "%d", &n ) == 1 )
        {
            //verify that remainder of input only consists of
            //whitespace characters
            while ( ( c = getchar() ) != EOF && c != '\n' )
            {
                if ( !isspace(c) )
                {
                    //we cannot use "break" here, because we want
                    //to break out of the outer loop, not the inner
                    //loop
                    goto invalid_input;
                }
            }

            //input is valid
            break;
        }

    invalid_input:

        //print error message
        printf( "Input is invalid!\n" );

        //discard remainder of line
        do
        {
            c = getchar();

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

    printf("You entered: %d\n",n);

    return 0;
}

这个程序具有以下行为:

Please enter an integer: abc
Input is invalid!
Please enter an integer: 6abc
Input is invalid!
Please enter an integer: 6.7
Input is invalid!
Please enter an integer: 6
You entered: 6

这个 scanf 解决方案存在以下问题:

  1. 如果用户输入的数字无法表示为 int(例如大于 INT_MAX 的数字),程序将会出现 未定义行为
  2. 如果用户输入空行,它不会打印错误消息。这是因为 scanf%d 格式化符号会消耗所有前导空格字符。

可以通过使用函数 fgetsstrtol 来解决这两个问题,而不是使用函数 scanf

执行所有这些验证检查会使代码变得相当庞大。因此,将所有代码放入自己的函数中是有意义的。以下是一个示例,它使用了我从我的另一个问题的答案中提取的函数get_int_from_user
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>

int get_int_from_user( const char *prompt );

int main( void )
{
    int number;

    number = get_int_from_user( "Please enter an integer: " );

    printf( "Input was valid.\n" );
    printf( "The number is: %d\n", number );

    return 0;
}

int get_int_from_user( const char *prompt )
{
    //loop forever until user enters a valid number
    for (;;)
    {
        char buffer[1024], *p;
        long l;

        //prompt user for input
        fputs( prompt, stdout );

        //get one line of input from input stream
        if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
        {
            fprintf( stderr, "Unrecoverable input error!\n" );
            exit( EXIT_FAILURE );
        }

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small)
        if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
        {
            int c;

            printf( "Line input was too long!\n" );

            //discard remainder of line
            do
            {
                c = getchar();

                if ( c == EOF )
                {
                    fprintf( stderr, "Unrecoverable error reading from input!\n" );
                    exit( EXIT_FAILURE );
                }

            } while ( c != '\n' );

            continue;
        }

        //attempt to convert string to number
        errno = 0;
        l = strtol( buffer, &p, 10 );
        if ( p == buffer )
        {
            printf( "Error converting string to number!\n" );
            continue;
        }

        //make sure that number is representable as an "int"
        if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
        {
            printf( "Number out of range error!\n" );
            continue;
        }

        //make sure that remainder of line contains only whitespace,
        //so that input such as "6sdfj23jlj" gets rejected
        for ( ; *p != '\0'; p++ )
        {
            if ( !isspace( (unsigned char)*p ) )
            {
                printf( "Unexpected input encountered!\n" );

                //cannot use `continue` here, because that would go to
                //the next iteration of the innermost loop, but we
                //want to go to the next iteration of the outer loop
                goto continue_outer_loop;
            }
        }

        return l;

    continue_outer_loop:
        continue;
    }
}

这个程序具有以下行为:

Please enter an integer: abc
Error converting string to number!
Please enter an integer: 6abc
Unexpected input encountered!
Please enter an integer: 6.7
Unexpected input encountered!
Please enter an integer: 6000000000
Number out of range error!
Please enter an integer: 6
Input was valid.
The number is: 6

0

也许不是最优解... 但只使用 scanfstrtol

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

typedef enum {false, true} bool;

bool isIntString(char* input ) {
    int i;
    for (i = 0; i < strlen(input); i++) {
        if(input[i] < '0' || input[i] > '9') {
            return false;
        }
    }
    return true;
}

int main()
{
    char s[10];
    char *end;
    bool correct = false;
    int result;


    printf("Type an integer value: ");
    do {
        scanf("%s",s);
        if ( isIntString(s)) {
            correct = true;
            result = strtol(s, &end, 10);
            break;
        } else {
            printf("you typed %s\n",s);
            printf("\ntry again: ");
        }
    } while (!correct);


    printf("Well done!! %d\n",result);

    return 0;
}

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