【问题标题】:int a[] = {1,2,}; Why is a trailing comma in an initializer-list allowed?int a[] = {1,2,};为什么允许在初始化列表中使用尾随逗号?
【发布时间】:2011-10-25 23:33:25
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 每个人似乎都同意“易于添加新行” - 但是定义语言规范的人们真的会为这些事情烦恼吗?如果他们真的那么理解,那么当很明显下一个标记实际上是下一个语句时,他们为什么不忽略丢失的;
  • @YetAnotherUser:是的,语言设计者会考虑这样的事情。允许您删除分号会产生更大的影响,并且在语言的许多部分中会非常模棱两可(请记住,空格在 C 中不是语义)。一个额外的逗号是这种情况并不模棱两可。额外的分号几乎不会有歧义,因此也是允许的。在不明确的情况下(例如在 for() 之后),添加它会引发编译器警告。
  • @Tomalak:这对人类读者来说是模棱两可的,而且经常是一个错误。这就是为什么它会发出警告。同样if (x = 1)在语法上没有歧义,但对人类来说非常歧义,因此会抛出警告。
  • @Rob:您的if 示例也没有歧义。我不认为“模棱两可”意味着你认为它的意思!
  • 只要我们同意它对编译器保护我们是有用的,而数组声明中的尾随逗号对编译器保护我们没有用处。

标签: c++ syntax grammar language-lawyer


【解决方案1】:

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

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

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

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

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

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

【讨论】:

  • 另外,当使用 VCS 时,两个版本之间的“差异”更加清晰,因为添加或删除项目时只有一行发生变化。
  • 如果理由是为了让代码生成更简单,那为什么不采用一些函数式语言的无括号样式呢?为什么不推断所有类型?并删除分号?等等。我认为真正的原因是语言设计者的一个非常主观和不幸的标准。
  • @Néstor:为什么“不幸”?这里有什么缺点?仅仅因为对语言的一小部分的代码生成(和易于操作)进行了一些考虑,并不意味着它必须成为语言中所有决策背后的主要动机。类型推断、删除分号等对语言有巨大的影响。你在这里设置了错误的二分法,IMO。
  • @Néstor:这就是实用主义战胜教条主义的地方:为什么它必须完全一件事或完全另一件事,当它更多时有用两者兼而有之?它实际上是如何妨碍的,能够在末尾添加逗号?这是一种在任何意义上都阻碍过你的矛盾吗?如果不是,请权衡这种无关紧要的不雅与在末尾允许逗号的实际好处
  • @Mrchief:这不是打字速度的问题——在复制、删除或重新排序项目时,这很简单。就在昨天,它让我的生活变得更简单了。没有缺点,为什么让生活更轻松?至于试图指责 MS,我强烈怀疑这在微软存在之前就已经在 C 中了……你说这个理由似乎很奇怪,但我敢打赌,它每天都会使数百家公司的数千名开发人员受益。这难道不是比寻找有利于编译器编写者的东西更好的解释吗?
【解决方案2】:

如果你做这样的事情会很有用:

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

【讨论】:

  • JavaScript 支持这种语法:var a = [1, 2,];,我知道的大多数其他语言也是如此...... ActionScript、Python、PHP。
  • @Sean 这会导致 IE JavaScript 解析错误,所以要小心!
  • 在 IE9 中不适合我。但它确实做了一些奇怪的事情......它创建了一个空元素。我会小心的。
  • @Sean 抱歉,你是对的 - 这不是 IE 中的解析错误,但它插入一个设置为 undefined 的额外元素。
  • 最令人沮丧的是,JSON 不支持这种语法。
【解决方案3】:

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

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

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

