3

以下代码不是我制作的。我正在搜索并在其他人的问题中找到它。

#include <stdio.h>
#define NAME_MAX    80
#define NAME_MAX_S "80"

int main(void)
{
    static char name[NAME_MAX + 1]; // + 1 because of null
    if(scanf("%" NAME_MAX_S "[^\n]", name) != 1) // This line
    {
        fputs("io error or premature end of line\n", stderr);
        return 1;
    }

    printf("Hello %s. Nice to meet you.\n", name);
}

你能告诉我标记的线是做什么的吗?

4

5 回答 5

7

这是一个字符串连接。当您编写字符串时,您可以使其在编译时连接起来,因此:

 "%" NAME_MAX_S "[^\n]",

最终会变成:

 "%80[^\n]"

scanf然后将读入名为name80 个字符的变量,而不是newline.

于 2012-12-16T18:05:48.833 回答
3

同时使用了两个有点晦涩的语言特性:

  1. 字符串粘贴。预处理后,该行是

    if(scanf("%" "80" "[^\n]", name) != 1)
    

    然后将相邻的字符串文字粘贴在一起,因此对于编译器的后面部分,就好像它说... scanf("%80[^\n]", name) ...

  2. 一种不太常见的scanf转换。 "%[...]"与 并不完全不同"%s",因此这与转换非常相似"%80s"。我相信您可以在手册页或其他参考资料中查找[scanf 转换说明符。scanf(3)

于 2012-12-16T18:08:42.463 回答
3

预处理器替换后,它看起来像这样:

if(scanf("%" "80"_MAX_S "[^\n]", name) != 1) // <-- This line

这相当于:

if(scanf("%80[^\n]", name) != 1) // <-- This line

最多读取 80 个字符或换行符。

大小name为 81。所以它可以容纳 80 个字符 + nul 终止符。这通常是为了避免在读取输入时缓冲区溢出。

于 2012-12-16T18:05:45.730 回答
2
if (scanf("%" NAME_MAX_S "[^\n]", name) != 1) // This line

这是一种写法:

if (scanf("%80[^\n]", name) != 1)

有多种功能可能会让您感到困惑。

  1. "%" NAME_MAX_S "[^\n]"表示法使用字符串连接从片段创建单个字符串。
  2. 转换规范使用否定的%80[^\n]“扫描集”来指定读取的字符串最多可以是 80 个非换行符。
  3. 当您在or中指定scanf()等人的长度时,您指定的字符数不包括空字节(旧设计)。这意味着您必须处理字符串变量的定义大小与转换规范中指定的长度之间的差值。%s%[]
  4. 整体条件正确检查字符串是否已成功读取。它将检测到任何转换失败(返回值 0,因为输入中的第一个字符是换行符)以及 EOF。唯一可能的问题是它不保留返回值来区分两者,但您可以使用feof()andferror()这样做——这就是它们的用途(在发生故障后区分错误)。

该代码用于NAME_MAX_S确保它有一个字符串。它可以使用:

#define STR_EVALUATE(x) #x
#define STRINGIFY(x) STR_EVALUATE(x)

if (scanf("%" STRINGIFY(NAME_MAX) "[^\n]", name) != 1)

这会将要维护的行数减少到 1。但它只适用于简单的数字;如果你有一个表达式#define NAME_MAX (2*LEN_NAME_COMPONENT+LEN_MIDDLE_INITIALS),字符串形成的整个过程将无法工作。然后你需要做:

char format[16];
sprintf(format, "%%%d[^\n]", NAME_MAX);

if (scanf(format, name) != 1)
于 2012-12-16T18:10:25.123 回答
2

stdin它从name变量中读取不超过 80 个(非换行符)字符。

如果遇到换行符,它将停止扫描。

它看起来很奇怪,因为它使用了 C 语言的一个有点晦涩且没有很好教的特性:它能够将相邻的常量字符串折叠成一个单独的字符串。

(它实际上是一个非常方便的功能:它允许将长字符串包装在多行上,并允许宏用常量字符串做有用的事情)

...所以,从根本上想象这条线看起来像:

if(scanf("%80[^\n]", name) != 1)

然后按照scanf 文档了解[^\n]它的作用。

顺便说一句……使用cpp stringificationNAME_MAX_S可以完全避免使用 常量:

if(scanf("%" #NAME_MAX "[^\n]", name) != 1)
于 2012-12-16T18:05:44.997 回答