353

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

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

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

在此处输入图像描述

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

那么,再次明确允许这种多余的逗号有什么特别的原因吗?

4

20 回答 20

448

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

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

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

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

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

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

于 2011-08-12T16:39:46.553 回答
130

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

int a[] = {
  1,
  2,
  3, //You can delete this line and it's still valid
};
于 2011-08-12T16:39:52.560 回答
37

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

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

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

于 2011-08-12T16:40:57.763 回答
31

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

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

简单地变成:

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

在以后的日期。

于 2011-08-12T16:39:34.207 回答
21

每个人所说的关于添加/删除/生成行的易用性都是正确的,但这种语法真正闪耀的地方是在将源文件合并在一起时。想象一下你有这个数组:

int ints[] = {
    3,
    9
};

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

然后你的伙伴编辑它,添加到最后:

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

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

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

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

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

于 2011-08-16T17:41:54.720 回答
16

我很惊讶,因为这段时间没有人引用Annotated C++ Reference Manual ( ARM ),它对[dcl.init]说了以下内容,重点是我的:

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

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

我们可以查看C99 的基本原理,看看为什么在 C 中允许这样做,它说:

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

于 2015-10-19T11:59:16.273 回答
15

出于向后兼容性的原因,我认为允许使用尾随逗号。有很多现有代码,主要是自动生成的,其中包含一个尾随逗号。它使得在末尾没有特殊条件的情况下编写循环更容易。例如

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

程序员真的没有任何优势。

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

于 2011-08-12T16:47:58.690 回答
13

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

于 2011-08-12T16:41:40.327 回答
13

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

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

最后添加宏来处理,会很痛苦。有了语法上的这个小改动,管理起来就很简单了。这比机器生成的代码更重要,因为在图灵完整语言中通常比非常有限的预处理器更容易做到这一点。

于 2018-04-20T20:55:41.667 回答
12

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

想象:

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("}")
于 2011-08-12T16:46:58.160 回答
6

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

想象一下下面的代码:

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

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

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

于 2011-08-12T16:40:47.593 回答
6

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

于 2011-08-12T16:40:52.863 回答
6

唯一不允许使用的语言 - 实际上* - 是 Javascript,它会导致无数问题。例如,如果您从数组中间复制并粘贴一行,将其粘贴到末尾,然后忘记删除逗号,那么您的 IE 访问者将完全破坏您的网站。

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

于 2011-08-12T18:37:15.930 回答
6

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

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

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

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

    return EXIT_SUCCESS
}

不,不仅因为最后的陈述是错误的,还因为它不一致。那么为什么要对集合做同样的事情呢?即使在允许您省略最后的分号和逗号的语言中,社区通常也不喜欢它。例如,Perl 社区似乎不喜欢省略分号、单行横线。他们也将其应用于逗号。

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

于 2011-08-12T23:17:11.040 回答
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 具有字符串连接功能,它允许您编写两个双引号字符串并使用任何内容连接它们(整数也可能发生类似问题,因为-符号具有多种含义)。

现在,如果原始数组末尾有一个无用的逗号怎么办?好吧,线条会移动,但不会发生这样的错误。很容易错过像逗号这样小的东西。如果你记得在每个数组元素后加一个逗号,这样的错误就不会发生。你不会想浪费四个小时调试一些东西,直到你发现逗号是你的问题的原因

于 2014-05-10T16:56:38.737 回答
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 的自动生成更容易。如果在每个逗号分隔的列表中都允许使用尾随逗号,例如在枚举声明中,或者在单个声明中使用多个变量声明符,则该声明将更加可信。他们不是。

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

于 2015-07-30T14:16:59.303 回答
2

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

于 2011-08-16T21:03:01.990 回答
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

那使用起来会很尴尬。

于 2018-04-23T12:15:49.117 回答
0

因此,当两个人在不同分支的列表中添加新项目时,Git 可以正确合并更改,因为 Git 以行为基础工作。

于 2019-12-25T17:48:28.763 回答
-4

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

于 2011-08-18T01:10:29.027 回答