避免 `fgets()` 两次输入命中

Avoiding `fgets()` double Enter hits

我正在尝试从用户那里收集数据,我想使用 fgets() 来完成任务。

在我的 main.c 文件中:

do
    {
        user = ask_user_info();

        // ... Code exporting data to file ...

        free(user);

        fprintf(stdout, "Do you want to add another user?\nChoice: ");
        scanf("%c[^\n]", &choice);

    } while (choice == 'y');

以下是我为完成工作而编写的函数:

UserData *ask_user_info()
{
    char firstname[STRLEN];
    char lastname[STRLEN];
    char username[STRLEN];
    char password[STRLEN];
    char email[STRLEN];

    fprintf(stdout, "First Name: ");
    get_user_input(firstname);
    flush_stdin();

    fprintf(stdout, "Last Name: ");
    get_user_input(lastname);
    flush_stdin();

    fprintf(stdout, "Username: ");
    get_user_input(username);
    flush_stdin();

    fprintf(stdout, "Password: ");
    get_user_input(password);
    flush_stdin();

    fprintf(stdout, "Email: ");
    get_user_input(email);
    flush_stdin();

    return fill_fields(firstname, lastname, username, password, email);
}

void get_user_input(char *input)
{
    int length;
    char *buffer = (char *) malloc (STRLEN * sizeof(char));
    (*buffer) = '[=11=]';

    if (fgets(buffer, STRLEN, stdin) != NULL)
    {
        length = strlen(buffer)-1;
        buffer[length] = '[=11=]';
        strncpy(input, buffer, length+1);
    }

    free(buffer);
}

UserData *fill_fields(const char firstname[], const char lastname[], 
                        const char username[], const char password[], const char email[])
{
    UserData *user = (UserData *) malloc (sizeof(UserData));

    user->firstname = (char *) malloc (strlen(firstname) * sizeof(char));
    strncpy(user->firstname, firstname, strlen(firstname));
    user->lastname = (char *) malloc (strlen(lastname) * sizeof(char));
    strncpy(user->lastname, lastname, strlen(lastname));
    user->username = (char *) malloc (strlen(username) * sizeof(char));
    strncpy(user->username, username, strlen(username));
    user->password = (char *) malloc (strlen(password) * sizeof(char));
    strncpy(user->password, password, strlen(password));
    user->email = (char *) malloc (strlen(email) * sizeof(char));
    strncpy(user->email, email, strlen(email));

    return user;
}

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

一切都编译并且一切似乎都正常工作,但是当程序要求用户输入时,(大多数时候)它需要用户点击 Enter 两次才能继续到下一个输入。

一方面我知道这种行为是由 flush_stdin() 函数引起的。另一方面,我无法摆脱 flush_stdin() 函数,因为它确保不会跳过任何输入字段。如果我这样做了,程序会输出类似 First Name: Last Name:

的内容

如何避免重复命中并确保收集到所有输入?

真正的诀窍是不要混合使用 fgets()scanf() 和相关函数。原因是它们处理换行符的方式不同。 fgets() 如果缓冲区足够长以容纳完整行,则将其读入。 scanf(),根据格式字符串的选择,在换行符处停止并将其留在流中 - 它将立即导致下一次调用 fgets() 到 return。

尝试使用 fgets() 来处理来自用户的 all 阅读。如果需要,您可以使用 sscanf() 来解释用户输入的字符串。

这样做的一个好处是您实际上不需要 flush_stdin() 函数,因为不会出现有时需要丢弃有时不需要的换行符的虚假情况。

双重输入的问题来自如下所示的调用组合:

get_user_input(some_field);
flush_stdin();
  • 第一行代码要求用户按 Enter 以向 fgets 表明他已完成输入响应。
  • 第二行代码要求用户按Enter以结束flush_stdin
  • 内的while循环

要解决此问题,您应该删除对 flush_stdin 的调用,并修改 get_user_input 以跳过空行:

void get_user_input(char *input) {
    int length;
    *input = '[=11=]';
    do {
        if (fgets(buffer, STRLEN, stdin) == NULL) {
            break;
        }
        length = strlen(buffer)-1;
        buffer[length] = '[=11=]';
    } while (length == 0);
}

do/while 循环将跳过输入中意外的 '\n',因为它从用户那里获取新数据,而无需显式刷新输入。

请注意,您可以从 get_user_input 中删除 malloc/free 以及字符串复制,因为调用方已经提供了足够的缓冲区。您的代码假定缓冲区至少 STRLEN 长,但最好将长度作为第二个参数显式传递给函数。