2012-12-16 57 views
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); 
} 

你能告诉我标记行是干什么的吗?

+0

从'stdin'中读取最多80个字符,它不是新行并且存储到数组'name'中。 – nhahtdh

+1

最多可扫描80个字符,并在^之后的某个字符处停止,在本例中为换行符。更多信息可以在这里找到“否定scanset”:http://www.cplusplus.com/reference/cstdio/scanf/ – Orwell

+0

这是一个正则表达式清楚 – Evert

回答

2

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

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

它看起来很奇怪,因为它使用了一个稍微模糊不清的C语言教程:它能够将邻近常量字符串合并为一个。

(它实际上是一个非常方便的功能:它允许在多条线路的包皮过长的字符串,并允许宏做常量字符串有用的东西)

...所以,从根本想象这行看起来像:

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

然后按照scanf documentation了解什么[^\n]一样。

顺便说一句...使用NAME_MAX_S常数可以完全使用cpp stringification避免:

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

字符串连接是添加到C89中的主流现有技术的有用添加之一。它一直是'标准',而有一个符合(正式)标准。此外,严格来说,它不是C预处理器功能。预处理器不执行字符串连接(C2011标准§5.1.1.2中的翻译阶段的第4阶段);它在第6阶段分开处理。 –

+0

是的,对不起,它是,我们的问题在这里:有一个[* CPP字符串连接*](http://gcc.gnu.org/onlinedocs/cpp/Concatenation.html),它通过粘贴cpp令牌用'##'。然后是** C编译器折叠相邻的常量字符串**,这是我们正在处理的功能。此外,还有[CPP字符串化](http://gcc.gnu.org/onlinedocs/gcc-4.3.4/cpp/Stringification.html),在这里会派上用场。另外,相邻常量的串化和崩溃是并存的,这就是为什么我感到困惑。谢谢。 – ZJR

+0

有'##'运算符和(如你所说)用'#'运算符进行了字符串化的预处理'token concatenation'。它们也是加入C89的主流现有技术的有益补充。 –

3

预处理器取代后,它应该是这样的:

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

这是相当于:

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

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

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

7

这是一个字符串连接。当你写的字符串,你可以把它这样,他们将在编译时串连,所以:

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

最终将成为:

"%80[^\n]" 

scanf然后将读入指定的变量name 80字符不是newline

3

两个有点晦涩的语言功能正在同时使用:

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

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

    然后将相邻的字符串文字粘贴在一起,所以对于编译器的后面的部分,它只是仿佛它说... scanf("%80[^\n]", name) ...

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

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. 当您指定scanf()等的长度为%s%[]时,指定的字符数为,而不包括空字节(旧设计)的。这意味着您必须处理字符串变量的已定义大小与转换规范中指定的长度之间的偏差。
  4. 整体条件正确检查字符串是否成功读取。它将检测到任何转换失败(返回值0,因为输入中的第一个字符是换行符)以及EOF。唯一可能的问题是它不保留返回值来区分这两者,但可以使用feof()ferror()这样做 - 这是它们的目的(在发生故障后区分错误)。

代码使用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) 
+0

非常感谢你们所有人。我现在明白了 – user1907948