【问题标题】:Omitting Sizes while Initializing C/C++ Multidimensional Arrays初始化 C/C++ 多维数组时忽略大小
【发布时间】:2014-04-16 06:53:00
【问题描述】:

我对 C/C++ 编译器的了解是,它们在初始化多维数组时会忽略内括号。

所以,你不能这样做:

int myArray[][] = { { 2, 3 }, { 4, 5 }, { 4, 1 } };

因为编译器会看到它完全一样

int myArray[][] = { 2, 3, 4, 5, 4, 1 };

现在它不知道是 6 * 1、3 * 2、2 * 3、1 * 6,还是别的什么(因为这可以是部分初始化列表,不一定完整)。

我的问题是,为什么这在许多编译器中都有效?

int myArray[][2] = { { 2 }, { 4, 5 }, { 4, 1 } };

编译器“直观地”将其视为:

int myArray[][2] = { { 2, 0 }, { 4, 5 }, { 4, 1 } };

这意味着它不会忽略大括号。到目前为止,我已经在三种不同的编译器上进行了尝试,并且都可以正常工作。

我希望答案是“这只是依赖于编译器”。我无权访问该标准,因此请提供来自该标准的答案。我不需要直觉,我有我的。

【问题讨论】:

  • open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf(这是两个“几乎发布”的 C++11 标准之一 - 最终版本仅包含编辑更改 AFAIK)
  • 只有 C 和/或 C++ 标准的作者才能知道这个问题的答案。
  • @MatsPetersson:这不是真的。这将使标准几乎毫无用处。
  • 呃,我误解了这个问题,我以为是“为什么我们不能让编译器根据我写的内容确定大小”。
  • 你不能做第一个例子的原因是你不能像那样声明一个多维数组。只有“最左边”的维度可以省略其大小。

标签: c++ c multidimensional-array initialization


【解决方案1】:

以下内容来自 K&R 的“The C Programming Language”,第 2 版,第 219,220 页的 A8.7 节:

聚合是一个结构或数组。如果聚合包含 聚合类型的成员,初始化规则递归应用。 初始化中可以省略大括号,如下所示:如果 本身是聚合的聚合成员的初始化程序 以左大括号开头,然后是后续的逗号分隔列表 初始化器初始化子聚合的成员;它是 初始化器比成员多是错误的。如果, 但是,子聚合的初始值设定项不以 left 开头 大括号,则只考虑列表中的足够元素 子集合的成员;任何剩余的成员都留给 初始化子聚合的聚合的下一个成员 是一部分。例如,

 int x[] = { 1, 3, 5 }; 

声明并初始化 x 为具有三个成员的一维数组,因为没有指定大小并且

有三个初始化器。

因此,鉴于这一行

int myArray[][2] = { { 2 }, { 4, 5 }, { 4, 1 } };

编译器将递归地初始化数组,注意每个子数组都以左大括号开始,并且初始化器的数量不超过所需数量,并将计算子数组的数量以确定数组的第一个维度。

以下内容来自 K&R 的“The C Programming Language”,第 2 版,第 220 页的 A8.7 节:

float y[4][3] = {
    { 1, 3, 5 },    
    { 2, 4, 6 },
    { 3, 5, 7 }
};

是一个全括号初始化:1,35初始化数组y[0]的第一行,即y[0][0],y[0][1],和y[0][2]。同样,接下来的两行初始化y[1]y[2]。初始化程序提前结束,因此y[3] 的元素被初始化为0。完全一样的效果可以通过

float y[4][3] = {
   1, 3, 5, 2, 4, 6, 3, 5, 7 
};

注意,在这两种情况下,数组的第四行都会被初始化 为零,因为没有指定足够的初始化器。

float y[4][3] = { 
    { 1 }, { 2 }, { 3 }, { 4 } 
};

初始化y 的第一列并保留其余的0

所以编译器不会忽略内括号。但是,如果您按顺序指定所有初始化程序且没有间隙,则内括号是可选的。如果您不想指定完整的初始化程序集,则使用内括号可以更好地控制初始化。

