问题:

我在使用 scanf() 时遇到问题。我从阅读论坛等中了解到 scanf() 在 C 中存在很大问题,但我仍在学习基础知识,所以我不知道所有细节。

我想解决的代码片段。

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

struct Biodata {
    char name[21],
    address[65],
    date[11],
    phone[17];
};

int main() {
    struct Biodata bio[10];
    int input_max = 0,
    index_struct = 0;
    while (printf("Input the amount of data! ") && scanf("%[0-9]*d", &input_max) < 1) {
        printf("Error! Please, try again!\n\n");
       fflush(stdin);
    }
    for (index_struct = 1; index_struct <= input_max; index_struct++) {
        printf("Your input data count is %d.\n", input_max);
        printf("Data %d.\n", index_struct);
        fflush(stdin);
        while (printf("Name\t: ") && scanf("%[A-Z a-z]s", &bio[index_struct].name) < 1) {
            printf("Error! Please, try again!\n\n");
            fflush(stdin);
        }
        fflush(stdin);
        while (printf("Address\t: ") && scanf("%[^\n]s", &bio[index_struct].address) < 1) {
            printf("Error! Please, try again!\n\n");
            fflush(stdin);
        }
        fflush(stdin);
        while (printf("Birthdate\t: (YYYY/MM/DD)\n") && scanf("%[^\n A-Z a-z]s", &bio[index_struct].date) < 1) {
            printf("Error! Please, try again!\n\n");
            fflush(stdin);
        }
        fflush(stdin);
        while (printf("Phone Number\t: ") && scanf("%[^\n A-Z a-z]s", &bio[index_struct].phone) < 1) {
            printf("Error! Please, try again!\n\n");
            fflush(stdin);
        }
        fflush(stdin);
        printf("\n");
    }
    printf("Input the index number you'd like the data to be pulled from! ");
    scanf("%d", &index_struct);
    printf("%-10s\n%-64s\n%-10s\n%-16s",
           bio[index_struct].name, bio[index_struct].address,
           bio[index_struct].date, bio[index_struct].phone);
    return 0;
}

当输入是空格时,我试图使每个输入都能够输出错误。 [^\n][A-Z][0-9] 的扫描集通常在更简单的情况下帮助我。但是,在这个中,当我在 input_max 中输入任何数字作为输入时
while (printf("Input the amount of data! ") && scanf("%[0-9]*d", &input_max) < 1) {
    printf("Error! Please, try again!\n\n");
    fflush(stdin);
}
input_max 给出的数字与其给出的数字不同。这里发生了什么?我能做些什么来解决它?

我也不完全理解那段代码是如何作为错误输出工作的,因为我在网上的某个地方发现了它。

编辑: 正如@JonathanLeffler 所建议的那样,scanf() 将我的输入作为 ASCII、ISO 8859-x 或 Unicode 或所有这些中的代码点。但是,当我删除扫描集时,输入保持原样,将其转换为 scanf(%d, &input_max) 。但是,我确实需要扫描集,所以我可以输入一个空格,并在我在 scanf() 中输入一个空格时弹出我设置的错误消息。

最佳答案

