int a = {1,2,}; 允许使用奇怪的逗号。 任何特定的原因?

也许我不是来自这个星球,但在我看来,以下应该是一个语法错误:

int a[] = {1,2,}; //extra comma in the end 

但事实并非如此。 当在Visual Studio上编译这个代码时,我感到很惊讶,但是我已经学会了不要信任MSVC编译器,所以我检查了这个标准,这也是标准所允许的。 如果你不相信我,你可以看到8.5.1的语法规则。

在这里输入图像描述

为什么这是允许的? 这可能是一个愚蠢的无用的问题,但我想让你明白我为什么问。 如果这是一个通用语法规则的子情况,我会理解 – 他们决定不要使一般语法更难,只是在初始化列表的末尾不允许多余的逗号。 但不,额外的逗号是明确允许的。 例如,不允许在函数调用参数列表的末尾有一个冗余的逗号(当函数采用... ), 这是正常的

那么,这个多余的逗号是明确允许的吗?

它使得生成源代码变得更容易,并且编写可以在以后轻松扩展的代码。 考虑需要添加一个额外的条目到:

 int a[] = { 1, 2, 3 }; 

…您必须将逗号添加到现有的行添加一个新的行。 比较三个已经有逗号的情况,你只需要添加一行。 同样,如果你想删除一行,你可以这样做,而不用担心它是否是最后一行,而且你可以重新排列行而不用用逗号来摆弄。 基本上这意味着你如何看待这些线条是一致的。

现在考虑生成代码。 像(伪代码):

 output("int a[] = {"); for (int i = 0; i < items.length; i++) { output("%s, ", items[i]); } output("};"); 

无需担心您正在写出的当前项目是第一个还是最后一个。 简单得多。

