【问题标题】:Behavior of C scanf() formattingC scanf() 格式化的行为
【发布时间】:2021-01-21 18:35:00
【问题描述】:

免责声明。我见过很多问题,包括几乎完全相同的代码 sn-ps,但似乎没有人回答这个问题。

对于入门级 CS 课程,我们的任务是制作一个带有 ID、名称的简单程序和用户输入的年龄并将其保存到文件中。这很简单,我很快就让它工作了。问题是,为了让名称输入的一部分正常工作,我不得不“欺骗”我遇到的问题。
有问题的代码 sn-p。

int id, age;
char name[40]={0};
printf("ID: ");
scanf("%i",&id);
printf("Name: ");
scanf("%*c");
scanf("%[^\n]%*c",name);
printf("Age: ");
scanf("%i",&age);

这很好用。但是这条线让我很恼火; scanf("%*c"); 它的唯一目的是处理潜伏在流中的 '\n' 字符,可能来自先前的输入。出于某种原因,我觉得这是在作弊,或者如果我必须使用此解决方法,我就做错了。任何提示表示赞赏。

【问题讨论】:

  • 一般建议:不要使用scanf()。使用fgets() 读取一行,然后使用sscanf() 解析它。
  • 如果您必须使用scanf(),请怜悯并检查返回值,否则您将遇到未定义的行为很快。

标签: c scanf stdin c99


【解决方案1】:

但这句话让我很烦; scanf("%*c");它的唯一目的是处理 潜伏在流中的 '\n' 字符,可能来自上一个 输入。

scanf() 非常适合读取单个字符。我通常会选择getchar(),如果你不在乎它是什么,就忽略它。

但是,对于您的特殊情况,我会接受 @OlafDietsche 在 cmets 中提出的建议:在格式字符串的开头插入一个空格(或换行符或制表符)以用于下一个 scanf

scanf(" %[^\n]%*c",name);

这将匹配任何零个或多个空白字符的运行,因此会占用您的换行符,以及任何后续的空白行以及下一行具有任何非空白的任何前导空白。无论如何,这是大多数其他字段指令的标准行为,这就是为什么您的数字字段没有相同的问题,但有意%[%c 字段自动完成, 以便这些可以读取并返回空格。

这也意味着您不需要在名称行末尾显式使用换行符...

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

...因为它会在随后的scanf() 调用中被%i 指令自动使用。

【讨论】:

  • scanf(" %[^\n]",name);`gets() 一样好,因为它没有宽度限制 - 即使是入门级 CS 课程。
【解决方案2】:

考虑使用fget() 读取用户输入@Barmar,然后进行解析。

char buffer[100];
int id, age;
char name[40]={0};

printf("ID: ");
fgets(buffer, sizeof buffer, stdin);
sscanf(buffer, "%i", &id);

printf("Name: ");
fgets(buffer, sizeof buffer, stdin);
sscanf(buffer, "%[^\n]", name);

printf("Age: ");
fgets(buffer, sizeof buffer, stdin);
sscanf(buffer, "%i", &age);

随着技能的提高,添加错误检查。

printf("ID: ");
if (fgets(buffer, sizeof buffer, stdin) == NULL) Handle_end_of_file();
if (sscanf(buffer, "%i",&id) != 1) Handle_non_numeric_input();
// also research strtol()

printf("Name: ");
if (fgets(buffer, sizeof buffer, stdin) == NULL) Handle_end_of_file();
if (sscanf(buffer, " %99[^\n]",name) != 1) Handle_space_only_name();
// Later, additional code to detect excessively long name 

【讨论】:

  • 使用getchar() 循环在恕我直言,比跳过所有需要使fgets() 干净地工作所需的循环更干净,例如有人输入的输入超出预期。
  • @supercat Std C,IMO,缺乏强大的 read_a_line()。我想要一个int read_a_line( size_t sz, char *buf, size_t *non_LF_read_count),它总是读取整行(尽管不保存\n)并在文件结束/输入错误时返回EOF,在成功/失败时返回1/0 (缓冲区太小)。 *nix getline() 不幸的是允许用户压倒内存资源。
  • 我想我可能倾向于使用 flags 参数来控制行为的某些方面,例如如果输入太长,实现是否应在实际时提供即时反馈,是否应存储尾随零等。不同的用例受益于略有不同的极端情况行为,并且使用标志参数可能比使用多个不同的函数来适应它们或要求用户代码解决标准函数中缺乏干净处理的问题更干净。跨度>
  • @supercat 关于标志的好主意。嗯。
  • 标志有点麻烦,但有很多用例代码希望添加尾随零字节,但也有很多代码可能希望将输入接收到足够大的缓冲区中以容纳它[并且将被零填充,将其长度存储在其他地方,或者期望始终保存一定长度的数据]。如果读取行例程没有在最大长度输入情况下添加尾随零字节,则用户代码将需要添加一个,但坚持存储零字节将使例程对于精确拟合场景无用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-19
  • 1970-01-01
  • 2020-05-10
相关资源
最近更新 更多