int a [] = {1,2,}; 允许使用怪异的逗号。有什么特殊原因吗?


335

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

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

但事实并非如此。当在Visual Studio中编译的代码我很惊讶,但我已经学会了不信任MSVC编译器尽可能C ++的规则而言,所以我检查的标准,它标准允许为好。如果您不相信我,可以查看8.5.1的语法规则。

在此处输入图片说明

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

那么,是否再次有明确的理由允许这个多余的逗号呢?


10
每个人似乎同意“容易增加新的生产线” -但人们定义语言规范真正理会这样的事情?如果他们确实是这样的理解者,那么;当清楚知道下一个标记实际上是下一个语句时,他们为什么不忽略缺失。
YetAnotherUser

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

5
@Tomalak:这对人类读者来说是模棱两可的,通常是一个错误。这就是为什么它会发出警告。同样if (x = 1),在语法上也不是模棱两可,但对人类却非常模棱两可,因此发出警告。
罗布·纳皮尔

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

5
只要我们同意对编译器保护我们免受攻击是有用的,而数组声明中的尾部逗号对于对编译器保护我们免受攻击没有帮助。
罗布·纳皮尔

Answers:


436

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

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

...您必须将逗号添加到现有行添加新行。相比之下,与其中三个的情况下已经有后一个逗号,你只需要添加一行。同样,如果您要删除一行,则可以这样做,而不必担心它是否是最后一行,并且您可以对行进行重新排序而不必担心逗号。基本上,这意味着您对待线条的方式是一致的。

现在考虑生成代码。类似于(伪代码):

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

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


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

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

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

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

6
这是在K&R C.
Ferruccio

126

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

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

6
JavaScript支持以下语法:var a = [1, 2,];我知道的其他大多数语言也都支持... ActionScript,Python,PHP。
肖恩·藤原

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

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

5
@Sean对不起,你是正确的-这不是在IE浏览器解析错误,但它插入一个额外的元素设置undefined
Skilldrick

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

38

我认为开发人员易于使用。

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

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


32

我一直认为这样做可以使添加额外的元素更加容易:

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

变成:

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

以后再说。


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

3
@Giorgio:好吧,它是从C继承的。这很可能只是对原始语言规范的疏忽,恰好有一个有用的副作用。
奥利弗·查尔斯沃思

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

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

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

21

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

int ints[] = {
    3,
    9
};

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

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

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

然后您同时对其进行编辑,添加到开头:

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

从语义上讲,这些操作(添加到开头,添加到结尾)应该完全合并安全,并且您的版本控制软件(希望是git)应该能够自动合并。可悲的是,事实并非如此,因为您的版本在9之后没有逗号,而您的好友也没有。而如果原始版本的末尾有9,则它们将自动合并。

因此,我的经验法则是:如果列表跨越多行,则使用尾部逗号;如果列表位于单行中,则不要使用逗号。


15

我相信由于向后兼容的原因,允许使用逗号结尾。现有的代码很多,主要是自动生成的,这些代码后跟逗号。这样可以更轻松地编写没有结尾的循环。例如

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

程序员实际上没有任何优势。

PS尽管以这种方式自动生成代码更容易,但实际上我始终注意不要在末尾加逗号,这会减少工作量,提高了可读性,这更重要。您编写一次代码,多次阅读。


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

12
@Dereleased-按照相同的逻辑,为什么不应该允许尾随(任何东西),怎么回事呢?int a = b + c +;或者if(a && b &&);只是在末尾复制粘贴,更容易编写代码生成器。这个问题既琐碎又主观,在这种情况下,最好对代码阅读器做最好的事情。
Gene Bushuyev 2011年

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

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

2
关于&&运算符,有时我会做类似的条件,if (true \n && b1 \n && b2)以便可以根据需要添加和删除行。
Christian Mann


11

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

想像:

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("}")

10

一直以来,没有人引用Annotated C ++ Reference ManualARM),这让我感到惊讶,它对[dcl.init]的强调如下:

显然有太多用于初始化的符号,但是每种符号似乎都很好地服务于特定的使用风格。= {initializer_list,选择}符号被选自C继承和用于数据结构和数组的初始化提供良好服务。[...]

尽管自编写ARM以来语法已经发展,但起源仍然存在。

我们可以转到C99的原理上来了解为什么在C语言中允许这样做,并说:

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


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

10

我看到其他答案中没有提到的一种用例,即我们最喜欢的宏:

int a [] = {
#ifdef A
    1, //this can be last if B and C is undefined
#endif
#ifdef B
    2,
#endif
#ifdef C
    3,
#endif
};

添加宏以处理最后的内容,会非常麻烦。由于语法上的微小更改,因此管理起来很简单。这比机器生成的代码更重要,因为通常使用图灵完整语言比非常有限的前处理器要容易得多。


7

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

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


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

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

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

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

@Dereleased微软发明了自己的规范,并命令其他人至少应改变心态(感谢上帝)
Chris McGrath

7

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

假设使用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}必须是语法错误。
乔治

6

原因很简单:轻松添加/删除行。

想象以下代码:

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

现在,您可以轻松地在列表中添加/删除项目,而不必有时添加/删除尾部逗号。

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


6

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


5

这样可以防止因在长列表中四处移动元素而导致的错误。

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

#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

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

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


4

像许多事物一样,数组初始化程序中的尾部逗号是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? */
};

我们读 :

...最后的初始值设定项后面的逗号不是错字,而是从原住民C继承过来的语法中的一个短处。允许存在或不存在它,但没有意义。ANSI C基本原理声称的理由是,它使C的自动生成更加容易。如果在每个逗号分隔的列表(例如枚举声明中)或单个声明中的多个变量声明符中都允许使用尾部逗号,则该声明将更加可信。他们不是。

...对我来说更有意义


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

1
...如果C愿意在合理的情况下(但没有)被赋予重要意义(这是一个强烈的理由主张禁止在该处使用逗号),则忽略该逗号,这很好奇在逗号没有含义的情况下,[即使一个解释enum foo {moe,,larry,curly,};为在moe和之间跳过一个数字larry,也不会愿意,尾随的逗号是经过处理还是被忽略通常并不重要。唯一可能重要的情况是,最后一项是否是其声明类型的最大值,以及……
supercat

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

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

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

2

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


1

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

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

用法:

LIST_BEGIN
   LIST_ENTRY(1)
   LIST_ENTRY(2)
LIST_END

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

#define LIST_LAST_ENTRY(x) x

而且使用起来很尴尬。


0

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


-4

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


这是不符合该标准的VC6的错误吗?
汤姆森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.