避免`fgets()`double输入命中

时间:2015-05-17 10:43:10

标签: c fgets

我正在尝试从用户收集数据,我想使用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) = '\0';

    if (fgets(buffer, STRLEN, stdin) != NULL)
    {
        length = strlen(buffer)-1;
        buffer[length] = '\0';
        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:

的内容

如何避免双击并确保收集所有输入?

2 个答案:

答案 0 :(得分:2)

真正的诀窍是不要将fgets()scanf()及相关功能的使用混为一谈。原因是它们以不同方式处理换行符的存在。如果缓冲区足够长以容纳完整的行,fgets()会读取它。 scanf(),取决于格式字符串的选择,在换行符处停止并将其保留在流中 - 这将导致下一次调用fgets()立即返回。

尝试使用fgets()处理来自用户的所有读数。您可以使用sscanf()来解释用户在需要时输入的字符串。

这样做的一个好处是你实际上并不需要flush_stdin()函数,因为有时候需要丢弃新行的虚假案例,有时候不会丢弃。

答案 1 :(得分:1)

双输入的问题来自于看起来像这样的调用组合:

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

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

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

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

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