用 scanf() 读取一个完整的句子

Reading a full sentence with a scanf()

我有以下功能:

Customer_Account new_acc(){
  Customer_Account new_account;
  new_account.account_id = account_id;
  account_id++;
  printf("\nEnter your name: ");
  scanf("%s", new_account.name);
  printf("Enter your date of birth (DD.MM.YYYY): ");
  scanf("%d.%d.%d", &new_account.birth_date.day, &new_account.birth_date.month, &new_account.birth_date.year);
  printf("\nEnter your ID number: ");
  scanf("%lf", &new_account.tc_ID_number);
  printf("\nEnter your address: ");
  scanf("%s", new_account.adress);
  printf("\nEnter your phone number: ");
  scanf("%lf", &new_account.phone);
  printf("\nEnter the amount you would like to deposit: ");
  scanf("%lf", &new_account.amount);
  printf("\n3[1;32m Account successfully created3[0m\n\n");
  sleep(1);
}

但是当我 运行 它并输入名字和姓氏时,它会给出以下输出:

Enter your name: name surname
Enter your date of birth (DD.MM.YYYY): 
Enter your ID number: 
Enter your address: 
Enter your phone number: 

如果我没记错的话,“\n”留在缓冲区中,因此它会跳过下一个 scanf()。我查找了一些解决方案,但它们也没有用。当我只输入一个单词时,我不会 运行 遇到问题。如何在没有 运行ning 的情况下阅读完整的句子?

使用 scanf() 对新的 C 程序员来说充满了陷阱。特别是因为不同的 转换说明符 处理前导空格的方式不同。除了 %c%[…](扫描集)和 %n 之外的所有内容都将忽略前导空格。此外,输入缓冲区中留下的任何(意思是任何)杂散 non-whitespace 字符将导致 matching-failure,或者如果它碰巧匹配下一个转换说明符,它将被视为您的输入,而不是您想要的。

此外,除非您 检查 return 以验证,否则您无法可靠地正确使用任何 user-input 函数输入和转换是否成功。

要消除与使用 scanf() 进行用户输入相关的所有陷阱,建议您使用 line-oriented 输入函数,例如 fgets() (或 POSIX getline())一次读取整行输入。所有 line-oriented 输入函数都读取尾部 '\n' 并将其包含在它们填充的缓冲区中。如果您需要解析行中的值,只需将缓冲区传递给 sscanf() 并像使用 scanf() 一样提取值,除非您从填充的缓冲区而不是 stdin 中读取。这样,无论转换成功还是失败,都不会影响您的下一次尝试输入——您用 fgets().

消耗了整行输入

如果您需要将读取的内容存储到缓冲区(字符数组)中,只需使用 strcspn() trim 结尾的 '\n',例如

#define MAXC 1024
...
    char buf[MAXC];
    
    fputs ("enter some string: ", stdout);
    if (fgets (buf, MAXC, stdin) == NULL) {    /* validate EVERY input */
        fputs ("(user canceled input with manual EOF)\n", stdout);
        return 1;
    }
    
    buf[strcspn (buf, "\n")] = 0;       /* trim \n by overwriting with [=10=] */

仅此而已,用户输入不会有任何问题。

在函数中进行输入时,您必须提供有意义的 return 类型,该类型可以指示用户输入是成功还是失败。 Return 结构不提供输入是否成功的任何指示。相反,使 return 键入任何可以区分 success/failure 的类型,并将指向要填充的结构的指针作为参数传递。 int 的 return 类型很好,0 的 return 表示失败, 1 表示成功(或者你想怎么做——只是保持一致)。

用户输入可能看起来无聊和多余,但你不是在创造艺术,你是在创造一个输入例程,它可以处理用户提供的任何输入(或猫踩在键盘上)并且仍然正确地产生(定义) 生成您的代码。

考虑到这一点,您可以将输入例程更改为使用 fgets() 等类似于以下内容的内容:

#define MAXC 1024       /* if you need a constant, #define one (or more) */

