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

Arm*_*yan 325 c++ syntax grammar language-lawyer

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

int a[] = {1,2,}; //extra comma in the end
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

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

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

Jon*_*eet 428

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

int a[] = {
   1,
   2,
   3
};
Run Code Online (Sandbox Code Playgroud)

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

现在考虑生成代码.像(伪代码)的东西:

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

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

  • 此外,使用VCS时,两个版本之间的"差异"更清晰,因为添加或删除项目时只有一行更改. (85认同)
  • @Néstor:为什么"不幸"?这有什么缺点?仅仅因为对代码生成(以及易于操作)进行了一些考虑**语言的一小部分*并不意味着它必须是语言中所有决策背后的主要动机.类型推断,删除分号等对语言有很大的影响.你在这里设置了一个错误的二分法,IMO. (45认同)
  • @Néstor:这就是实用主义胜过教条主义的地方:为什么它必须*完全*一个或*完全*另一个,当它更有用*是两者的混合?它是如何实际阻碍的,能够在最后添加逗号?这是一种在任何意义上都阻碍了你的不一致吗?如果没有,请权衡这种无关紧要的优雅与最终允许逗号的实际*好处*. (16认同)
  • @Mrchief:这不是打字率的问题 - 在复制,删除或重新排序项目时,这是一个简单的问题.它让我的生活在昨天变得更加简单.没有缺点,为什么*不*让生活更轻松?至于尝试将手指指向MS,我强烈怀疑在微软存在之前就存在这种情况......你说这种理由似乎很奇怪,但我敢打赌它每天都会让数百家公司的数千名开发人员受益.这不是一个比寻找有利于编译器编写者的更好的解释吗? (7认同)
  • 这是在K&R C. (6认同)
  • 如果理由是使代码生成更简单,那么为什么不采用某些函数语言的无括号样式呢?为什么不推断所有类型?并删除分号?等等.我认为真正的原因是语言设计者的一个非常主观和不幸的标准. (4认同)
  • 不幸的是因为它与其他规则不一致,例如方法参数之间的逗号,为什么代码生成人员没有得到"帮助"呢?我对类型推断,分号的删除等的提及是一个夸张的例子,对代码生成有很大的帮助.换句话说,恕我直言,或语言完全一致或完全面向代码生成. (4认同)
  • 除了c ++之外,它还支持许多其他语言,我知道php和c#支持这个,所以它不仅仅是c ++的东西 (4认同)

Ski*_*ick 126

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

int a[] = {
  1,
  2,
  3, //You can delete this line and it's still valid
};
Run Code Online (Sandbox Code Playgroud)

  • @Sean这会在IE JavaScript中导致解析错误,所以要小心! (14认同)
  • 在IE9中它不适合我.但它确实做了一些奇怪的事情......它创建了一个null元素.我会提防. (10认同)
  • JavaScript支持这种语法:`var a = [1,2,];`,我知道的大多数其他语言也是如此... ActionScript,Python,PHP. (6认同)
  • @Sean对不起,你是对的 - 它不是IE中的解析错误,但它*会*将一个额外的元素集插入`undefined`. (5认同)
  • 最令人沮丧的是,JSON不支持这种语法. (3认同)

vcs*_*nes 38

我想,开发人员易于使用.

int a[] = {
            1,
            2,
            2,
            2,
            2,
            2, /*line I could comment out easily without having to remove the previous comma*/
          }
Run Code Online (Sandbox Code Playgroud)

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


Oli*_*rth 32

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

int a[] = {
            5,
            6,
          };
Run Code Online (Sandbox Code Playgroud)

简单地变成:

int a[] = { 
            5,
            6,
            7,
          };
Run Code Online (Sandbox Code Playgroud)

