【问题标题】:Why won't this scanf format-string work? "%[^\n]\n"为什么这个 scanf 格式字符串不起作用? "%[^\n]\n"
【发布时间】:2016-05-17 07:17:32
【问题描述】:

我见过一些例子,人们给 scanf 一个 "%[^\n]\n" 格式字符串来读取整行用户输入。如果我的理解是正确的,这将读取每个字符,直到到达换行符,然后换行符被 scanf 消耗(并且不包含在结果输入中)。

但我无法让它在我的机器上运行。我试过的一个简单例子:

#include <stdio.h>

int main(void)
{
    char input[64];

    printf("Enter some input: ");
    scanf("%[^\n]\n", input);

    printf("You entered %s\n", input);
}

当我运行此程序时,系统提示我输入,我输入了一些字符,然后按 Enter,光标移到下一行的开头,但 scanf 调用没有完成。

我可以按我喜欢的次数按 Enter 键,但它永远不会完成。

我发现结束 scanf 调用的唯一方法是:

  • 在提示符处输入\n 作为第一个(也是唯一一个)字符
  • 在提示符处输入 Ctrl-d 作为第一个(也是唯一一个)字符
  • 输入一些输入,一个或多个\n,零个或多个其他字符,然后按Ctrl-d

我不知道这是否与机器有关,但我很想知道发生了什么。我在 OS X 上,如果相关的话。

【问题讨论】:

  • 不要发布文字图片!
  • 它的 %[^\n],试试这个
  • @user3121023 谢谢,这正是我想使用 `"%[^\n]\n" 的原因。
  • 将结尾的\n 放在调用scanf() 的格式字符串中意味着消耗任何/所有结尾的空白。 (尾随空格会做同样的事情)。所以 scanf() 将不会退出,直到遇到尾随空格以外的其他内容。
  • 关于这一行:scanf("%[^\n]\n", input); 这将允许用户继续输入字符,没有上限。这意味着用户可以溢出input[] 数组。 input[64] 是输入缓冲区的定义,所以该行应该是:scanf("%63[^\n]\n", input); 注意:比输入缓冲区的实际最大长度小一,因为 scanf 带有 '%s' 或 %[..] ' 格式转换说明符将始终将 NUL 字节附加到输入。

标签: c input scanf


【解决方案1】:

根据documentation for scanf(强调我的):

格式字符串由空白字符组成(格式字符串中的任何单个空白字符都会消耗输入中所有可用的连续空白字符),除% 之外的非空白多字节字符(每个这样的字符在格式字符串中消耗了与输入完全相同的一个字符)和转换规范。

因此,您的格式字符串 %[^\n]\n 将首先从输入中读取(并存储)任意数量的非空白字符(由于 %[^\n] 部分),然后,由于以下换行符,读取(和丢弃)任意数量的空白字符,例如空格、制表符或换行符。

因此,要使您的 scanf 停止读取输入,您需要在换行符后至少键入一个 非空格 字符,或者安排输入流结束(例如,通过按 Ctrl+D 在 Unix-ish 系统上)。

相反,要使您的代码按预期工作,只需从格式字符串的末尾删除最后一个 \n(正如 Umamahesh P 已建议的那样)。

当然,这会在输入流中保留换行符。为了摆脱它(如果你想稍后再读另一行),你可以getc它离开流,或者只是附加%*c(意思是“读取一个字符并丢弃它”)甚至%*1[\n] (读取一个换行符并将其丢弃)到您的 scanf 格式字符串的末尾。

Ps. 请注意,您的代码还有其他一些问题。例如,为了避免缓冲区溢出错误,您确实应该使用%63[^\n] 而不是%[^\n] 来限制scanf 将读入缓冲区的字符数。 (限制需要比缓冲区的大小小一,因为 scanf 将始终附加一个尾随空字符。)

此外,%[ 格式说明符始终要求至少有一个匹配字符,如果没有可用的字符将失败。因此,如果您在不输入任何内容的情况下立即按 enter,您的 scanf 将失败(静默,因为您不检查返回值)并且输入缓冲区将充满随机垃圾。为避免这种情况,您应该 a) 检查 scanf 的返回值,b) 在调用 scanf 之前设置 input[0] = '\0',或 c) 最好两者兼而有之。

最后,请注意,如果您只想逐行读取输入,则使用fgets要容易得多。是的,如果你不想要它,你需要自己去除尾随换行符(如果有的话),但这仍然比尝试使用 scanf 来完成它并不真正适合的工作更容易和更安全:

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

void chomp(char *string) {
    int len = strlen(string);
    if (len > 0 && string[len-1] == '\n') string[len-1] = '\0';
}

int main(void)
{
    char input[64];

    printf("Enter some input: ");
    fgets(input, sizeof(input), stdin);
    chomp(input);

    printf("You entered \"%s\".\n", input);
}

【讨论】:

  • 您的回答很好地解释了正在发生的事情以及如何正确处理它。我使用 scanf 是因为这是在我正在使用的书中练习的上下文中,并且我省略了一些细节以使问题更清楚,但是您关于 fgets 和缓冲区溢出的观点得到了适当的注意。谢谢!
【解决方案2】:

scanf()format 中的空白字符具有特殊含义:

空格字符:该函数将读取并忽略任何空格 在下一个非空白字符之前遇到的字符 (空白字符包括空格、换行符和制表符—— 见 isspace)。格式字符串中的单个空格验证任何 从流中提取的空白字符的数量(包括 无)。

因此,"%[^\n]\n" 就相当于"%[^\n] ",告诉scanf() 忽略%[^\n] 之后的所有空白字符。这就是为什么所有'\n's 都被忽略的原因,直到输入非空白字符,您的情况就是这样。

参考:http://www.cplusplus.com/reference/cstdio/scanf/

【讨论】:

    【解决方案3】:

    删除第二个换行符,以下就足够了。

    scanf("%[^\n]", input);
    

    回答原文,

    scanf("%[^\n]\n", input);
    

    如果您在输入后输入非空白字符,这也应该有效。示例:

    Enter some input: lkfjdlfkjdlfjdlfjldj
    t
    You entered lkfjdlfkjdlfjdlfjldj
    

    【讨论】:

    • 通过解释 为什么 它就足够了,以及为什么 OP 看起来合理的语法不起作用,这个答案会得到显着改善。
    • 修改了答案以提供更多详细信息。谢谢。
    • 您的示例代码中应该有“\\n”而不是“\n”。
    • 我认为您编辑的解释不正确。虽然,确实,输入文字 \n 将使 OP 的 scanf() 停止读取输入,因此输入 anything 不是空格。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-26
    • 1970-01-01
    • 1970-01-01
    • 2011-12-21
    • 2015-02-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多