【问题标题】:Does the standard define the type for `a[i]` where `a` is `T [M][N]`?标准是否定义了 `a[i]` 的类型,其中 `a` 是 `T [M][N]`?
【发布时间】:2013-08-01 13:49:32
【问题描述】:

我偶尔会使用多维数组,并且好奇标准(C11 和/或 C++11)对使用比为数组声明的“维度”更少的“维度”进行索引的行为。

给定:

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

标准是否说明a[1]a[0][1] 是什么类型,是否合法,是否应按预期正确索引子数组?

auto& b = a[1];

std::cout << b[1][1];

【问题讨论】:

标签: c++ c multidimensional-array language-lawyer


【解决方案1】:

m[1] 只是 int[2][2] 类型。同样m[0][1] 只是int[2]。是的,索引为子数组的工作方式与您认为的一样。

【讨论】:

  • 是的,您可以将two-D array 视为array of one-D 同样3-D 可以解释为array of 2-D
  • 但是,由于m[1]m[0][1] 是数组类型,每个都会自动转换为指向其第一个元素的指针,除非在C 中它是sizeof、@987654332 的操作数@ 或一元 &amp;。 C++ 类似,但auto &amp;b = a[1] 是新的,所以我不知道在那种情况下会发生什么。
  • 由于它是自动引用,我想它不会衰减为指针。
  • @EricPostpischil:您可以推断auto,因为它使用与模板类型推导相同的机制。在template &lt;typename T&gt; void f(T&amp;); f(a); 中,参数类型将是对整个数组的引用。
【解决方案2】:

标准是否定义了a[i] 的类型,其中aT [M][N]

当然。该标准基本上定义了 all 表达式的类型,如果没有,它将是一个缺陷报告。但我猜你对那种类型可能更感兴趣......

虽然标准可能未明确提及您的情况,但规则已说明且很简单,给定 N 类型为 T 的数组 a,表达式 a[0] 是 @ 类型的左值表达式987654329@。在变量声明int a[2][2]a 的类型是array of 2 elements of type array of two elements of type int,应用上面的规则意味着a[0]左值到 2 个元素的数组,或者您必须在程序中键入它:int (&amp;)[2]。添加额外维度不会影响机制。

【讨论】:

  • C(不是 C++)中可能存在异常——VLA 到底是什么类型?很高兴 C++ 得到了一个基于模板的机制。
  • @BenVoigt:VLA 就是 VLA :) 并且 C++14 承诺 Arrays of Runtime Bound(尚未批准,但在即将投票的草案中)与 VLA 类似的功能只有小的差异(sizeof 在 VLA 中是运行时,在 ARB 中是不允许的,因为大多数其他直接作用于类型的东西)。如果我理解正确的话,所有涉及 ARB 的表达式都涉及衰减,并且类型是指向第一个元素的指针。
  • So I see 我认为std::dynarray 可以很好地替代 VLA。
  • @BenVoigt:在新标准中有两个相似的特性却有着完全不同的接口,这有点尴尬。此外,ARB 不能(很可能)用于实现dynarray,这意味着它们都需要直接编译器支持。
  • ARB 只能是自动存储持续时间的局部变量...这排除了使用它们创建 std::dynarray 的仅限库的实现。但我发现 ARB 提案考虑不周。他们没有提供std::dynarray 提案没有提供的任何东西,并且还提出了一个荒谬的说法,即检测 ARB 对于静态分析工具来说很容易。恰恰相反,我很确定由于模板和constexpr 函数的复杂性,确定绑定是否是编译时常量表达式需要完整的编译器通过。
【解决方案3】:

我认为 C11 中的这个例子隐含地解释了它。

C11 6.5.2.1 Array subscripting

示例考虑由声明int x[3][5]; 定义的数组对象这里x 是一个3 × 5 的整数数组;更准确地说,x 是一个由三个元素对象组成的数组,每个对象都是一个由五个整数组成的数组。在表达式x[i](相当于(*((x) + (i))))中,x 首先被转换为指向五个整数的初始数组的指针。然后根据x的类型调整i,这在概念上需要将i乘以指针指向的对象的大小,即五个int对象的数组。添加结果并应用间接以产生五个整数的数组。当在表达式x[i][j] 中使用时,该数组又被转换为指向第一个整数的指针,因此x[i][j] 产生int

