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

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

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

Why is this allowed? This may be a stupid useless question but I want you to understand why I am asking. If it were a sub-case of a general grammar rule, I would understand - they decided not to make the general grammar any more difficult just to disallow a redundant comma at the end of an initializer list. But no, the additional comma is explicitly allowed. For example, it isn't allowed to have a redundant comma in the end of a function-call argument list (when the function takes ...), which is normal.

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


当前回答

我很惊讶这么长时间以来没有人引用注解c++参考手册(ARM),它是这样描述[dcl. C]的。我的:

用于初始化的表示法显然太多了,但每种表示法似乎都适用于特定的使用风格。={initializer_list,opt}符号继承自C语言,可以很好地初始化数据结构和数组。[…]

尽管自ARM诞生以来语法一直在发展,但起源仍然存在。

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

类的初始化式中允许有一个尾随逗号 初始化器列表。标准保留了这个语法,因为它 提供从初始化式中添加或删除成员的灵活性 列表,并简化了此类列表的机器生成。

其他回答

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

例如,让我们假设我们有一个这样的代码。

#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有一个字符串连接特性,它允许您编写两个双引号字符串并不使用任何东西将它们连接起来(类似的问题也会发生在整数上,因为- sign有多个含义)。

现在,如果原始数组的末尾有一个无用的逗号呢?线会移动,但这样的bug不会发生。像逗号这么小的东西很容易被漏掉。如果你记得在每个数组元素后面加一个逗号,这样的错误就不会发生。您不希望浪费4个小时调试某个东西,直到您发现逗号是问题的原因。

每个人都说添加/删除/生成行很容易,但这种语法真正的亮点是合并源文件。假设你有这样一个数组:

int ints[] = {
    3,
    9
};

假设您已经将这段代码签入存储库。

然后你的朋友编辑它,在结尾添加:

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

你同时编辑它,在开头加上:

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

从语义上讲,这些类型的操作(添加到开头,添加到结尾)应该是完全合并安全的,你的版本控制软件(最好是git)应该能够自动合并。遗憾的是,情况并非如此,因为你的版本在9后面没有逗号,而你朋友的版本有。然而,如果最初的版本后面有9,他们就会自动生成。

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

如果你这样做,它会很有用:

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

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

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

添加宏到最后处理,将是巨大的痛苦。通过语法上的这个小变化,管理起来很简单。这比机器生成的代码更重要因为用图灵完备语言比用有限的预处理器要容易得多。

除了代码生成和编辑方便之外,如果您想实现解析器,这种类型的语法更简单、更容易实现。c#在一些地方遵循这个规则,比如在枚举定义中有一个以逗号分隔的项目列表。