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”