【问题标题】:Why is dimension range of higher dimensions in multi-dimensional array required?为什么需要多维数组中更高维度的维度范围?
【发布时间】:2016-02-09 10:46:00
【问题描述】:

根据帖子,

Passing a 2D array to a C++ function

int array[10][10];
void passFunc(int a[][10]) //  <---Notice 10 here
{
    // ...
}
passFunc(array);

从编译器内部的角度来看,为什么需要这个更高的维度。

【问题讨论】:

  • 如果您将二维数组视为平面一维数组。您认为它可能会如何被编入索引。
  • 只要索引是问题,实现 n-d 数组 "ptr+ d1*size+d2*size+.....+index" 也可以工作
  • 因为当你尝试访问a[2][0] 时,第二维需要知道在哪个内存位置找到这个元素(它在a+(2*10)+0
  • @tobi303;为什么必须在声明int a[10]; 中提供长度?
  • @hacks 这是一个不同的问题,答案是:告诉编译器它应该分配多少内存。

标签: c++ c


【解决方案1】:

另一种解释(数组到指针衰减):

假设我们有一个一维数组,我们像这样使用它:

int array[10];
int i = array[3];

编译器必须知道在哪里可以找到array[3]。它知道它需要跳过 3 个ints 才能到达array[3] 中的那个。所以它起作用了。

但是如果我们有一个二维数组,

int array[2][5];
int i = array[1][1];

这里要获取i,编译器需要跳过多少ints?它需要跳过一整行,再加上一个。跳过一个很容易,因为我们知道一个int 的大小。但我们还需要知道数组中行的大小——行的大小由类型的大小 * 每行的列数决定。这是看待它的一种方式,它解释了为什么需要后一个维度。

让我们把它变成一个小脑筋急转弯,把它进一步提升到一个维度,

int array[2][2][2];
int i = array[1][1][1];

我们将尺寸称为X、Y、Z

在这里,我们可以说我们有一个有限的 3D 空间 ints。单位当然是一个int 的大小。行数由Y定义,平面数由Z定义。这使得 X 作为基本单位,正如我们所说的,它是一个int 的大小。三者结合产生一个“点”。

为了能够到达该 3D 空间中的任何点,我们需要知道每个维度“停止”的位置以及下一个维度的开始位置。所以我们需要:

  1. 单元的大小(int),遍历X维度
  2. 每个平面的大小(Y),要遍历Y维度
  3. 平面数,要遍历 Z 维度

同样,X 已经给了我们,因为我们使用的是int。但是我们不知道每架飞机的大小,也不知道有多少架飞机。所以我们需要指定除第一个维度之外的所有维度。这是一般规则。

这也解释了为什么这个问题需要比单纯的指针衰减更详细的解释,因为一旦你得到超过 2 个维度,你仍然需要知道它是如何工作的。 换句话说,您需要整体大小(维度的乘积)不溢出,并且您需要 每个 大小的维度能够使用连续的[] 索引。

【讨论】:

  • 但请注意,这忽略了函数参数不是二维数组这一点。
  • 说“3D 数组是指向 2D 数组的指针,它是指向作为指针的数组的指针”是完全错误的。 数组不是指针。
  • @PeterA.Schneider 注意我提到了这一点:我所说的“部分正确”的意思是衰减过程可以工作。您可以使用指向 int 的简单指针遍历 3D 数组。
  • 您关于指针的陈述非常具有误导性。这正是我们每天都会收到诸如“如何通过二维数组int **”之类的问题的原因。当涉及到指针和数组时,您应该非常精确并使用标准术语以避免混淆。
  • @Olaf 我没有说“指向指针的指针”,我说它们都可以衰减到只有一个指针。此外,似乎很容易错过我之所以这么说以及“部分正确”,是因为我在其他答案中看到了它的想象。不过,我会修正措辞以减少混乱。
【解决方案2】:

C/C++ 中的数组是一种类型,但不是一等对象,它们在传递给函数时“衰减”为指向第一个元素的指针。

int[10][10] 是一个由 10 个int[10] 数组组成的数组...函数声明:

void foo(int x[][10]);

typedef int IntArray10[10];
void bar(IntArray10 *x);

对于编译器来说是相同的。

因此,当将二维数组传递给函数时,您传递的是指向第一个元素的指针(并且第一个维度被忽略),但元素本身是一个数组,编译器需要知道它的大小。

【讨论】:

  • 请注意,在 C++ 数组中,一等对象。如果通过引用传递,它们不会衰减。 (假设 C/C++ 是一种语言的危险之一)
  • 有趣的是,当传递一个多维数组作为参数时,我们有 array-decay 和 "array-type-preservation":elements -结果指针点仍然是成熟的数组。类似于为什么一个人不能写int arr[2][3]; int **p = arr;,即使一个可以**arr
  • 嗯,函数参数中发生的是类型调整,即看起来像数组的东西被调整为指针。当您将合适类型的对象传递给函数时,就会发生衰减。
  • @MSalters:你没有“通过引用”传递数组,而是传递了一个引用。
【解决方案3】:

与您可能从参数int a[][10] 中的“[]”所想的相反,该函数不采用二维数组而是一个指向一维数组的指针——它的原型相当于

void passFunc(int (*a)[10])

array 可以衰减为指向其第一个元素的指针,就像所有数组一样。
像往常一样,该指针是&amp;array[0],在这种情况下,它是一个指向具有十个ints 的数组的指针 - 一个int (*)[10]

所以不是你需要指定“更高维度”,而是参数是一个指向十个元素的数组的指针,而不是一个二维数组。

(您不能让a 成为指向未指定大小的数组的指针,因为编译器需要知道a[1] 相对于a[0] 的位置,即它需要知道sizeof(*a)。)

【讨论】:

    【解决方案4】:

    它是必需的,因为在 C 中不存在多维数组的概念。
    我们可以定义任何东西的数组,甚至是另一个数组。最后一个可以看作是一个多维数组。
    现在考虑一个二维数组:

    int arrray[5][4];
    

    这将被解释为一个由 5 个元素组成的数组,每个元素都是一个由 4 个 int 组成的数组。
    在内存中,布局是顺序的:首先是第一个数组的 4 个元素,然后是第二个,依此类推,直到第 5 个数组的 4 个元素。
    要访问第 3 个数组 array[2][1] 的第 2 个元素,编译器必须跳过前 2 个数组,但这样做需要知道要跳过多少个元素
    IE。从数学上讲,第三个数组的第二个元素的地址是:

    //pElement2Array3  points the 2nd element of the 3rd array.
    //Note that 4 is the number of elements of the base dimension
    int *pElement2Array3 = &array[0] + (2 * 4) + 1;
    

    (注意:(2 * 4) + 1 不乘以 sizeof(int),因为这是通过指针算法自动完成的。)
    当使用寻址array[2][1]时,编译器会自动执行上面的数学运算,但是要执行它,编译器必须知道基本数组中有多少元素(可能是数组数组的一个数组...... )

    【讨论】:

    • 是的,这是真的。考虑一下(我们可以 bcz 我正在实现)我有原型 int passFunc(int[][]);诠释一个[2][2];通过函数(一);只有使用正确的参数才能解析调用,并且调用者范围知道“a”及其维度,为什么我们还需要被调用者范围中的维度?
    • 在编译器必须生成代码来寻址数组元素的任何地方,我们都需要维度。
    【解决方案5】:

    简单地说,因为多维数组是从右到左“增长”的。可以这样想:

    int arr [a]; 是一个a 整数数组。

    int arr [b][a] 是由b 组成的数组a 整数数组。

    等等。

    至于为什么在将数组传递给函数时可以省略最左边的维度,是因为“数组衰减”。 6.7.6.3/7 说:

    将参数声明为“类型数组”应调整为 ‘‘限定类型指针’”

    也就是说,如果您将函数参数声明为int param[10]int param[],编译器会将其替换为指向数组的第一个元素的int 指针:int* param。数组本身仍然由调用者分配。

    这样可以防止数组按值传递给函数,这将非常无效,并且在大多数情况下没有任何意义。

    现在对于多维数组,上述规则同样适用。因此,如果您有int param[10][10],它会衰减为指向第一个元素的指针。第一个元素是一个由 10 个整数组成的数组,因此您将获得 一个数组指针,该指针指向一个由 10 个整数组成的数组:int (*param)[10]

    如果您拥有int param[][10],也会发生同样的情况,您仍然会获得int (*param)[10]。所以最左边的维度可以是任何东西 - 没关系,因为无论如何它都会被忽略。

    但是最左边之后的其他维度是必需的,否则编译器将不知道数组会衰减到哪种指针类型。在函数声明的特殊情况之外,没有像 int(*param)[] 这样的东西,它的意思是“指向未知大小数组的数组指针”。

    【讨论】:

      【解决方案6】:

      作为函数参数int a[][10]等价于int (*a)[10],意思是:a是一个指向10个数组的指针int。在这种情况下,它不代表二维数组。

      如果高维留空,则编译器无法知道指针a 指向的数组长度,也无法执行指针运算。

      【讨论】:

      • 只是说“你必须这样做”并不能解释为什么需要这样做
      猜你喜欢
      • 1970-01-01
      • 2020-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多