/* pass pointer to account to fill so return can be used
 * to indicate success or failure of input routine,
 * return 0 on error or manual EOF, 1 on successful input.
 */
int new_acc (Customer_Account *new_account)
{
    char buf[MAXC];                         /* array to hold all user input */
    
    new_account->account_id = account_id;
    account_id++;
    
    /* name */
    fputs ("\nEnter your name: ");          /* no conversion, printf not required */
    if (fgets (buf, MAXC, stdin) == NULL) { /* validate read with fgets */
        fputs ("(user canceled input)\n", stderr);
        return 0;                           /* return 0 on failure */
    }
    buf[strcspn (buf, "\n")] = 0;           /* trim \n */
    strcpy (new_account->name, buf);        /* copy buf to new_account->name */
    
    /* date of birth */
    fputs ("Enter your date of birth (DD.MM.YYYY): ", stdout);
    if (!fgets (buf, MAXC, stdin)) {        /* same validation -- just shorthand */
        fputs ("(user canceled input)\n", stderr);
        return 0;                           /* return 0 on failure */
    }
    if (sscanf (buf, "%d.%d.%d", 
                &new_account->birth_date.day,
                &new_account->birth_date.month,
                &new_account->birth_date.year) != 3) {
        fputs ("error: DOB - input or matching failure.\n", stderr);
        return 0;
    }
    
    /* ID */
    fputs ("\nEnter your ID number: ", stdout);
    if (!fgets (buf, MAXC, stdin)) {        /* ditto */
        fputs ("(user canceled input)\n", stderr);
        return 0;                           /* return 0 on failure */
    }
    if (sscanf (buf, "%lf", &new_account->tc_ID_number) != 1) {
        fputs ("error: ID - input or matching failure.\n", stderr);
        return 0;
    }
    
    /* Address */
    fputs ("\nEnter your adress: ", stdout);
    if (!fgets (buf, MAXC, stdin)) {        /* ditto */
        fputs ("(user canceled input)\n", stderr);
        return 0;                           /* return 0 on failure */
    }
    buf[strcspn (buf, "\n")] = 0;           /* trim \n */
    strcpy (new_account->address, buf);     /* copy buf to new_account->name */
    
    /* Phone Number */
    fputs ("\nEnter your phone number: ");
    if (!fgets (buf, MAXC, stdin)) {        /* ditto */
        fputs ("(user canceled input)\n", stderr);
        return 0;                           /* return 0 on failure */
    }
    if (sscanf (buf, "%lf", &new_account->phone) != 1) {
        fputs ("error: Phone - input or matching failure.\n", stderr);
        return 0;
    }
    
    /* Amount */
    fputs ("\nEnter the amount you would like to deposit: ", stdout);
    if (!fgets (buf, MAXC, stdin)) {        /* ditto */
        fputs ("(user canceled input)\n", stderr);
        return 0;                           /* return 0 on failure */
    }
    if (sscanf (buf, "%lf", &new_account->amount) != 1) {
        fputs ("error: Amount - input or matching failure.\n", stderr);
        return 0;
    }
    
    return 1;        /* return 1 indicating successful input */
}

(注意: 我会删除出生日期中 "%d" 之间的 '.' 因为除非用户输入 '.' matching-failure 会发生——这很可能是您的问题的根源)

它基本上对每个输入做同样的事情——没关系。冗余——是的,坚实的——也是。

另一个关于用 scanf() 读取字符串的警告。如果您不提供 field-width 修饰符以确保您不超出数组范围,scanf() 并不比 gets() 更安全,请参阅 Why gets() is so dangerous it should never be used!

您可以通过声明一个临时结构来填充调用函数并传递一个指向该函数的指针来使用输入例程,例如

    for (;;) {
        Customer_Account tmp;
        
        if (!new_acc (&tmp))
            break;
        
        /* assign tmp to final storage here */
        
        sleep (1);
    }

这大致相当于您的函数的工作方式——除了我们检查 new_acc (&tmp) 的 return——我们知道输入是成功还是失败。

检查一下,如果您还有其他问题,请告诉我。