在以后的日子.

  • @Giorgio - 源代码适用于人类,而非机器.这样的小事让我们不能做出简单的换位错误,这是一种祝福,而不是疏忽.作为参考,它也可以在PHP和ECMAScript(以及JavaScript和ActionScript)中以这种方式工作,尽管它在JavaScript对象表示法(JSON)中是无效的(例如``[1,2,3,]`可以但是`{a: 1,b:2,c:3,}`不是). (11认同)
  • 我不认为编辑速度稍快是弄乱语法的一个很好的理由.恕我直言,这只是另一个奇怪的C++功能. (3认同)
  • @Giorgio:嗯,它是从C继承而来的.它完全有可能只是原始语言规范的疏忽,碰巧有一个有用的副作用. (3认同)

amo*_*oss 21

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

int ints[] = {
    3,
    9
};
Run Code Online (Sandbox Code Playgroud)

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

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

int ints[] = {
    3,
    9,
    12
};
Run Code Online (Sandbox Code Playgroud)

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

int ints[] = {
    1,
    3,
    9
};
Run Code Online (Sandbox Code Playgroud)

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

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


Gen*_*yev 15

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

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

程序员没有任何优势.

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

  • @Dereleased - 用同样的逻辑,为什么不应该允许尾随(任何东西),如何`int a = b + c +;`或`if(a && b &&);`它会更容易复制并且最后粘贴任何东西,更容易编写代码生成器.这个问题既简单又主观,在这种情况下,为代码阅读器做最好的事情总是好的. (11认同)
  • 我完全不同意; [我认为]它已经找到了很久以后创建的许多语言的方法,因为它有利于程序员能够转换数组的内容,无条件地注释掉行,等等,而不必担心愚蠢的转置引起的语法错误.我们还没有足够的压力吗? (5认同)
  • @GeneBushuyev - 我不同意这些.虽然允许在数组等中使用尾随逗号是一个删除bug的功能,并且使您的生活更容易作为程序员,但我会为了可读性而采取措施从条件中删除尾随的AND(&&)语句,加号和其他杂项运算符声明.这简直太丑了,IMO. (2认同)
  • 关于`&&`运算符,有时我会像`if(true \n && b1 \n && b2)`这样的条件,以便我可以根据需要添加和删除行. (2认同)

Fre*_*ihl 12

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


Max*_*kin 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";
Run Code Online (Sandbox Code Playgroud)

即,无需对第一个或最后一个项目进行特殊处理,以避免随后使用逗号.

例如,如果代码生成器是用Python编写的,那么很容易通过使用str.join()函数来避免使用尾随逗号:

print("enum Items {")
print(",\n".join(items))
print("}")
Run Code Online (Sandbox Code Playgroud)


Yan*_*kes 9

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

int a [] = {
#ifdef A
    1, //this can be last if B and C is undefined
#endif
#ifdef B
    2,
#endif
#ifdef C
    3,
#endif
};
Run Code Online (Sandbox Code Playgroud)

添加宏来处理最后,将是巨大的痛苦.通过语法上的这种微小变化,这对于管理来说是微不足道的.这比机器生成的代码更重要,因为在Turing完整语言中通常比非常有限的预处理器更容易.


Sha*_*our 8

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

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

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

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

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

  • 支持文献中最受支持的答案,以及此功能的真正来源。 (2认同)
  • 简短而准确。可惜人们错过了这个回复。 (2认同)

Tho*_*ini 7

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

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


Lou*_*uis 7

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

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

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

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

    return EXIT_SUCCESS
}
Run Code Online (Sandbox Code Playgroud)

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

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


Vla*_*lad 6

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

想象一下以下代码:

int a[] = {
   1,
   2,
   //3, // - not needed any more
};
Run Code Online (Sandbox Code Playgroud)

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

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


Mar*_*k B 6

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


Kon*_*ski 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

它很棒,因为它显示了Stack Exchange网站的原始三部曲.

Stack Overflow
Super User
Server Fault
Run Code Online (Sandbox Code Playgroud)

但它有一个问题.你看,这个网站上的页脚显示超级用户之前的服务器故障.在任何人注意之前更好地解决问题.

#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;
    }
}
Run Code Online (Sandbox Code Playgroud)

毕竟,移动线条不是那么难,不是吗?

Stack Overflow
Server FaultSuper User
Run Code Online (Sandbox Code Playgroud)

我知道,没有名为"Server FaultSuper User"的网站,但我们的编译器声称它存在.现在,问题在于C具有字符串连接功能,它允许您编写两个双引号字符串并使用任何内容连接它们(类似的问题也可能发生在整数上,因为-符号有多种含义).

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