也许我不是来自这个星球,但在我看来,以下内容应该是语法错误:在Visual Studio上编译此代码时,我感到很惊讶,但是我了解到就C ++规则而言,我不信任MSVC编译器,因此我检查了该标准,并且该标准也允许该标准。如果您不相信我,可以看到8.5.1的语法规则。



为什么允许这样做?这可能是一个愚蠢的无用问题,但我想让您理解我为什么要问。如果这是一般语法规则的一个子情况,我会理解-他们决定不为了简化通用语法而仅仅在初始化器列表的末尾不允许多余的逗号。但是不可以,其他逗号是明确允许的。例如,在函数调用参数列表的末尾(当函数采用...时)不允许使用多余的逗号,这是正常现象。明确允许使用此多余逗号的特殊原因?

评论

每个人似乎都同意“轻松添加新行”-但是定义语言规范的人真的对这些事情感到困扰吗?如果他们真的是这样的理解,那么他们为什么不忽略缺失;很明显,下一个标记实际上就是下一个语句。

@YetAnotherUser:是的,语言设计师会考虑这种事情。允许您删除分号将产生更大的影响,并且在语言的许多部分都将非常含糊(请记住,空格不是C语言中的语义)。多余的逗号是这种情况不是模棱两可的。多余的分号几乎永远不会模棱两可,因此也是允许的。如果它是模棱两可的(例如,在for()之后),则添加它会引发编译器警告。

@Tomalak:这对人类读者来说是模棱两可的,通常是一个错误。这就是为什么它会发出警告。类似地,如果(x = 1)在语法上不是模棱两可的,但对于人类却是非常模棱两可的,因此会发出警告。

@Rob:您的if示例也不是模棱两可的。我认为“模棱两可”并不意味着您认为的含义!

只要我们同意对编译器保护我们免受攻击是有用的,而数组声明中的结尾逗号对编译器保护我们免受攻击是没有帮助的。

#1 楼

它使生成源代码和编写可在以后轻松扩展的代码变得更加容易。请考虑向以下内容添加额外条目的要求:

与之比较的是,这三个后面都有逗号,您只需要添加一行。同样,如果您要删除一行,则可以不必担心是否是最后一行,并且可以对行进行重新排序而不必担心逗号。基本上,这意味着线的处理方式是统一的。

现在考虑生成代码。诸如(伪代码)之类的东西:

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


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

评论


另外,使用VCS时,两个版本之间的“差异”更为清晰,因为添加或删除项目时仅一行会发生变化。

–凯文·潘科(Kevin Panko)
11年8月12日在21:05

如果要使代码生成更简单,那么为什么不采用某些函数语言的无括号样式呢?为什么不推断所有类型?并删除分号?等等。我认为真正的原因是语言设计师的主观和不幸标准。

–NéstorSánchezA.
11年8月13日在0:20

@Néstor:为什么“不幸”?这里有什么缺点?仅仅因为已经考虑了语言的一小部分的代码生成(和易于操作)的考虑,并不意味着它必须成为语言中所有决策的主要动机。类型推断,分号的删除等对语言有巨大的影响。 IMO,您在此处设置了错误的二分法。

–乔恩·斯基特(Jon Skeet)
2011年8月13日下午5:59

@Néstor:在这里,实用主义战胜了教条主义:为什么当这两者混合在一起更有用时,为什么必须完全是一回事或完全是另一回事?实际情况如何,能够在末尾添加逗号?这是否曾经在任何意义上阻碍过您的矛盾?如果没有,请权衡无关紧要的优雅与最后允许逗号的实际好处。

–乔恩·斯基特(Jon Skeet)
11年8月13日在22:47

@Mrchief:这不是打字速度的问题,而是复制,删除或重新排序项目时的简单性。就在昨天,这简化了我的生活。没有缺点,为什么不让生活更轻松?至于试图将矛头指向MS,我强烈怀疑这是在Microsoft甚至还没有出现之前就已经存在于C中了。您说这种说法似乎很奇怪,但是我敢打赌,它每天都会使数百家公司中的数千名开发人员受益。这不是寻找比编译器编写者受益的更好的解释吗?

–乔恩·斯基特(Jon Skeet)
2011年8月17日下午5:17



#2 楼

如果您执行以下操作,这将非常有用:

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


评论


JavaScript支持以下语法:var a = [1,2,] ;,我知道的大多数其他语言也是如此... ActionScript,Python,PHP。

–西恩·藤原
2011年8月14日,下午3:43

@Sean会在IE JavaScript中引起解析错误,所以要当心!

