首页 文章

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

提问于
浏览
315

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

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

但事实并非如此 . 当这个代码在Visual Studio上编译时,我感到很惊讶,但就C规则而言,我已经学会了不相信MSVC编译器,所以我检查了标准,并且标准允许它也是 is . 如果你不相信我,你可以看到8.5.1的语法规则 .

enter image description here

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

那么,再说一遍,这个多余的逗号是否有任何特殊原因允许?

19 回答

  • 5

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

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

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

  • -4

    它使代码生成器更容易吐出数组或枚举 .

    想像:

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

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

  • 1

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

    int ints[] = {
        3,
        9
    };
    

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

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

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

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

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

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

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

  • 32

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

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

    程序员没有任何优势 .

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

  • 7

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

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

  • 2

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

    显然有太多的初始化符号,但每个符号似乎都很好地服务于特定的使用方式 . = {initializer_list,opt}表示法是从C继承的,非常适合数据结构和数组的初始化 . [...]

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

    我们可以去C99 rationale看看为什么在C中这是允许的,它说:

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

  • 6

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

    想象一下以下代码:

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

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

    与其他答案相反,我并不认为生成列表的简便性是一个正当理由:毕竟,对于特殊情况下的最后(或第一行)代码来说,这是微不足道的 . 代码生成器只写一次并多次使用 .

  • 17

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

    例如,假设我们有一个看起来像这样的代码 .

    #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具有字符串连接功能,它允许您编写两个双引号字符串并使用任何内容连接它们(类似的问题也可能发生在整数上,如 - 符号有多重含义) .

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

  • 417

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

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

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

  • 5

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

  • 4

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

  • 6

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

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

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

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

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

  • 12

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

    #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
    

    这将是非常尴尬的使用 .

  • 11

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

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

    ...您必须将逗号添加到现有行并添加新行 . 将其与三个已经有逗号的情况相比较,您只需要添加一行 . 同样,如果你想删除一条线,你可以这样做,而不必担心它是如何统一你如何处理线 .

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

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

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

  • 15

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

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

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

  • 122

    像许多事情一样,数组初始值设定项中的尾随逗号是从C继承的C之一(并且必须永远支持) . "Deep C secrets"中提到了 A view totally different from those placed here .

    在有一个以上“逗号悖论”的例子之后:

    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的自动生成更容易 . 如果在每个逗号分隔列表中允许使用尾随逗号,例如枚举声明或单个声明中的多个变量声明符,则声明将更可信 . 他们不是 .

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

  • 6

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

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

    简单地变成:

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

    在以后的日子 .

相关问题