为什么不像在普通数组中那样在函数中声明2D数组参数?
void F(int bar[]){} //Ok
void Fo(int bar[][]) //Not ok
void Foo(int bar[][SIZE]) //Ok
为什么需要声明列的大小?
答案 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
存储为连续的内存单元。它由12
到0
之间的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
或数组。它将地址保存到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
被取消引用以达到适当的索引。
这就是为什么{{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数组
您可以使用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)
可能有各种原因(缓存优化,内存使用等) 希望将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 ++不是一种安全的语言。
如果我在某处混合了行和列,请告诉我。