– Skilldrick
2011年8月15日上午10:30

IE9中不适合我。但是它确实做了一些奇怪的事情……它创建了一个null元素。我会提防的。

–西恩·藤原
11年8月16日在2:06

@Sean对不起,您是对的-这不是IE中的解析错误,但是它将插入一个额外的元素集,该元素集未定义。

– Skilldrick
11年8月16日在9:57

最令人沮丧的是,JSON不支持此语法。

– Timmmm
17年1月19日在10:01

#3 楼

我想让开发人员易于使用。

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


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

#4 楼

我一直认为这样做可以轻松添加额外的元素:

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


简单地变成: >稍后。

评论


我不认为稍微加快编辑速度不是弄乱语法的一个很好的理由。恕我直言,这只是另一个奇怪的C ++功能。

–乔治
11年8月12日在20:59

@Giorgio:好吧,它是从C继承的。这很可能只是对原始语言规范的疏忽,恰好有一个有用的副作用。

–奥利弗·查尔斯沃思(Oliver Charlesworth)
11年8月12日在21:01

好的,我不知道它来自C。我刚刚检查了Java也允许它。但是,这感觉有点奇怪:按照我的直觉,逗号是分隔符而不是终止符。此外,可以省略最后一个逗号。那么,它是终止符,分隔符还是两者?但是,可以使用此功能,并且很高兴知道。

–乔治
11年8月12日在21:16

@ Giorgio-源代码适用于人类,而不适用于机器。像这样的小事情可以防止我们产生简单的转换错误,这是福气,而不是疏忽。供参考,它也可以在PHP和ECMAScript(以及JavaScript和ActionScript)中以这种方式工作,尽管它在JavaScript对象符号(JSON)中无效(例如[1,2,3,]可以,但是{a:1,b :2,c:3,}不是)。

–已取消发行
2011年8月12日在21:51



@Groky:我对它的思考越深,我越相信一种编程语言的语法应该尽可能简单和一致,并且有尽可能少的例外:这使得学习该语言更加容易(需要记住的规则更少) )。与在列表中添加项目或从列表中删除项目时保存一次或两次击键的优势(相对于我花费的总编码时间,我不经常这样做)相比,具有明确定义的语法。

–乔治
11年8月18日在6:10

#5 楼

每个人都在谈论添加/删除/生成行的难易程度都是正确的,但是这种语法的真正亮点是将源文件合并在一起。假设您有以下数组:

int ints[] = {
    3,
    9
};


并假设您已将此代码检入到存储库中。

然后您的好友对其进行编辑,最后添加:

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


同时编辑它,并添加到开头:

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


在语义上,这类操作(添加到开头,添加到结尾)应该完全合并安全,并且您的版本控制软件(希望是git)应该能够自动合并。遗憾的是,情况并非如此,因为您的版本在9之后没有逗号,而您的好友也没有。而如果原始版本的尾部有9个字符,它们将自动合并。

因此,我的经验法则是:如果列表跨越多行,请使用尾部逗号,如果列表跨越多行,则不要使用逗号列表在一行上。

#6 楼

我相信由于向后兼容的原因,允许使用逗号结尾。现有的代码很多,主要是自动生成的,这些代码后跟逗号。
例如,


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


对于程序员来说确实没有任何好处。 />
PS尽管以这种方式自动生成代码更容易,但实际上我始终小心不要放在逗号后面,这样可以减少工作量,提高可读性,这更重要。您编写一次代码,多次阅读。

评论


我完全不同意; [我的看法是]它已经找到了在C语言之后很长时间创建的多种语言中使用的方法,正是因为它对程序员有利于能够在数组的内容周围移动,随意注释行等,这是有利的,不必担心傻傻的换位引起的语法错误。我们还不够压力吗?

–已取消发行
11年8月12日在21:54

@Dereleased-用同样的逻辑,为什么不应该尾随(任何东西),int a = b + c +怎么样?或if(a && b &&);最后,将任何内容复制粘贴都将变得更加容易,并且编写代码生成器也将变得更加容易。这个问题既琐碎又主观,在这种情况下,最好对代码阅读器做最好的事情。

–基因布祖耶夫(Gene Bushuyev)
11年8月12日在22:06

@Gene Bushuyev:是的!我经常使用+或&&来表示长表达式,并在该行的末尾加上运算符,当然,当我要删除表达式的最后一个操作数时,我必须花一些时间。我认为这种逗号语法真的很奇怪!

–乔治
11年8月18日在6:15



