Int a[] = {1,2,};为什么在初始化列表中允许使用尾随逗号?

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

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

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

enter image description here

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

那么,还有什么特别的原因,这个多余的逗号是允许< >强明确< / >强 ?

31899 次浏览

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

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

只是变成了:

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

在晚些时候。

它使生成源代码变得更容易,也使编写日后可以轻松扩展的代码变得更容易。考虑一下添加额外条目需要什么:

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

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

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

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

不需要担心您正在输出的当前项是第一个还是最后一个。更加简单。

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

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

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

想象下面的代码:

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

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

与其他答案相反,我真的不认为生成列表的便性是一个有效的理由:毕竟,代码对最后(或第一行)行进行特殊处理是微不足道的。代码生成器只编写一次,并使用多次。

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

我认为是为了便于开发人员使用。

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

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

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

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

想象一下:

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

后面的逗号,我认为是允许向后兼容的原因。有很多现有的代码,主要是自动生成的,它们在后面放了一个逗号。它使得在结尾没有特殊条件的情况下更容易编写循环。 例如< / p >

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

这对程序员来说并没有什么好处。

附注:虽然这样更容易自动生成代码,但实际上我总是注意不要在后面加上逗号,这样做的工作量最小,可读性得到了提高,这是更重要的。你写了一次代码,你读了很多次。

唯一在实践中不被允许的语言是Javascript,它会导致无数的问题。例如,如果你复制&从数组中间粘贴一行,粘贴到末尾,然后忘记删除逗号,那么你的网站将对IE访问者完全崩溃。

*理论上这是允许的,但ie浏览器不遵循标准,并将其视为错误

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

假设C,你会这样写吗?

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


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


return EXIT_SUCCESS
}

不。不仅因为最后的语句是一个错误,而且因为它是不一致的。那么为什么对收藏也要这样呢?即使在允许省略最后分号和逗号的语言中,社区通常也不喜欢这样做。例如,Perl社区似乎不喜欢省略分号,除了一行程序。他们也把这个应用到逗号上。

不要在多行集合中省略逗号,就像不要在多行代码块中省略分号一样。我是说,即使语言允许,你也不会这么做,对吧?对吧?

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

int ints[] = {
3,
9
};

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

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

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

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

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

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

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

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

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

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

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

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

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

像许多东西一样,数组初始化器中的尾随逗号是c++从C继承的东西之一(并且必须永远支持)。和这里的景色完全不同“深层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的自动生成更容易。如果允许在每个逗号分隔的列表中使用后面的逗号,则该声明将更加可信,例如在枚举声明中,或在单个声明中有多个变量声明器。事实并非如此。

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

我很惊讶,这么长时间以来,没有人引用注释c++参考手册(手臂),它强调了我的(dcl.init):

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

虽然语法自手臂编写以来已经发生了变化,但起源仍然存在。

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

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

. List,简化机器生成这样的列表

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

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

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

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

#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

用起来会很尴尬。

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