您将 [ 误认为是 %s%d 的修饰符——例如%3d — 不是。 %[ 本身是一个转换说明符,工作方式类似于 %s

因此,正如@user3629249 在评论中指出的那样,s 说明符末尾的 d%[ (例如在 %[^\n A-Z a-z]s 中)是无关紧要的。 %[ 中的空格也很重要。所以 %[A-z a-z]%[A-Za-z] 不同

让我们看看在打开格式警告的情况下编译时遇到的问题。 (-Wformat 如果你使用 gcc 或 clang),你会得到类似的东西:

foo.c:19:68: warning: format specifies type 'char *' but the argument has type 'int *' [-Wformat]
  while (printf("Input the amount of data! ") && scanf("%[0-9]*d", &input_max)<1) {
                                                        ~~~~~      ^~~~~~~~~~
                                                        %d
foo.c:29:55: warning: format specifies type 'char *' but the argument has type 'char (*)[21]' [-Wformat]
    while (printf("Name\t: ") && scanf("%[A-Z a-z]s", &bio[index_struct].name)<1) {
                                        ~~~~~~~~~     ^~~~~~~~~~~~~~~~~~~~~~~
foo.c:34:54: warning: format specifies type 'char *' but the argument has type 'char (*)[65]' [-Wformat]
    while (printf("Address\t: ") && scanf("%[^\n]s", &bio[index_struct].address)<1) {
                                           ~~~~      ^~~~~~~~~~~~~~~~~~~~~~~~~~
foo.c:39:78: warning: format specifies type 'char *' but the argument has type 'char (*)[11]' [-Wformat]
    while (printf("Birthdate\t: (YYYY/MM/DD)\n") && scanf("%[^\n A-Z a-z]s", &bio[index_struct].date)<1) {
                                                           ~~~~~~~~~~~~~     ^~~~~~~~~~~~~~~~~~~~~~~
foo.c:44:67: warning: format specifies type 'char *' but the argument has type 'char (*)[17]' [-Wformat]
    while (printf("Phone Number\t: ") && scanf("%[^\n A-Z a-z]s", &bio[index_struct].phone)<1) {
                                                ~~~~~~~~~~~~~     ^~~~~~~~~~~~~~~~~~~~~~~~

您的代码还有其他问题:
  • 你从 1 到 10 索引你的 Biodata(查看 index_struct for loop)但是它被声明为一个大小为 10 的数组 Biodata bio[10];

  • 在 C 中数组是基于 0 的,所以它们从 0size-1 并且你的 for 循环会遇到段错误,因为 bio[10] 将是未定义的。
  • 您在 for 循环中要求 input_max 但在 for 循环中需要它。
  • 如果 input_max 大于 bio 数组的声明大小,会发生什么?

  • 其他一些需要考虑的好事情:
  • printf 是一个用于报告错误的错误函数,错误应该转到 stderr,而不是 stdout,因此最好使用 fprintf 并指定 stderr。
  • 既然您有兴趣确保正确解析输入,为什么不创建自己的解析器而不是使用 scanf?
  • 您正在强制重新提示错误,让我们将其分解为自己的功能。

  • 我们一起做吧。

    注意我在下面使用的 C 风格和命名约定

    我的 C 风格与你的有点不同,我有我的理由:-),既然这只是意见,让我们一起去吧。

    对我们想要的内容有一些评论的结构
    struct biodata {
        char name[21];        /* format: FirstName LastName */
        char address[65];     /* format: Free-form upto 65 chars */
        char birthday[11];    /* format: YYYY/MM/DD */
        char phone[17];       /* format: up to digits or a spaces */
    };
    

    一些匹配器

    下面是一组匹配函数,它接收输入行并告诉我们该行是否完全符合我们的预期。如果是,则返回 true,否则返回 false。为此,您将需要 #include <stdbool.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <stdbool.h>
    #include <time.h>
    
    struct biodata {
            char name[21];        /* format: FirstName LastName */
            char address[65];     /* format: Free-form upto 65 chars */
            char birthday[11];    /* format: YYYY/MM/DD */
            char phone[17];       /* format: up to digits or a spaces */
    };
    
    
    bool match_name(const char *line)
    {
        char tmp[128];
            return line!=NULL
            && sscanf(line, "%128[A-Za-z]%128[ ]%128[A-Za-z]", tmp, tmp, tmp) == 3
            && strlen(tmp) < 21;
    }
    
    bool match_address(const char *line)
    {
        return line != NULL
            && strlen(line) > 5
            && strlen(line) < 65; /* no format check here, maybe later */
    }
    
    bool match_telephone(const char *line)
    {
        char tmp[128];
        return line  /* notice the alternate form of checking line!=NULL */
            && sscanf(line, "%128[0-9 ]", tmp)==1
            && strlen(tmp) < 17;
    }
    
    /* here we'll use strptime to see if our line is a valid date */
    bool match_birthday(const char *line)
    {
         struct tm tm; /* time structure */
         if(!line)
              return false;
    
         return strptime(line, "%Y/%m/%d", &tm) != NULL;
    }
    
    char * ask(const char *prompt, char *line, size_t maxlen)
    {
        printf("%-30s:", prompt);
        fflush(stdout);
        fgets(line, maxlen, stdin);
        return line; /* we return the pointer for ease of use */
    }
    
    /* a test function */
    void test_matchers() {
    
        char line[256];
        /* remember ask returns our line so we are able to pass it to our matchers */
        while(!match_name(ask("Name (first last)", line, sizeof(line))))
            ;
        while(!match_address(ask("Address (at least 5 chars)", line, sizeof(line))))
            ;
        while(!match_birthday(ask("Birthday (YYYY/MM/DD)", line, sizeof(line))))
            ;
        while(!match_telephone(ask("Telephone (max 16 digits)", line, sizeof(line))))
            ;
    }
    
    
    int main()
    {
         test_matchers();
         return 0;
    }
    

    测试一下。
    $ ./bar
    
    Name (first last)             :Ahmed Masud
    Address (at least 5 chars)    :1999 Somewhere Out there, Bingo, Bongo, 10002, USA
    Birthday (YYYY/MM/DD)         :1970/01/10
    Telephone (max 16 digits)     :1-201-555-1212
    

    现在让我们以合理的方式将内容复制到我们的结构中

    打印生物数据的功能
    /* add a function to print a biodata */
    
    void print_bio(const struct biodata *bio)
    {
        printf("***** bio data *****\n"
           "Name: %-10s\nAddress: %-64s\nBirthday: %-10s\nPhone: %-16s\n",
               bio->name, bio->address,
               bio->birthday, bio->phone);
    }
    

    新的主要功能

    请注意,它大部分类似于 test_matches 。除了我们添加了复制
    行到适当的领域
    int main()
    {
    
        char line[256];
        struct biodata bio;
    
        while(!match_name(ask("Name (first last)", line, sizeof(line))))
            ;
        strncpy(bio.name, line, sizeof(bio.name));
    
        while(!match_address(ask("Address (at least 5 chars)", line, sizeof(line))))
            ;
        strncpy(bio.address, line, sizeof(bio.address));
    
        while(!match_birthday(ask("Birthday (YYYY/MM/DD)", line, sizeof(line))))
            ;
        strncpy(bio.birthday, line, sizeof(bio.birthday));
    
        while(!match_telephone(ask("Telephone (max 16 digits)", line, sizeof(line))))
            ;
        strncpy(bio.phone, line, sizeof(bio.phone));
    
        print_bio(&bio);
        return 0;
    }
    

    好的,所以我们可以提示用户并将内容放入我们的结构中,但是在 main 中执行它很笨拙,所以让我们将其放入自己的函数中。
    int get_bio(struct biodata *bio)
    {
    
        char line[256];
    
        while(!match_name(ask("Name (first last)", line, sizeof(line))))
            ;
        strncpy(bio->name, line, sizeof(bio->name));
    
        while(!match_address(ask("Address (at least 5 chars)", line, sizeof(line))))
            ;
        strncpy(bio->address, line, sizeof(bio->address));
    
        while(!match_birthday(ask("Birthday (YYYY/MM/DD)", line, sizeof(line))))
            ;
        strncpy(bio->birthday, line, sizeof(bio->birthday));
    
        while(!match_telephone(ask("Telephone (max 16 digits)", line, sizeof(line))))
            ;
        strncpy(bio->phone, line, sizeof(bio->phone));
    
        return 0;
    }
    
    
    int main()
    {
        struct biodata bio[3]; /* let's get 3 records */
            int i;
    
            /* bio is made up of a bunch of struct biodata's so we divide its size by sizeof the struct biodata to get how many (in our case 3) */
    
            for(i = 0; i < sizeof(bio)/sizeof(struct biodata); i++)
            {
                printf("\n\nEnter record number: %d\n", 1+i); /* why 1+i here ? :) */
                get_bio(&bio[i]);
            }
    
            for(i=0; i < sizeof(bio)/sizeof(struct biodata); i++)
            {
                    print_bio(&bio[i]);
    
            }
        return 0;
    }
    

    锻炼

    我将把其余的功能留作练习。

    同时,我希望你考虑我们开发这个的方式。从最内部的功能开始,慢慢向外移动。

    像乐高积木一样分解问题并首先处理内部部件,测试它们是否完全按照您的要求执行,然后围绕它们慢慢构建。

    显然,匹配器应该在开发 ask 之前单独开发和测试。我把它留给你来分解。

    关于c - scanf ("%[^\n]d", x) 或 scanf ("%[0-9]d", x) 将 1 变为 49,将 2 变为 50,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/55252794/

    10-15 00:25