今天,一位同事在工作中向我展示了一种声明2D阵列的方式,我可以线性分配它,但仍然使用2D方括号([][]
)符号来访问元素。
例如:
#include <stdio.h>
#include <stdlib.h>
#define SIZE 2
int main () {
int (*a)[SIZE][SIZE] = malloc (sizeof (int) * SIZE * SIZE);
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
(*a)[i][j] = 0;
}
}
(*a)[0][1] = 100;
/* should yield:
* 0
* 100
* 0
* 0
*/
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
printf ("%d\n", (*a)[i][j]);
}
}
free (a);
return EXIT_SUCCESS;
}
这与计算索引然后执行指针关节(例如*(a + (x * SIZE + y))
或更多的a[x * SIZE + y]
)以访问元素形成对比。
关键部分是指针x
的形状声明(例如(*x)[][]
),它似乎将此信息编码为x
指向的值的类型。
除此之外,我不明白这是如何工作的。这种符号究竟在做什么?是语法糖吗?它看起来与数组的动态堆栈分配是一致的(参见Array size at run time without dynamic allocation is allowed?作为其中的一个示例),但显然这种分配发生在堆上。
我已经查找了有关指针的这种表示法/声明的更多信息,但除了术语元素类型之外找不到更多 - 但是我是不确定这是否相关。
编辑#1:
我应该提到这个问题是在使用堆的情况下,而不是堆栈。我知道基于堆栈的数组动态分配,但我所做的工作专门研究动态内存分配。
答案 0 :(得分:1)
没有错,但不是更常见(和惯用的方式)。要声明大小为N的动态数组,请使用:int *arr = malloc(N * sizeof(int));
。实际上,这将arr
声明为指向N int 数组的第一个元素的指针。 2D数组是一个数组数组,因此要声明一个二维数组N * N,更常见的方法是:
int (*arr)[N] = malloc(N * N * sizeof(int));
这实际上将arr
声明为指向N int的N个数组的第一个元素的指针。然后,您通常可以使用arr[i][j]
。
那令人惊奇的是什么int (*a)[SIZE][SIZE] = malloc (sizeof (int) * SIZE * SIZE);
?
将arr声明为指向整数的二维数组NxN数组的第一个(和单个)元素的指针。好消息是声明对于所有维度的大小都是明确的,但缺点是您必须始终取消引用它:(*arr)[i][j]
这与[]
运算符的每个定义没有区别 C 来自arr[0][i][j]
。
这只不过是我自己的意见,但我强烈建议你坚持第一种方法。第一个和单个元素技巧可能会扰乱您代码的任何未来读者或维护者,因为它不是惯用语。
答案 1 :(得分:1)
int (*a)[SIZE][SIZE]
将a
声明为指向SIZE
SIZE
int
数组SIZE == 3
的指针 - 假设为 +---+ +---+---+---+
a: | | -------> | | | |
+---+ +---+---+---+
| | | |
+---+---+---+
| | | |
+---+---+---+
,您可以得到以下内容:
(*a)[i][j]
(实际上,布局是严格线性的,但我们现在将使用此表示法)。
要访问指向数组的任何元素,我们写a
- 我们必须明确取消引用a
,因为我们不想索引到{{ 1}},我们想要索引a
指向的内容。
请记住,a[i]
被定义为*(a + i)
- 从该地址给出地址a
,偏移i
个元素(不是字节!)并遵循结果。因此,(*a)[i][j]
相当于a[0][i][j]
。
现在,如果a
指向int
的3x3数组,则a + 1
指向int
的下一个3x3数组:< / p>
+---+ +---+---+---+
a: | | -------> | | | |
+---+ +---+---+---+
| | | |
+---+---+---+
| | | |
+---+---+---+
a + 1: ---------> | | | |
+---+---+---+
| | | |
+---+---+---+
| | | |
+---+---+---+
我们将(*(a + 1))[i][j]
访问,或只是a[1][i][j]
。
现在,为什么要首先使用指向数组的指针?在这种情况下,我们将动态分配数组,如果a)我们不知道在运行时需要多少SIZExSIZE
个数组,或者b)如果结果,我们会这样做数组太大而无法分配为auto
变量,或c)如果我们想根据需要扩展或缩小SIZExSIZE
数组的数量。
这种分配多维数组的方法如何工作?我们首先分配一个N
- T
元素数组:
T *arr = malloc( sizeof *arr * N );
sizeof *arr
相当于sizeof (T)
,因此我们为N
T
类型的T
对象留出空间。
现在让我们用数组类型R [M]
替换R (*arr)[M] = malloc( sizeof *arr * N );
:
sizeof *arr
sizeof (R [M])
相当于N
,因此我们为R [M]
类N
对象留出空间 - IOW,M
{{ 1}} - R
的元素数组。我们动态创建了R a[M][N]
的等价物。
我们也可以把它写成
R (*arr)[M] = malloc( sizeof (R) * M * N );
虽然我更喜欢使用sizeof *arr
;你会在一秒钟内看到原因。
现在,我们可以将R
替换为另一个数组类型S [L]
:
S (*arr)[L][M] = malloc( sizeof *arr * N );
sizeof *arr
相当于sizeof (S [L][M])
,因此我们为N
类型的S [L][M]
个对象分配了足够的空间,或N
{{1由L
的{{1}}数组组成。我们动态创建了M
的等价物。
动态分配1D,2D和3D阵列的语义完全相同 - 所有改变的类型都是类型。通过每次使用S
,我只需要跟踪我需要该类型的元素数量。
答案 2 :(得分:0)
int (*a)[SIZE][SIZE] = malloc (sizeof (int) * SIZE * SIZE);
的作用是声明指向二维整数数组的指针。这只有在你故意想要在堆中而不是在堆栈中分配空间时才有用(例如,如果在编译时数组的维度是未知的)你将取消引用指针并像访问它一样访问它一个普通的二维数组。
您可以通过将变量声明为指针数组来跳过解除引用步骤,每个指针指向一个标准的整数数组int *a[SIZE]
,甚至指向int **a
。在这两种情况下,您都可以使用括号表示法a[x][y]
访问任何值,而无需在之前取消引用a
。
如果您在编译时知道数组的大小,并且不需要在堆中分配它,则可以像这样声明数组:
int a[SIZE][SIZE];
它在分配堆栈空间时更短,效率更高。
您始终可以使用[][]
访问数组。 你必须记住,C中的所有内容都与内存地址偏移有关。如果您有一个声明为 int a[4]
的整数数组,并且您使用方括号(例如a[3]
)访问它,那么您告诉处理器获取a
的内存地址并应用{的偏移量{1}}。您可以使用3 * sizeof(int)
或甚至*(&a + 3)
来访问相同的元素,因为获取地址并添加偏移量与获取偏移量和添加地址相同。
因此,当您使用 3[a]
时,编译器与上面完全相同,只有更多维度。因此,您不需要执行a[2][3]
,因为当您执行a[x * SIZE + y]
时,这正是编译器为您所做的。
编辑:正如评论中指出的那样,实际上指针不一定存储内存引用,尽管这绝对是最常见的实现。
我希望我的解释清楚。
答案 3 :(得分:0)
int (*a)[SIZE][SIZE]
是类型为int[SIZE][SIZE]
的数组的数组指针。这是一种特殊的指针,用于指向整个数组,但其他方式与任何其他指针一样。所以当你写(*a)[i][j]
时,你说“给我指针的内容(2D数组),然后在那些内容中给我项目编号[i] [j]”。
但由于数组指针的行为与其他指针相似,因此您可以使用它指向第一个元素而不是整个2D数组。 (就像您可以使用int*
指向int[n]
数组的第一项一样。)这是使用省略最左侧维度的技巧完成的:int (*a)[SIZE] = ...
。现在指向数组数组中的第一个1D数组。现在,您可以将其用作a[i][j]
,这样更易读,更方便。
数组指针,上面的技巧,以及如何使用它们将2D数组动态分配为一块内存,这些都在我对Correctly allocating multi-dimensional arrays的回答中得到解决。