如果你做这样的事情是有用的:

 int a[] = { 1, 2, 3, //You can delete this line and it's still valid }; 

易于使用的开发人员,我会想。

 int a[] = { 1, 2, 2, 2, 2, 2, /*line I could comment out easily without having to remove the previous comma*/ } 

另外,如果由于某种原因你有一个为你生成代码的工具, 该工具不必关心它是否是初始化中的最后一项。

我一直认为它可以更容易地追加额外的元素:

 int a[] = { 5, 6, }; 

简单地变成:

 int a[] = { 5, 6, 7, }; 

晚一些。

尾随逗号我相信是允许向后兼容的原因。 有很多现有的代码,主要是自动生成的,它将尾随逗号。 这使得编写一个没有特殊情况的循环变得更容易。 例如

 for_each(my_inits.begin(), my_inits.end(), [](const std::string& value) { std::cout << value << ",\n"; }); 

程序员没有任何优势。

PS虽然以这种方式自动生成代码比较容易,但实际上我总是注意不要放在尾部的逗号,努力最小化,可读性得到提高,这更重要。 你写一次代码,你多读了几次。

每个人都在谈论添加/删除/生成行的方便性是正确的,但是这个语法的真正亮点在于将源文件合并在一起。 想象一下你有这个数组:

 int ints[] = { 3, 9 }; 

并假设你已经将这个代码检入到一个存储库中。

然后你的朋友编辑它,最后join:

 int ints[] = { 3, 9, 12 }; 

而你同时编辑它,增加到开始:

 int ints[] = { 1, 3, 9 }; 

在语义上,这些操作(添加到开始,添加到最后)应该完全合并安全,您的版本控制软件(希望git)应该能够automerge。 可悲的是,事实并非如此,因为你的版本在9和你的好友之后没有逗号。 而如果原始版本的尾部9,他们会automerged。

所以,我的经验法则是:如果列表跨越多行,请使用尾随逗号,如果列表在单行上,则不要使用它。

我所知道的原因之一就是自动生成代码应该很简单, 最后一个元素不需要任何特殊处理。

它使得代码生成器更容易地吐出数组或枚举。

想像:

 std::cout << "enum Items {\n"; for(Items::iterator i(items.begin()), j(items.end); i != j; ++i) std::cout << *i << ",\n"; std::cout << "};\n"; 

也就是说,不需要对第一个或最后一个项目进行特殊处理,以避免跟随后面的逗号。

例如,如果代码生成器是用Python编写的,则很容易通过使用str.join()函数来避免拖尾尾随逗号:

 print("enum Items {") print(",\n".join(items)) print("}") 

在实践中,唯一不允许使用的语言是Javascript,它会带来无数的问题。 例如,如果您从数组中间复制并粘贴一行,将其粘贴到最后,忘记删除逗号,那么您的网站将完全被您的IE访问者破坏。

*理论上允许,但Internet Explorer不遵循标准,将其视为错误

原因很简单:易于添加/删除线条。

想象下面的代码:

 int a[] = { 1, 2, //3, // - not needed any more }; 

现在,您可以轻松地将项目添加/删除到列表中,而无需添加/删除尾随逗号。

与其他答案相比,我并不认为生成列表的方便性是一个有效的原因:毕竟,代码对最后一行(或第一行)进行特殊处理是微不足道的。 代码生成器被写入一次,并被多次使用。

对于机器来说更容易,即parsing和生成代码。 通过一致性,对人来说也更容易,即修改,注释和视觉优雅。

假设C,你会写下面的内容吗?

 #include <stdio.h> #include <stdlib.h> int main(void) { puts("Line 1"); puts("Line 2"); puts("Line 3"); return EXIT_SUCCESS } 

不仅仅是因为最后的陈述是一个错误,而且还因为它是不一致的。 那么为什么要collections呢? 即使在允许您省略最后一个分号和逗号的语言中,社群通常也不喜欢它。 例如,Perl社区似乎并不喜欢省略分号,单线条。 它们也适用于逗号。

不要忽略多行集合中的逗号,这是因为您没有为多行代码块省略分号。 我的意思是,即使语言允许,你也不会这样做,对吗? 对?

它允许每一行遵循相同的forms。 首先,这样可以更容易地添加新行,并使版本控制系统有效地跟踪更改,还可以更轻松地分析代码。 我想不出一个技术原因。

这可以防止移动元素在长长的列表中造成的错误。

例如,假设我们有一个这样的代码。

 #include <iostream> #include <string> #include <cstddef> #define ARRAY_SIZE(array) (sizeof(array) / sizeof *(array)) int main() { std::string messages[] = { "Stack Overflow", "Super User", "Server Fault" }; size_t i; for (i = 0; i < ARRAY_SIZE(messages); i++) { std::cout << messages[i] << std::endl; } } 

这很棒,因为它展示了Stack Exchange网站的原始三部曲。

 Stack Overflow Super User Server Fault 

但是有一个问题呢。 你看,这个网站上的页脚显示超级用户之前的服务器故障。 在任何人注意之前最好解决。

 #include <iostream> #include <string> #include <cstddef> #define ARRAY_SIZE(array) (sizeof(array) / sizeof *(array)) int main() { std::string messages[] = { "Stack Overflow", "Server Fault" "Super User", }; size_t i; for (i = 0; i < ARRAY_SIZE(messages); i++) { std::cout << messages[i] << std::endl; } } 

毕竟,移动线路不可能那么辛苦,是吗?

 Stack Overflow Server FaultSuper User 

我知道,没有网站称为“服务器故障超级用户”,但我们的编译器声称它存在。 现在,问题是C有一个string连接function,它允许你写两个双引号的string,并将它们连接在一起(类似的问题也可以发生在整数,因为-符号有多重含义)。

现在如果最初的数组有一个无用的逗号呢? 那么,线路会被移动,但这样的错误不会发生。 错过逗号之类的东西很容易。 如果你记得在每个数组元素之后加一个逗号,这样的错误就不会发生。 你不想浪费四个小时的时间去debugging,直到你发现逗号是你问题的原因 。

像许多事情一样,数组初始值设定项中的尾随逗号是C ++从Cinheritance的东西之一(并且将永远支持)。 “Deep C的秘密”一书中提到了与这里放置的完全不同的观点

其中有一个以上的“逗号悖论”的例子:

 char *available_resources[] = { "color monitor" , "big disk" , "Cray" /* whoa! no comma! */ "on-line drawing routines", "mouse" , "keyboard" , "power cables" , /* and what's this extra comma? */ }; 

我们读 :

…在最终初始化之后的尾随逗号不是一个错字,而是原住民C语法中的一个小错误。 它的存在或缺席是允许的,但没有意义 。 在ANSI C原理中声明的理由是它使C的自动化生成更容易。 如果在每个逗号分隔列表中允许尾随逗号 ,比如在枚举声明中,或者在单个声明中使用多个variables声明符,则这个声明将更加可信 。 他们不是。

对我来说这更有意义

在这段时间之后,我感到非常惊讶,没有人引用注释过的C ++参考手册 ( ARM ),它强调了下面关于[dcl.init]的内容

初始化显然有太多的符号,但是每个似乎都很好地适用于特定的使用风格。 = {initializer_list,opt}表示法是从Cinheritance而来的 ,并且适用于数据结构和数组的初始化。 […]

尽pipe从ARM写入起语法已经发展,但起源依然存在。

我们可以去C99的理由 ,看看为什么这是允许在C,它说:

K&R允许初始化程序列表末尾的逗号。 标准保留了这个语法,因为它提供了从初始化列表添加或删除成员的灵活性,并且简化了机器生成这样的列表。

除了代码生成和编辑的简单性,如果你想实现一个parsing器,这种types的语法更简单,更容易实现。 C#遵循这个规则在几个地方有一个逗号分隔的项目列表,如enum定义中的项目。

如果你使用一个没有指定长度的数组,VC ++ 6.0可以自动识别它的长度,所以如果你使用“int a [] = {1,2,};”a的长度是3,而最后一个是'你已经初始化了,你可以使用“cout”