2D数组作为函数的参数

时间:2012-09-29 12:11:15

标签: c++

为什么不像在普通数组中那样在函数中声明2D数组参数?

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

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

6 个答案:

答案 0 :(得分:18)

静态数组:

你似乎没有完全明白这一点。我想过尝试解释一下。正如上面的一些答案所描述的那样,2D Array中的C++作为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

在此考虑,Bigger 2D Array存储为连续的内存单元。它由120之间的11个元素组成。行为3,列为4。如果要访问第三个数组,则需要跳过整个第一个和第二个数组。也就是说,您需要跳过等于cols的数量乘以您想要跳过的数组的元素。它出来是cols * 2

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

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

arr[i][j] ;

编译器会自动执行此计算。

Base Address + (i * cols + j) ;

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

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

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

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

我们到达1006所在的地址6。 简而言之,我们需要告诉编译器此计算的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 Pointer上的1000或数组。它将地址保存到20002D Pointer本身保存内存位置的地址。这里指针算术由编译器完成,由此它判断元素的正确位置。

要将记忆分配给arr = new int *[3] ; ,我们就这样做了。

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

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

ptr

最后,2D Array的每个arr[i][j] ; 本身就是一个数组。要访问您要执行的元素...

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

编译器会这样做......

2D Array

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

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

答案 1 :(得分:6)

你不能写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的大小,因此必须提供最右侧数组的大小。

答案 2 :(得分:1)

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

 void Foo(int bar[][SIZE]) 

相当于:

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

答案 3 :(得分:1)

编译器需要知道第二个维度计算偏移量的时间。事实上,2D阵列存储为一维阵列。 如果你想发送一个没有已知尺寸的数组,可以考虑使用指针指针以及某种方式来自己了解尺寸。

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

答案 4 :(得分:1)

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

答案 5 :(得分:1)

基本上有三种方法可以在C / C ++中分配2d数组

在堆上分配为2d数组

您可以使用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)

在堆上分配为1d数组

可能有各种原因(缓存优化,内存使用等) 希望将2d数组存储为1d数组:

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];

这被分配为堆栈上的1d数组,因此编译器需要知道 到达你需要的元素的第二个维度就像我们在 第二个例子,因此以下情况也是如此:

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

此数组的函数签名应为:

void foo(int baz[][10])

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

如果我在某处混合了行和列,请告诉我。