C++11 8.3.4 Arrays 中类似

示例:考虑

int x[3][5];

这里的x 是一个 3 × 5 的整数数组。当x 出现在表达式中时,它被转换为指向(三个中的第一个)五元整数数组的指针。在等价于*(x + i) 的表达式x[i] 中,首先将x 转换为所描述的指针;然后将x + i 转换为x 的类型,这涉及将i 乘以指针指向的对象的长度,即五个整数对象。结果已添加 和间接应用于产生一个数组(五个整数),然后将其转换为指向第一个整数的指针。如果有另一个下标,同样的论点再次适用;这次结果是一个整数。 —结束示例] —结束说明]

【讨论】:

  • 我很好奇 c++ 标准引号导致将 a[1] 解释为指针,但 b 类型实际上是对数组的引用,在这种情况下没有衰减(我知道我是否使用过auto 而不是 auto&amp; 它会衰减)。
  • 嘿,即使您引用了,您也应该使用&lt;code&gt;` 编写编程术语
  • @GrijeshChauhan 真的吗?我从来没有这样做过。看起来应该更好,我马上就去。
  • @YuHao 好吧,例如我在你的这个答案中。我总是查看Jonathan Leffler 如何写他的答案,我从这个答案和编辑(修订历史)中学习并改进了很多
  • @GrijeshChauhan 是的,经过编辑,看起来好多了,谢谢,我以后一定会尝试遵循这种风格。
【解决方案4】:

要记住的关键点是,在 C 和 C++ 中,多维数组只是数组的数组(因此 3 维数组是数组数组的数组)。多维数组的所有语法和语义都遵循于此(当然,也遵循该语言的其他规则)。

所以给定一个对象定义:

int m[2][2][2];

mint[2][2][2]类型的对象(两个数组组成的数组,每个数组包含两个元素,每个元素包含两个元素,每个数组都是两个ints的数组) .

当您编写m[1][1][1] 时,您已经在评估mm[1]m[1][1]

表达式m 是一个左值,引用int[2][2][2] 类型的数组对象。

m[1] 中,数组表达式m 被隐式转换为(“衰减”为)指向数组第一个元素的指针。这个指针的类型是int(*)[2][2],一个指向int 的二元素数组的二元素数组的指针。 m[1] 的定义等同于 *(m+1)+1m 前进一个元素并取消引用结果指针值。所以m[1] 指的是int[2][2] 类型的对象(由两个数组组成的数组,每个数组由两个int 元素组成)。

(数组索引运算符[]被定义为对指针进行操作,而不是数组。在arr[42]这样的常见情况下,指针恰好是隐式数组的结果- 到指针的转换。)

我们对m[1][1] 重复该过程,给我们一个指向两个ints(类型为int(*)[2])的数组的指针。

最后,m[1][1][1] 获取评估 m[1][1] 的结果并再次重复该过程,为我们提供一个指向 int 类型对象的左值。这就是多维数组的工作原理。

更简单的是,像foo[index1][index2][index3] 这样的表达式可以直接使用指针和数组。这意味着您可以使用指针和任意大小的分配来构造(几乎)像真正的多维数组一样工作的东西。这使您有可能在每行中拥有具有不同数量元素的“参差不齐”的数组,甚至是缺少的行和元素。但接下来由您来管理每一行甚至每个元素的分配和释放。

推荐阅读:comp.lang.c FAQ第6节。

附注:在某些语言中,多维数组不是数组的数组。例如,在 Ada 中(它使用括号而不是方括号进行数组索引),你可以有一个数组数组,索引如arr(i)(j),或者你可以有一个二维数组,索引如arr(i, j)。 C 不同; C 没有对多维数组的直接内置支持,但它为您提供了自己构建它们的工具。

【讨论】:

    猜你喜欢
    • 2022-10-13
    • 1970-01-01
    • 2021-10-15
    • 2018-12-05
    • 1970-01-01
    • 2018-10-21
    • 1970-01-01
    • 2018-07-04
    • 2021-12-11
    相关资源
    最近更新 更多