int a [] = {1,2,};奇怪的逗号允许。任何特殊原因?

时间:2011-08-12 16:36:31

标签: c++ syntax grammar language-lawyer

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

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

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

enter image description here

为什么允许这样做?这可能是一个愚蠢无用的问题,但我希望你理解我为什么这么问。如果它是一般语法规则的子案例,我会理解 - 他们决定不再使一般语法更难以在初始化列表的末尾禁止冗余逗号。但不,额外的逗号是 明确 允许的。例如,在函数调用参数列表的末尾(当函数需要...),这是正常的时,不允许使用冗余逗号。

那么,再说一遍,这个冗余逗号是否有任何特殊原因 明确地 允许?

20 个答案:

答案 0 :(得分:425)

它可以更容易地生成源代码,也可以编写可以在以后轻松扩展的代码。考虑添加额外条目所需的内容:

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

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

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

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

无需担心您写出的当前项目是第一项还是最后一项。更简单。

答案 1 :(得分:125)

如果您执行以下操作,这很有用:

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

答案 2 :(得分:38)

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

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

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

答案 3 :(得分:32)

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

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

简单地变成:

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

以后。

答案 4 :(得分:21)

每个人都在谈论添加/删除/生成行的简易性是正确的,但是这种语法的真正意义在于将源文件合并在一起。想象一下,你有这个阵列:

int ints[] = {
    3,
    9
};

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

然后你的好友编辑它,添加到最后:

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

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

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

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

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

答案 5 :(得分:15)

尾随逗号我认为是出于向后兼容的原因。有很多现有的代码,主要是自动生成的,它带有一个尾随的逗号。它使得在没有特殊条件的情况下编写循环变得更容易。 e.g。

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

程序员没有任何优势。

P.S。虽然以这种方式自动生成代码更容易,但实际上我总是注意不要使用尾随逗号,努力是最小的,可读性得到改善,而且更重要。你编写一次代码,你多次阅读。

答案 6 :(得分:12)

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

答案 7 :(得分: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("}")

答案 8 :(得分:8)

在这段时间没有人引用Annotated C++ Reference Manual ARM )后,我感到很惊讶,它强调以下关于 [dcl.init] 的内容矿:

  

显然有太多的初始化符号,但每个似乎都很好地服务于特定的使用方式。 <{em> = {initializer_list,opt} 表示法继承自C ,适用于数据结构和数组的初始化。 [...]

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

我们可以转到C99 rationale,了解为什么在C中允许这样做,它说:

  

K&amp; R允许在初始化程序结尾处使用尾随逗号   初始化列表。标准保留了这种语法,因为它   可以灵活地添加或删除初始化程序中的成员   列表,并简化此类列表的机器生成。

答案 9 :(得分:7)

机器更容易,即解析和生成代码。 它对人类来说也更容易,即通过一致性来修改,评论和视觉优雅。

假设C,您会写下以下内容吗?

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

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

    return EXIT_SUCCESS
}

没有。不仅因为最终语句是错误,而且因为它不一致。那么为什么收藏品一样呢?即使在允许您省略最后一个分号和逗号的语言中,社区通常也不喜欢它。例如,Perl社区似乎不喜欢省略分号,禁止使用单行。他们也将它应用于逗号。

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

答案 10 :(得分:7)

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

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

添加宏来处理上一个,会很痛苦。通过语法上的这种微小变化,这对于管理来说是微不足道的。这比机器生成的代码更重要,因为在图灵完整的语言中通常比非常有限的预处理器更容易。

答案 11 :(得分:7)

唯一的语言 - 在实践中* - 不允许使用Javascript,它会导致无数的问题。例如,如果你复制&amp;从数组中间粘贴一行,将其粘贴到最后,忘记删除逗号,然后您的网站将完全破坏您的IE访问者。

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

答案 12 :(得分:6)

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

答案 13 :(得分:6)

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

想象一下以下代码:

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

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

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

答案 14 :(得分: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具有字符串连接功能,它允许您编写两个双引号字符串并使用任何内容连接它们(类似的问题也可能发生在整数上,因为-符号有多种含义)。

现在如果原始数组在结尾处有一个无用的逗号怎么办?好吧,线条会被移动,但这样的错误不会发生。很容易错过像逗号一样小的东西。如果你记得在每个数组元素后面加一个逗号,那么这样的bug就不会发生。你wouldn't want to waste four hours debugging something, until you would find the comma is the cause of your problems

答案 15 :(得分:4)

与许多事情一样,数组初始值设定项中的尾随逗号是C ++从C继承的东西之一(并且必须永远支持)。 “Deep C secrets”一书中提到了与此处放置的视图完全不同的视图

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

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的自动生成更容易。 如果在每个逗号分隔列表中允许使用尾随逗号,例如在枚举声明中,或在单个声明中使用多个变量声明符,则声明将更可信。他们不是。

......对我来说这更有意义

答案 16 :(得分:2)

除了代码生成和编辑简易性之外,如果要实现解析器,这种类型的语法更简单,更容易实现。 C#在几个地方遵循此规则,即有一个以逗号分隔的项目列表,例如enum定义中的项目。

答案 17 :(得分: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

使用它会非常尴尬。

答案 18 :(得分:0)

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

答案 19 :(得分:-4)

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