@GeneBushuyev-我不同意这些。虽然允许在数组等中使用尾随逗号是一个错误消除功能,并且使您的程序员生活更加轻松,但出于可读性的考虑,我将采取措施从条件条件中删除尾随AND(&&)语句,加号和其他各种运算符陈述。 IMO,这真是丑陋。

– Sune Rasmussen
2011年11月22日,9:25

关于&&运算符,有时我会做一些条件,例如if(true \ n && b1 \ n && b2),以便可以根据需要添加和删除行。

–克里斯蒂安·曼(Christian Mann)
16-6-4在14:12



#7 楼

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

#8 楼

这使吐出数组或枚举的代码生成器更容易。

想象:

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

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


#9 楼

一直以来,没有人引用过Annotated C ++ Reference Manual(ARM),我感到很惊讶,它说了以下有关[dcl.init]的内容,重点是我的:许多用于初始化的符号,但是每种符号似乎都能很好地满足特定的使用风格。 = {initializer_list,opt}表示法是从C继承的,非常适用于数据结构和数组的初始化。 [...]


尽管自从编写ARM以来语法已经发展,但起源仍然存在。在C中被允许,并且它表示:


K&R在
初始值设定项列表的末尾允许在初始值设定项中使用尾部逗号。该标准保留了这种语法,因为它
提供了在初始化器列表中添加或删除成员的灵活性,并且简化了此类列表的生成。


评论


支持文献中最支持的答案,以及该功能的真正来源。

– Marko
20年4月1日在14:02

#10 楼

我看到其他答案中未提及的一个用例,
我们最喜欢的宏:由于语法上的微小变化,这很容易管理。这比机器生成的代码更重要,因为通常在图灵完整语言中比在非常有限的预处理器中更容易做到。

#11 楼

原因很简单:轻松添加/删除行。想象以下代码:

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


现在,您可以轻松添加/删除

与其他答案相反,我并不认为易于生成列表是一个正当的理由:毕竟,它是对于代码来说,最后一行(或第一行)的特殊化是微不足道的。代码生成器只编写一次并使用多次。

#12 楼

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

#13 楼

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

*在从理论上讲是允许的,但Internet Explorer不遵循该标准,并将其视为错误

评论


JavaScript的“数组”(只是具有神奇长度属性的对象)还是很不寻常的:var x = []是合法的(除非IE <9,但是规范说是合法的)

– Peter C
11年8月12日在21:12

根据ECMAScript规范,它是完全有效的。从理论上讲,它应该可以在根据上述规范(尤其是此处找到的规范的一部分)实现JavaScript的任何浏览器中工作。

–已取消发行
11年8月12日在21:59

不幸的是,JavaScript只不过是为公众制作应用程序。因此,当大约50%的用户在使用您的应用程序时遇到问题时,这并不是完全有效。是的,如果我可以禁止IE <9的话,那么花很多时间在使良好的代码正常工作上……

– kgadek
2011年8月12日在22:44



@Dere:是的,我在回答中说了很多=)

–托马斯·博尼尼(Thomas Bonini)
11年8月12日在22:48

@Dereleased微软发明了自己的规范和命令,其他人至少遵守了这种思想改变(感谢上帝)

–克里斯·麦格拉思(Chris McGrath)
2011年8月14日下午2:14

#14 楼

对于机器来说更容易,例如解析和生成代码。
对人类来说也更容易,例如通过一致性进行修改,注释和视觉优雅。

假设C,您会写吗

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    puts("Line 1");
    puts("Line 2");
    puts("Line 3");

    return EXIT_SUCCESS
}


否。不仅因为最终声明是错误的,而且还因为不一致。那么为什么对收藏夹也是如此?即使使用允许您省略最后的分号和逗号的语言,社区通常也不喜欢它。例如,Perl社区似乎不喜欢省略分号,单线。他们也将其应用于逗号。

由于您不省略多行代码块的分号,同样的原因也不要在多行集合中省略逗号。我的意思是,即使语言允许,您也不会这样做,对吗?对吧?

评论


有允许的语言(例如Pascal)。即您必须选择之间;作为终止子(C)或作为分隔符(Pascal)。与“,”相同。如果','是一个终止符,但对我来说还可以,但是{1、2、3}必须是语法错误。

–乔治
11年8月18日在6:19

#15 楼

这样做可以防止因在长列表中四处移动元素而导致的错误。例如,假设我们有一个类似这样的代码。

>
这很好,因为它显示了Stack Exchange网站的原始三部曲。您会看到,该网站上的页脚在超级用户之前显示服务器故障。更好地解决这个问题,以防任何人察觉。

