【问题标题】:Proper way to read integer with scanf?用scanf读取整数的正确方法?
【发布时间】:2019-11-19 22:04:04
【问题描述】:

我想实现一个读取整数的函数,但这个函数应该是:

  • \n 有弹性
  • ^D (EOF) 有很强的抵抗力
  • 符合printf "42 20 10" | ./a.out

现在我写了这个,但我觉得它很难看,太复杂了:

#include <stdio.h>
#include <stdbool.h>

int read_integer(char *text, int min, int max, int nom) {
    int n;
    bool failure = false;

    do {
        printf("%s [%d] ? : ", text, nom);

        // Slurp spaces    
        scanf("%*[\t ]");

        // Hack to capture default value
        char buf[2];
        if (scanf("%1[\n]", buf) == 1) {
            return nom;
        }

        if (failure = (scanf("%d", &n) == 0 || n < min || n > max)) {
            if (feof(stdin)) {
                printf("\n");
                return nom;
            }
            printf("Error: value should be between %d and %d!\n\n", min, max);
            scanf("%*[^\n]%*1[\n]");
        }     

    } while(failure);

    scanf("%*[^\n]%*1[\n]");

    return n;
}

int main(void) {
    do {
        printf("You said %d\n", read_integer("What's the answer", 10, 50, 42));        
    } while(!feof(stdin));
}

有没有更好的办法?

目前不行,最后一行捕获了从未输入过的42,不显示新行:

$ gcc main.c && ./a.out
What's the answer [42] ? : oops
Error: value should be between 10 and 50!

What's the answer [42] ? : 100
Error: value should be between 10 and 50!

What's the answer [42] ? : You said 42
What's the answer [42] ? :

编辑

在 cmets 中,我尝试使用 fgets 编写相同的内容,但仍然不完美。或者至少非常复杂......

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

/**
 * Read an integer from `stdin`.
 * @param min Minimum accepted value
 * @param max Maximum accepted value
 * @param nom Default value
 * @return captured integer
 */
int read_integer(char *text, int min, int max, int nom) {
    int n = nom;
    bool failure = false;

    do {
        printf("%s [%d] ? : ", text, nom);

        // Read user input
        char line[24];
        do {
            if (fgets(line, sizeof(line), stdin) != line || feof(stdin)) {
                exit(EXIT_FAILURE);
                break;
            }
        } while (strchr(line, '\n') == NULL);

        // Default value?
        {
            char *cursor = line;
            while ((*cursor == ' ' || *cursor == '\t') && *cursor != '\0') {
                cursor++;
            }        
            if (*cursor == '\n') {
                return n;
            }
        }

        // Not a number ?
        if (sscanf(line, "%d", &n) != 1) {
            printf("Error: this is not valid entry!\n\n");
            continue;
        } 

        // Not in the range ?
        if (n < min || n > max) {
            printf("Error: value should be between %d and %d!\n\n", min, max);
            continue;
        }

        return n;
    } while(true);
}

int main() {
    do {
        printf("You said %d\n", 
            read_integer("What's the answer", 10, 50, 42));        
    } while(!feof(stdin));
}

【问题讨论】:

  • 最简单的方法是用fgets()读取一行,然后用sscanf()解析。
  • 任何时候你试图做一些复杂的事情,答案是“不要使用scanf”。您尝试使用scanf 进行的任何复杂操作几乎总是(a)麻烦多于其价值或(b)最终注定要失败。如果您只是使用"%d",并检查scanf's 的返回值,您将实现使用scanf 可以合理实现的所有功能。如果您想要更多的东西,请帮自己一个忙并寻求非scanf 解决方案。
  • 我同意,scanf 是邪恶的,应该很少使用。但这是否可以仅使用 scanf 来做到这一点?
  • @nowox 可以用螺丝刀打钉子——但这是个坏主意,甚至不是一个有趣的挑战。如果您以某种方式仅使用scanf 就可以做到这一点,那么没有人会羡慕您的男子气概,他们只会摇头浪费时间,尤其是因为它太多了 i> 使用fgetsstrtol 更容易获得相同的结果。 scanf 是一个残酷的骗局,它浪费了数以千计的初级程序员的时间,并且不知何故继续这样做,即使我们都知道它是邪恶的。
  • 用 scanf 读取整数的正确方法? 不要。它很脆弱,当它破裂时,你没有可靠的方法来恢复。使用fgets()/getline() 读取一行,然后使用strtol() 解析数字。 strtol() 跳过前导空格并返回它无法转换的第一个字符的地址 - 使用它来“遍历”行,直到 strtol() 失败或您超出解析范围。

标签: c scanf stdin


【解决方案1】:

使用fgetsstrtol,如果strtol忽略了多余的字符,别忘了抱怨(使用可选的endptr检查) .将 fgetsstrtol 放入一个函数中,并在对该函数的调用周围添加验证,这样每次读取整数时就不会重复相同的代码。

【讨论】:

  • 使用 fgets 和 strtol 你在我写评论的时候发布了...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-04
  • 1970-01-01
  • 1970-01-01
  • 2019-05-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多