【问题标题】:2D-array as argument to function二维数组作为函数的参数
【发布时间】:2012-09-29 12:11:15
【问题描述】:

为什么不能像使用普通数组一样在函数中声明二维数组参数?

 void F(int bar[]){} //Ok
 void Fo(int bar[][]) //Not ok
 void Foo(int bar[][SIZE]) //Ok

为什么需要声明列的大小?

【问题讨论】:

  • 我认为你可以使用void Foo(int **bar)。你能确认一下吗?
  • 我不能用静态分配的二维数组来调用它
  • @gokcehan 那行不通,二维数组不是 C 中的数组数组。它的存储方式与一维数组相同 - 连续内存。
  • @Carlj901 静态分配的二维数组是在堆栈上分配的,因此需要知道维度。
  • @gokcehan:不,它不能。完全没有。永远。

标签: c++


【解决方案1】:

静态数组:

你似乎没有完全明白这一点。我想试着解释一下。正如上面的一些答案所描述的,C++ 中的2D Array 作为1D Array 存储在内存中。

int arr[3][4] ;   //consider numbers starting from zero are stored in it

在内存中看起来有点像这样。

1000    //ignore this for some moments       1011  
^                                             ^
^                                             ^

0   1   2   3   4   5   6   7   8   9   10   11
|------------|  |-----------|   |-------------|
  First Array    Second Array     Third Array

|----------------------------------------------|    
                Larger 2D Array

考虑这里,更大的2D Array 存储为连续的内存单元。它由12 个元素组成,从011。行是3,列是4。如果要访问第三个数组,则需要跳过整个第一个和第二个数组。也就是说,您需要跳过等于cols 的数量乘以要跳过的数组数量的元素。结果是cols * 2

现在,当您指定要访问数组的任何单个索引的维度时,您需要事先告诉编译器确切要跳过多少元素。所以你给它cols 的确切数字来执行其余的计算。

那么它是如何进行计算的呢?假设它适用于column major order,也就是说,它需要知道要跳过的列数。当您将此数组的一个元素指定为...

arr[i][j] ;

编译器自动执行此计算。

Base Address + (i * cols + j) ;

让我们尝试一个指数的公式来测试它的准确性。我们想要访问2nd 数组的3rd 元素。我们会这样做......

arr[1][2] ;   //access third element of second array

我们把它放在公式中......

  1000 + ( 1 * 4 + 2 )
= 1000 + ( 6 )
= 1006   //destination address

我们到达6 所在的地址1006。 简而言之,我们需要告诉编译器cols 的数量以进行此计算。所以我们把它作为一个函数的参数发送。

如果我们正在处理3D Array,像这样...

int arr[ROWS][COLS][HEIGHT] ;

我们必须在函数中将数组的最后两个维度发送给它。

void myFunction (int arr[][COLS][HEIGHT]) ;

现在的公式会变成这样..

Base Address + ( (i * cols * height) + (j * height) + k )  ;

要这样访问它...

arr[i][j][k] ;

COLS 告诉编译器跳过2D Array 的数量,HEIGHT 告诉它跳过1D Arrays 的数量。 对于任何维度,依此类推。

动态数组:

当您询问在这样声明的动态数组的情况下的不同行为时..

int ** arr ;

编译器对它们的处理方式不同,因为Dynamic 2D Array 的每个索引都包含另一个1D Array 的地址。它们可能出现在堆上的连续位置上,也可能不出现。它们的元素由它们各自的指针访问。我们上面的静态数组的动态对应物看起来有点像这样。

1000  //2D Pointer
^
^
2000       2001     2002
^          ^        ^
^          ^        ^
0          4        8
1          5        9
2          6        10
3          7        11

1st ptr  2nd ptr   3rd ptr

假设是这种情况。这里是2D Pointer1000 位置上的数组。它将地址保存到2000,它本身保存了一个内存位置的地址。这里的指针运算是由编译器完成的,通过它来判断元素的正确位置。

为了给2D Pointer分配内存,我们这样做了..

arr = new int *[3] ;

并以这种方式为每个索引指针分配内存..

for (auto i = 0 ; i < 3 ; ++i)
  arr[i] = new int [4] ;

最后,2D Array 中的每个ptr 本身就是一个数组。要访问您所做的元素...

arr[i][j] ;

编译器会这样做...

*( *(arr + i) + j ) ;
   |---------|
     1st step
|------------------|
      2nd step

在第一步中,2D Array 被取消引用到其适当的1D Array,在第二步中,1D Array 被取消引用以到达适当的索引。 这就是为什么 Dynamic 2D Arrays 被发送到函数而不提及它们的行或列的原因。

注意: 很多细节都被忽略了,很多东西都在描述中,特别是内存映射只是为了给你一个想法。

【讨论】:

  • 解释得很好......“让我们说它适用于列主要订单”。这是什么意思'col major order'
  • 这意味着编译器需要 cols 达到二维数组的索引。在行主顺序中,它需要行数。
  • 那么公式会变成什么?
  • 技术上i*cols + j 索引是行主要顺序。 stackoverflow.com/questions/5991837/row-major-order-indices。行优先和列优先是指如何以一维线性化方式访问内存。这里的行主要顺序是指在每行基础上展开的内存。列主要是指按列展开内存。为了完整起见,列主要线性索引将是j*rows + i。尽管您确实在帖子中提到了列主要顺序,但它可能会与实际定义混淆。我建议编辑您的帖子,否则是一个很好的答案。
【解决方案2】:

你不能写void Foo(int bar[][]),因为bar会衰减为一个指针。想象以下代码:

void Foo(int bar[][]) // pseudocode
{
    bar++; // compiler can't know by how much increase the pointer
    // as it doesn't know size of *bar
}

因此,编译器必须知道*bar 的大小,因此必须提供最右边数组的大小。

【讨论】:

  • 如果它衰减成一个指针,是不是应该可以写 void Foo(int *bar) 并将一个二维数组传递给那个函数?
  • 是的,虽然你必须自己做算术。
  • @Carlj901,n 维数组衰减为指向 (n-1) 维数组的指针,而不是 (n-x) 维数组。你可能只需要向量的向量......
【解决方案3】:

因为当你传递一个数组时,它会衰减为一个指针,所以排除最外面的维度是可以的,这是你唯一可以排除的维度。

 void Foo(int bar[][SIZE]) 

相当于:

 void Foo(int (*bar)[SIZE]) 

【讨论】:

  • 我还是不明白为什么你不能写void Foo(int bar[][])
  • 由于数组在传递给函数时只是指向第一个元素的指针,因此您需要告诉编译器除了最外层维度之外的大小。或者你可以像这样传递它:void Foo(int **bar).
  • 您无法将真正的二维数组传递给void Foo(int **bar)
【解决方案4】:

编译器需要知道第二维是多长时间来计算偏移量。二维数组实际上存储为一维数组。 如果您想发送一个没有已知维度的数组,请考虑使用指向指针的指针和某种方式自己知道维度。

这不同于例如java,因为在java中数据类型也包含维度。

【讨论】:

  • 好点,但有人可能会问,为什么 int** 有效?该符号未指定大小。
  • 因为 int[N][M] 与 int** 不同。 int[][] 是一个静态分配的数组,因此它实际上是一个维度为 NM 的一维数组。而 int* 实际上是一个数组数组。所以第一个数组包含指向第二个数组的指针。分配的静态不包含这些指针,如果您尝试从 int[N][M] int** 进行转换,您将得到奇怪的结果。
【解决方案5】:

由于静态二维数组就像一维数组,加了一些糖以更好地访问数据,因此您必须考虑指针的算术运算。
当编译器试图访问元素array[x][y]时,它必须计算元素的地址内存,即array+x*NUM_COLS+y。所以它需要知道一行的长度(它包含多少个元素)。
如果您需要更多信息,我建议您link

【讨论】:

  • 我认为它需要知道列的长度。通过知道长度,它可以推断出有多少行。但是为什么 int** 有效?您没有指定尺寸。
  • 二维数组在内存中按行定位。而行的长度就是列的数量(你可以把它想象成一个矩阵)。 PS:数组被声明为数组[NUM_ROWS][NUM_COLS]。
  • 嗯,我不这么认为。二维数组实际上是一维数组的数组。我们使用矩阵概念作为抽象。我相信没有我们所知道的
  • 好吧,可能有误会,可能是我的错(原谅我的英语不好)。我同意静态二维数组就像我的回答中所说的一维数组。并且 int** 不能工作,因为静态二维数组不是指针的指针。矩阵视觉是为了理解为什么编译器需要 NUM_COLS:将 2D 数组的抽象 (array[x][y]) 转换为实际内存地址。
  • @DeadMG 是的,但是如果您创建这样的结构:int** M=malloc(NUM_ROWS*sizeof(int*)); for (i=0;i&lt;NUM_ROWS;i++){ M[i]=malloc(NUM_COLS*sizeof(int)); } 那么您将拥有类似于 2D 数组的东西,而不是正确的 2D 数组。我相信你知道这一点,这只是为了解释为什么我每次都说静态 2D ;)
【解决方案6】:

在 C/C++ 中分配二维数组基本上有三种方法

在堆上分配为二维数组

您可以使用malloc 在堆上分配一个二维数组,例如:

const int row = 5;
const int col = 10;
int **bar = (int**)malloc(row * sizeof(int*));
for (size_t i = 0; i < row; ++i)
{
    bar[i] = (int*)malloc(col * sizeof(int));
}

这实际上存储为数组数组,因此不一定 在内存中连续。请注意,这也意味着将有一个指针 每个数组都会消耗额外的内存使用(本例中为 5 个指针,10 指针,如果你以相反的方式分配它)。您可以将此数组传递给 带有签名的函数:

void foo(int **baz)

在堆上分配为一维数组

由于各种原因(缓存优化、内存使用等),它可能是 希望将二维数组存储为一维数组:

const int row = 5;
const int col = 10;
int *bar = (int*)malloc(row * col * sizeof(int));

知道第二维,您可以使用以下方法访问元素:

bar[1 + 2 * col]  // corresponds semantically to bar[2][1]

有些人使用预处理器魔法(或 C++ 中 () 的方法重载)来 自动处理,例如:

#define BAR(i,j) bar[(j) + (i) * col]
..
BAR(2,1) // is actually bar[1 + 2 * col]

你需要有函数签名:

void foo(int *baz)

为了将这个数组传递给一个函数。

在栈上分配

您可以使用以下方式在堆栈上分配一个二维数组:

int bar[5][10];

这是作为堆栈上的一维数组分配的,因此编译器需要知道 像我们在 第二个例子,因此以下也是正确的:

bar[2][1] == (*bar)[1 + 2 * 10]

这个数组的函数签名应该是:

void foo(int baz[][10])

您需要提供第二个维度,以便编译器知道要到达内存中的哪个位置。您不必给出第一个维度,因为 C/C++ 在这方面不是一种安全的语言。

如果我在某处混淆了行和列,请告诉我..

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 2021-10-17
    • 2012-01-22
    • 1970-01-01
    • 2017-02-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多