【讨论】:

    【解决方案4】:

    我一直认为添加额外元素会更容易:

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

    简单地变成:

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

    在以后的日期。

    【讨论】:

    • 我不认为稍微加快编辑速度是搞乱语法的好理由。恕我直言,这只是另一个奇怪的 C++ 功能。
    • @Giorgio:嗯,它是从 C 继承而来的。这完全有可能只是原始语言规范中的一个疏忽,恰好有一个有用的副作用。
    • 好吧,我不知道它来自C。我刚刚检查了它在Java中也是允许的。不过感觉有点奇怪:在我的直觉中,逗号是分隔符而不是终止符。此外,可以省略最后一个逗号。那么,它是终结符、分隔符还是两者兼而有之?但是好的,这个功能是可用的,很高兴知道。
    • @Giorgio - 源代码适用于人类,而不是机器。像这样防止我们犯简单的换位错误的小事情是一种祝福,而不是疏忽。作为参考,它在 PHP 和 ECMAScript(以及 JavaScript 和 ActionScript)中也是如此,尽管它在 JavaScript 对象表示法 (JSON) 中无效(例如,[1,2,3,] 可以,但{a:1, b:2, c:3,} 不是)。
    • @Groky:我想的越多,我就越相信编程语言的语法应该尽可能简单和一致,并且尽可能少的例外:这使得更容易学习语言(要记住的规则更少)。在列表中添加/删除项目时节省一两次击键的优势(顺便说一句,与我花费在编码上的总时间相比,我不经常这样做)与我相比似乎相当微不足道具有明确定义的语法。
    【解决方案5】:

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

    int ints[] = {
        3,
        9
    };
    

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

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

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

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

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

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

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

    【讨论】:

      【解决方案6】:

      我很惊讶,因为一直没有人引用Annotated C++ Reference Manual(ARM),它说以下关于 [dcl.init] 的内容,重点是我的:

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

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

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

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

      【讨论】:

      • 支持文献支持最多的答案,以及此功能的真正来源。
      【解决方案7】:

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

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

      对于程序员来说真的没有任何优势。

      附:虽然以这种方式自动生成代码更容易,但实际上我总是注意不要放置尾随逗号,工作量很小,可读性得到了提高,而且这一点更重要。你写一次代码,你读了很多遍。

      【讨论】:

      • 我完全不同意; [我认为] 它已经在 C 之后很久创建的许多语言中找到了它的方式,正是因为它有利于程序员能够移动数组的内容,随意注释行等等,不必担心愚蠢的转置引起的语法错误。我们的压力还不够吗?
      • @Dereleased -- 按照同样的逻辑,为什么不允许尾随(任何东西),int a = b + c +;if(a &amp;&amp; b &amp;&amp;); 怎么样,只需复制粘贴任何东西在最后,更容易编写代码生成器。这个问题既琐碎又主观,在这种情况下,为代码阅读器做最好的事情总是好的。
      • @Gene Bushuyev:没错!我经常有带有 + 或 && 的长表达式,运算符位于行尾,当然,当我想删除表达式的最后一个操作数时,我必须花费一些额外的时间。我觉得这个逗号语法真的很奇怪!
      • @GeneBushuyev - 我不同意这些。虽然允许在数组等中使用尾随逗号是一个消除错误的功能,并使您作为程序员的生活更轻松,但为了可读性,我会采取措施从条件中删除尾随 AND (&&) 语句、加号和其他杂项运算符陈述。简直丑陋,IMO。
      • 关于&amp;&amp; 运算符,有时我会使用if (true \n &amp;&amp; b1 \n &amp;&amp; b2) 之类的条件,以便可以根据需要添加和删除行。
      【解决方案8】:

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

      【讨论】:

        【解决方案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
        };
        

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

        【讨论】:

          【解决方案10】:

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

          想象一下:

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

          【讨论】:

            【解决方案11】:

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

            想象一下下面的代码:

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

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

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

            【讨论】:

              【解决方案12】:

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

              【讨论】:

                【解决方案13】:

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

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

                【讨论】:

                • JavaScript 的“数组”(它们只是具有神奇长度属性的对象)无论如何都相当不寻常:var x = [,,,] 是合法的(IE
                • 根据 ECMAScript 规范,它是完全有效的;从理论上讲,它应该可以在任何根据上述规范实现 JavaScript 的浏览器中工作,特别是 the part of the specification found here
                • 不幸的是,JavaScript 就是为公众制作应用程序。所以不,当约 50% 的用户在使用您的应用程序时遇到问题时,它并不完全有效。是的,如果可以的话,我会禁止 IE 好的代码 在那里工作......
                • @Dere:是的,我在回答中说了这么多=)
                • @Dereleased microsoft 发明了自己的规范和命令,其他人至少遵守了这种心态正在改变(感谢上帝)
                【解决方案14】:

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

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

                #include <stdio.h>
                #include <stdlib.h>
                
                int main(void)
                {
                    puts("Line 1");
                    puts("Line 2");
                    puts("Line 3");
                
                    return EXIT_SUCCESS
                }
                

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

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

                【讨论】:

                • 有些语言(例如 Pascal)允许这样做。 IE。你必须在两者之间做出选择;作为终止符 (C) 或分隔符 (Pascal)。 ',' 也一样。如果 ',' 是终止符,对我来说没问题,但是 {1, 2, 3} 必须是语法错误。
                【解决方案15】:

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

                例如,假设我们有一个如下所示的代码。

                #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 具有字符串连接功能,它允许您编写两个双引号字符串并使用空连接它们(整数也可能发生类似问题,因为- 符号有多种含义)。

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

                【讨论】:

                  【解决方案16】:

                  像许多事情一样,数组初始化程序中的尾随逗号是 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 的自动生成更容易。 如果在每个逗号分隔列表中都允许使用尾随逗号,例如在枚举声明中,或在单个声明中使用多个变量声明符,则该声明会更可信。他们不是。

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

                  【讨论】:

                  • enum 案例中禁止使用逗号有点有趣,因为在这种情况下,缺少逗号会造成最少的歧义。给定struct foo arr[] = {{1,2,3,4,5}, {3,4,5,6,7}, };该语言可以赋予两个合理的含义:创建一个二元素数组,或创建一个三元素数组,其中最后一项具有默认值。如果 C 采用了后来的解释,我可以看到禁止 enum foo {moe, larry, curly, }; 的原则是应该只有一种写语句的方式(不带逗号),但是......
                  • ...考虑到 C 愿意在逗号被合理地(但没有)赋予重要意义的情况下忽略逗号(这将是支持禁止的有力论据它在那里)奇怪的是,在逗号没有意义的情况下它不愿意[即使有人将enum foo {moe,,larry,curly,};解释为跳过moelarry之间的数字,这通常并不重要尾随逗号是被处理还是被忽略。唯一重要的情况是最后一项是否是其声明类型的最大值,并且...
                  • ...可以通过简单地说应该忽略最后分配的枚举值之后发生的溢出来处理。
                  • @supercat 有一些语言,如 C#,在开发语言时,先验设计研究会考虑 IDE 功能和集成。 C 不是(也不可能是)这些语言之一。
                  • 即使使用像 C# 这样的语言,不断变化的设计目标也会导致一些非常严重的设计不一致。例如,该语言不支持任何形式的普通方法和运算符的返回类型重载(即使底层框架可以支持它),因为它被视为与拥有易于编译的语言的目标背道而驰,但lambda 评估包括类型推断规则,其解析是 NP 完全的。添加新的方法/运算符重载规则可能会破坏现有代码(尽管我认为好的规则可以最大限度地减少这种危险)......
                  【解决方案17】:

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

                  【讨论】:

                    【解决方案18】:

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

                    #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
                    

                    这样用起来会很尴尬。

                    【讨论】:

                      【解决方案19】:

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

                      【讨论】:

                        【解决方案20】:

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

                        【讨论】:

                        • 这是VC6的一个不符合标准的bug吗?
                        猜你喜欢
                        • 2012-07-20
                        • 2015-01-08
                        • 1970-01-01
                        • 2015-05-19
                        相关资源
                        最近更新 更多