【讨论】:

    【解决方案2】:

    以下内容来自 K&R 的“The C Programming Language”的 A8.7,第 2 版,第 220 页

    float y[4][3] = {
        { 1, 3, 5 },    
        { 2, 4, 6 },
        { 3, 5, 7 }
    };
    

    等价于

    float y[4][3] = {
       1, 3, 5, 2, 4, 6, 3, 5, 7 
    };
    

    请注意,在这两种情况下,数组的第四行都将被初始化为零,因为没有指定足够的初始化器。

    float y[4][3] = { 
        { 1 }, { 2 }, { 3 }, { 4 } 
    };
    

    初始化 y 的第一列,其余为 0。

    所以编译器不会忽略内括号。但是,如果您按顺序指定所有初始化程序且没有间隙,则不需要内大括号。如果您不想指定一整套初始化程序,使用内大括号可以更好地控制初始化。

    【讨论】:

    • 能否将您的两个答案合并到第三个答案中?我不想接受一个而离开另一个,因为它们都有助于正确答案。
    • 没问题,我将此答案附加到我的另一个答案中。
    【解决方案3】:

    以下是 C 标准中的一些引用,可以帮助理解数组的初始化。

    20 如果聚合或联合包含的元素或成员是 聚合或联合,这些规则递归地应用于 子聚合或包含的联合。如果一个初始化器 子聚合或包含联合以左大括号开头, 由该大括号及其匹配的右大括号括起来的初始化程序 初始化子聚合的元素或成员或 包含联合。否则,列表中只有足够的初始化器是 考虑到子聚合的元素或成员或 包含联盟的第一个成员;任何剩余的初始化器 剩下来初始化聚合的下一个元素或成员 当前子聚合或包含的联合是其中的一部分。

    21 如果大括号括起来的列表中的初始值设定项比那里少 是聚合的元素或成员,或 用于初始化已知大小的数组的字符串文字 是数组中的元素,聚合的其余部分应为 隐式初始化与具有静态存储的对象相同 持续时间。

    22 如果一个未知大小的数组被初始化,它的大小是确定的 通过具有显式初始化程序的最大索引元素。数组 type 在其初始化列表的末尾完成。

    这是标准中的一个例子

    int y[4][3] = {
        { 1, 3, 5 },
        { 2, 4, 6 },
        { 3, 5, 7 },
    };
    

    是一个带全括号初始化的定义:1、3、5初始化y的第一行(数组对象y[0]),即y[0][0]、y[0][1] , 和 y[0][2]。同样,接下来的两行初始化 y[1] 和 y[2]。初始化器提前结束,所以 y[3] 用零初始化。完全一样的效果可以通过

    int y[4][3] = {
        1, 3, 5, 2, 4, 6, 3, 5, 7
    };
    

    y[0] 的初始值设定项不以左大括号开头,因此使用了列表中的三个项目。同样,接下来的三个依次为 y[1] 和 y[2]。

    【讨论】:

      【解决方案4】:

      ANSCI C-89 (3.5.7) 说:

      float y[4][3] = {
          { 1, 3, 5 },
          { 2, 4, 6 },
          { 3, 5, 7 },
      };
      

      是一个带全括号初始化的定义:1、3和5初始化数组对象y[0]的第一行(即y[0][0]y[0][1]y[0][2])。同样,接下来的两行初始化y[1]y[2]。初始化程序提前结束,所以y[3] 用零初始化。完全一样的效果可以通过

      float y[4][3] = { 1, 3, 5, 2, 4, 6, 3, 5, 7 };
      

      y[0] 的初始值设定项不以左大括号开头,因此使用了列表中的三个项目。同样,接下来的三个依次为y[1]y[2]。还有,

      float z[4][3] = {
          { 1 }, { 2 }, { 3 }, { 4 }
      };
      

      按照指定初始化z 的第一列,用零初始化其余列

      【讨论】:

        【解决方案5】:

        我猜,这一章是相关的:

        8.5.1 聚合

        (...)

        当初始化一个多维数组时,初始化子句初始化数组中最后一个(最右边)索引变化最快的元素(8.3.4)。 [ 例子:

        int x[2][2] = { 3, 1, 4, 2 };
        

        x[0][0] 初始化为 3,x[0][1] 初始化为 1,x[1][0] 初始化为 4,x[1][1] 初始化为 2。另一方面,

        float y[4][3] = {
            { 1 }, { 2 }, { 3 }, { 4 }
        };
        

        初始化 y 的第一列(被视为二维数组),其余部分为零。 ——结束示例]

        在表单的声明中

        T x = { a };
        

        可以在初始化列表中省略大括号,如下所示。105 如果初始化列表以左大括号开头,则后续的初始化子句的逗号分隔列表初始化子聚合的成员;初始化子句比成员多是错误的。但是,如果子聚合的初始化器列表不以左大括号开头,则仅从列表中获取足够的初始化器子句来初始化子聚合的成员;任何剩余的初始化子句都被留下来初始化当前子聚合是其成员的聚合的下一个成员。 [ 例子:

        float y[4][3] = {
            { 1, 3, 5 },
            { 2, 4, 6 },
            { 3, 5, 7 },
        };
        

        是一个完全支撑的初始化:1、3、5初始化数组y[0]的第一行,即y[0][0]y[0][1]y[0][2]。同样,接下来的两行初始化y[1]y[2]。初始化程序提前结束,因此y[3]s 元素被初始化,就好像使用 float() 形式的表达式显式初始化一样,即使用 0.0 初始化。在下面的例子中,初始化列表中的大括号被省略了;但是初始化器列表与上面示例的完全支撑初始化器列表具有相同的效果,

        float y[4][3] = {
            1, 3, 5, 2, 4, 6, 3, 5, 7
        };
        

        y 的初始化程序以左大括号开头,但 y[0] 的初始化程序没有,因此使用了列表中的三个元素。同样,接下来的三个依次为y[1]y[2]。 ——结束示例]

        【讨论】:

          【解决方案6】:

          语言规范确实在数组初始化中声明“未给出的字段将为零”:

          第 8.5 节:n3337 版本的 C++11 标准的第 7 段:

          如果列表中的初始化子句少于 聚合中的成员,然后每个成员未显式初始化 应从一个空的初始化列表(8.5.4)初始化。 [ 示例:

          struct S { int a; const char* b; int c; };
          S ss = { 1, "asdf" };
          

          将 ss.a 初始化为 1,将 ss.b 初始化为“asdf”,将 ss.c 初始化为 int() 形式的表达式,即 0。—结束示例]

          【讨论】:

            猜你喜欢
            • 2011-04-03
            • 2015-02-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-02-20
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多