#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 Overflow
Super User
Server Fault


我知道,没有名为“ Server FaultSuper User”的网站,但是我们的编译器声称它存在。现在,问题在于C具有字符串连接功能,该功能允许您编写两个双引号字符串并不使用它们进行连接(整数也可能发生类似的问题,因为-符号具有多种含义)。

现在,如果原始数组结尾处没有逗号,该怎么办?好吧,这些线会四处移动,但是不会发生这样的错误。很容易错过像逗号这样的小东西。如果您记得在每个数组元素后都添加逗号,那么这种错误就不会发生。在发现逗号是造成问题的原因之前,您不想浪费四个小时来调试某些东西。

#16 楼

像许多事物一样,数组初始化程序中的尾部逗号是C ++从C继承的事物之一(并且必须永远支持)。 “ 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? */
};


我们读: br /> ...最终初始化程序之后的逗号不是错字,而是原住民C继承的语法错误。允许存在或不存在它,但没有意义。 ANSI C基本原理要求的理由是,它使C的自动生成更加容易。如果在每个逗号分隔的列表(例如枚举声明中)或单个声明中的多个变量声明符中都允许使用尾部逗号,则该声明将更加可信。他们不是。


...对我来说更有意义

评论


在枚举情况下禁止使用逗号有点有趣,因为在这种情况下,缺少逗号会带来最少的歧义。给定struct foo arr [] = {{1,2,3,4,5},{3,4,5,6,7},};语言可以赋予两种有意义的含义:创建一个包含两个元素的数组,或者创建一个包含最后一个值的默认值的包含三个元素的数组。如果C采用了后来的解释,我可以看到禁止枚举foo {moe,larry,curl,};本着这样一种原则,即只有一种方式可以编写该语句(不带逗号),但是...

–超级猫
15年8月1日在19:40

...如果C愿意在合理的情况下(但没有)被赋予重要意义(这将是一个强有力的论点,要求在该处禁止它),则愿意忽略逗号,这很好奇在逗号没有意义的情况下,[即使有人解释了枚举foo {moe , larry,curly,};由于在moe和larry之间跳过了一个数字,尾随的逗号是经过处理还是被忽略通常并不重要。唯一可能重要的情况是最后一项是否为其声明类型的最大值,并且...

–超级猫
2015年8月1日19:44

...可以通过简单地说应该忽略在最后分配的枚举值之后发生的溢出来处理。

–超级猫
2015年8月1日19:44

@supercat在C#之类的语言中,先验设计研究甚至在开发语言时会考虑IDE功能和集成。 C不是(也不可能是)这些语言之一。

– Nikos Athanasiou
15年8月5日,12:43

即使使用C#之类的语言,不断变化的设计目标也导致了一些非常严重的设计不一致。例如,该语言不支持正常方法和运算符的任何形式的返回类型重载(即使基础框架可以支持它),因为它被认为与拥有一种易于编译的语言相反,但是lambda评估包括分辨率为NP完全的类型推断规则。添加新的方法/运算符重载规则可能会破坏现有代码(尽管我认为好的规则可以最大程度地减少此类危险)...

–超级猫
2015年8月5日14:57



#17 楼

除了简化代码生成和编辑之外,如果您想实现解析器,则这种语法也更容易实现。 C#在多个地方都遵循此规则,这里有一个逗号分隔的项目列表,例如enum定义中的项目。

#18 楼

它使生成代码更加容易,因为您只需要添加一行,而不必将添加最后一项视为特殊情况。使用宏生成代码时尤其如此。有一种尝试试图从语言中消除对宏的需求,但是许多语言确实与宏一起发展。多余的逗号允许定义和使用以下宏:

#define LIST_BEGIN int a[] = {
#define LIST_ENTRY(x) x,
#define LIST_END };


用法:

LIST_BEGIN
   LIST_ENTRY(1)
   LIST_ENTRY(2)
LIST_END


这是一个非常简化的示例,但是宏通常使用此模式来定义诸如分发,消息,事件或翻译映射和表之类的内容。如果最后不允许使用逗号,则需要特殊的命令:

#19 楼

这样,当两个人在单独分支的列表中添加新项目时,Git可以正确合并更改,因为Git是基于行工作的。

#20 楼

如果使用没有指定长度的数组,VC ++ 6.0会自动识别其长度,因此如果使用“ int a [] = {1,2,};”,a的长度为3,但最后一个没有。尚未初始化,可以使用“ cout <


评论


这是不符合标准的VC6的错误吗?

–汤姆森
2014-09-18 8:29