为什么不能像使用普通数组一样在函数中声明二维数组参数?
void F(int bar[]){} //Ok
void Fo(int bar[][]) //Not ok
void Foo(int bar[][SIZE]) //Ok
为什么需要声明列的大小?
为什么不能像使用普通数组一样在函数中声明二维数组参数?
void F(int bar[]){} //Ok
void Fo(int bar[][]) //Not ok
void Foo(int bar[][SIZE]) //Ok
为什么需要声明列的大小?
静态数组:
你似乎没有完全明白这一点。我想试着解释一下。正如上面的一些答案所描述的, a 2D Array
inC++
作为 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
元素组成,从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 ;
编译器对它们的处理方式不同,因为 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 Pointer
or 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
发送到函数而不提及它们的行或列的原因。
注意: 很多细节都被忽略了,很多东西都在描述中,特别是内存映射只是为了给你一个想法。
你不能写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
,因此必须提供最右边数组的大小。
编译器需要知道第二维计算偏移量的时间。二维数组实际上存储为一维数组。如果您想发送一个没有已知维度的数组,请考虑使用指向指针的指针和某种方式来自己了解维度。
这与例如 java 不同,因为在 java 中数据类型也包含维度。
由于静态 2D 数组就像 1D 数组,带有一些糖以更好地访问数据,因此您必须考虑指针的算术运算。
当编译器试图访问元素array[x][y]时,它必须计算元素的地址内存,即array+x*NUM_COLS+y。所以它需要知道一行的长度(它包含多少个元素)。
如果您需要更多信息,我建议您使用此链接。
因为当您传递一个数组时,它会衰减为一个指针,因此排除最外面的维度是可以的,这是您可以排除的唯一维度。
void Foo(int bar[][SIZE])
相当于:
void Foo(int (*bar)[SIZE])
在 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++ 在这方面不是一种安全的语言。
让我知道我是否在某处混淆了行和列..