7

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

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

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

4

6 回答 6

19

静态数组:

你似乎没有完全明白这一点。我想试着解释一下。正如上面的一些答案所描述的, a 2D ArrayinC++作为 a 存储在内存中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

考虑到这里,Bigger2D Array被存储为连续的内存单元。它由总12元素组成,从011。行是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 ;

编译器对它们的处理方式不同,因为 a 的每个索引都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 Pointeror Array 上的位置1000。它保存2000自己保存内存位置地址的地址。这里的指针运算是由编译器完成的,通过它来判断元素的正确位置。

要将内存分配给2D 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发送到函数而不提及它们的行或列的原因。

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

于 2012-09-29T14:23:34.143 回答
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,因此必须提供最右边数组的大小。

于 2012-09-29T12:25:14.997 回答
1

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

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

于 2012-09-29T12:23:54.337 回答
1

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

于 2012-09-29T12:32:46.610 回答
1

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

 void Foo(int bar[][SIZE]) 

相当于:

 void Foo(int (*bar)[SIZE]) 
于 2012-09-29T12:16:35.113 回答
1

在 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++ 在这方面不是一种安全的语言。

让我知道我是否在某处混淆了行和列..

于 2012-09-29T14:15:42.867 回答