C中的多维数组:它们是锯齿状的吗?
Posted
技术标签:
【中文标题】C中的多维数组:它们是锯齿状的吗?【英文标题】:Multi-Dimensional Arrays in C: are they jagged? 【发布时间】:2014-03-04 16:41:00 【问题描述】:关于 C 编程语言 (ANSI-C) 的一个简单问题:
C 中的多维数组是锯齿状的吗?
我的意思是——我们是在谈论“数组数组”(一个指向内存中其他地址的指针数组),还是只是“长一维数组”(按顺序存储在内存中)?
令我困扰的是,我有点确定:
matrix[i][j]
等价于* ( * (matrix + i) + j)
【问题讨论】:
这取决于你将如何分配内存。 @KarthikSurianarayanan 当然。我说的是多维数组的常规声明,例如:int matrix[3][4]; @programmer 为您自己获取一份 C99 标准的副本。它非常有用,请阅读第 70 页。 @tesseract 但我说的是 ANSI-C(我有《The C Programming Language 2nd edition》一书)。 【参考方案1】:C 中的多维数组是连续的。以下:
int m[4][5];
由 4 个int[5]
s 组成,在内存中彼此相邻。
指针数组:
int *m[4];
是锯齿状的。每个指针都可以指向不同长度的单独数组(的第一个元素)。
m[i][j]
等价于*(*(m+i)+j)
。请参阅C11 standard,第 6.5.2.1 节:
下标运算符[]的定义是E1[E2]等同于(*((E1)+(E2)))
因此,m[i][j]
等价于(*(m+i))[j]
,后者等价于*(*(m+i)+j)
。
之所以存在这种等价性,是因为在大多数情况下,数组类型的表达式会衰减为指向其第一个元素的指针(C11 标准,6.3.2.1)。 m[i][j]
解释如下:
m
是一个数组数组,因此它衰减为指向第一个子数组 m[0]
的指针。
m+i
是指向m
的i
th 子数组的指针。
m[i]
等价于*(m+i)
,取消引用指向m
的i
th 子数组的指针。由于这是一个数组类型的表达式,它会衰减为指向 m[i][0]
的指针。
m[i][j]
等价于 *(*(m+i)+j)
,解引用指向 m
的 i
th 子数组的 j
th 元素的指针。
请注意,指向数组的指针不同于指向其第一个元素的指针。 m+i
是指向数组的指针;它不是数组类型的表达式,并且无论是指向指针的指针还是指向任何其他类型的指针,它都不会衰减。
【讨论】:
m+i
是一个指针类型的表达式。本例中的类型为int (*)[N]
,其中 N 是次维度的声明宽度。如果传递了m + i
,则函数参数应该相应地声明为指向这种类型的指针,就像传递m
一样。 (或者直接声明int param[][N]
,它们是等价的)。
@WhozCraig:int (*)[N]
是一个指向整数数组的指针,而不是数组本身。
好吧,我把最后一段读了六遍,终于在我脑海中浮现出来,对不起,多余的帖子,你没看错。是“不腐烂”(我在那个音符上绝对讨厌的一个词,标准也是如此)让我感到震惊。 +1 顺便说一句
@WhozCraig:是的,我认为标准从来没有使用过这个词。 6.3.2.1 第 3 部分说“类型为 ''array of type'' 的表达式被转换为类型为 ''pointer to type'' 的表达式”,但是“衰变”对 Google 来说更容易。
我在 SO 这个词的来源上问了很久。任何人都可以找到的最古老的参考是 1987 年。我只是讨厌这个术语,因为它推断出在运行时采取的功能性操作,并且不存在这样的操作。在讨论差异时,我经常简单地说,“指针是保存地址的变量(并且有自己独立的不同地址);数组是是地址的变量(当分配给指针 =P) 时,它们的“值”就是所说的地址。【参考方案2】:
一个连续的内存区域:
int arr[N][M];
非连续的内存区域:
int** arr = malloc(N*sizeof(int*));
for (int i=0; i<N; i++)
arr[i] = malloc(M*sizeof(int));
在这两种情况下,您都可以将arr
用作二维数组(例如arr[1][2] = 3
)。但您可以安全地应用更大的复制操作,例如 memset(arr,0,N*M*sizeof(int))
,仅在第一种情况下。
【讨论】:
我认为 matrix[i][j] 等同于 *(*(matrix + i) + j) 是不是错了? 是的。您可以在这两种情况下使用matrix[i][j]
,但您只能在第二种情况下使用*(*(matrix + i) + j)
。如果您尝试将其用于静态声明的数组 (matrix[N][M]
),那么您很可能会在“外部 *
”中执行非法内存访问。
@Elias Van Ootegem,这不正是我在评论中所说的吗? OP问“我错了吗”。我回答“是”并举了一个例子。
这两种情况都可以使用*(*matrix+i)+j)
;根据索引运算符的定义,它完全等同于matrix[i][j]
。
@barakmanos: rofl... 只是继续是和“一样吗?”,并注意到我在同样的方式:“我错了吗...?” > 是【参考方案3】:
这取决于。
C 中的多维数组是按顺序排列的。
如果你想使用指针,你可以创建交错数组。
【讨论】:
谢谢,我也会复制我写给 Klas Lindback 的内容:那么,为什么 matrix[i][j] 等价于 *(*(matrix + i) + j),如果我们在谈论一维数组?这令人困惑。 这不等同于@Programmer。这意味着任何位于由matrix + i
加上j
指向的地址所指向的地址。这将假定matrix+i+j
包含一个指针。这可能会给你一个 sigsegv
等价于*(matrix +i + j)
谢谢,但这不是我的输出显示的内容。请尝试运行以下程序,并查看输出(我的输出是“错误的”):#include 如果你声明一个多维数组,你会得到“长一维数组”(它是按顺序存储在内存中的)。
如果你声明一个指向指针的指针(指向指针......),你会得到数组的数组。
这种差异是 C 初学者很困惑的根源。
【讨论】:
谢谢。那么,如果我们谈论一维数组,为什么 matrix[i][j] 等价于 *(*(matrix + i) + j)?这令人困惑。 @Programmer 不是。它甚至应该在编译时给你一个错误。*(matrix + i + j * dim_1)
等价于 matrix[i][j]
,其中 dim_1 是主要组件的元素数。
嗯。不,那些甚至不一样。首先,*(matrix + i + j * dim_1)
将导致int(*)[N]
位于matrix[i+j*dim_1]
,这与 OP 的表达式完全不同。其次,如果这个 故意作为单个线性 1-dim 完成,则索引将是 (ar + idim_0 + j) 以等同于在int mat[dim_1][dim_0];
的二维声明矩阵中成为mat[i][j]
(我认为,这就是您想要表达的内容)。
@Programmer matrix[i][j] 的元素是按顺序存储的,就像一维数组一样,但从概念上讲它是二维的。 matrix[i][j]
与 matrix[i][j+1]
相邻,而 matrix[i][j]
位于 dim_1 int
s 远离 matrix[i+1][j]
。【参考方案5】:
一个或多个数组,例如int matrix[A][B]
不是锯齿状的,因为matrix
的每个元素都是array of B int
。
您想知道*(*(matrix+i)+j)
的结果是并将其与matrix[i][j]
的结果进行比较。
由于matrix
的类型是array of A array of B int
,那么表达式matrix+i
是一个指向matrix
的i
tharray of B int
的指针,它的类型是int (*)[B]
。取消引用此表达式会产生 array of B int
。表达式*(matrix+i)+j)
产生一个指向该数组的j
th int
的指针。取消引用该表达式会产生 int
。这相当于表达式matrix[i][j]
的作用。
一个指针数组,例如int *matrix[A]
,可能是锯齿状的,因为matrix
的每个元素可能指向不同大小的分配。
【讨论】:
我认为 matrix[i][j] 等同于 *(*(matrix + i) + j) 是不是错了? 不,但是matrix
的类型对于弄清楚添加的实际作用很重要。
如果矩阵是这样声明的:int matrix[2][3];说 matrix[1][2] 就像写 *(*(matrix + 1) + 2) 有错吗?【参考方案6】:
你是对的,matrix[i][j]
等价于*(*(matrix + i) + j)
,因为arr[i]
等价于*(arr + i)
。但是,请记住,如果 arr
被声明为
int arr[64];
那么任何对arr
的引用都可以隐式转换为&arr[0]
,即指向第一个元素的指针。数组数组也会发生同样的事情:
int matrix[8][8];
这里matrix
的类型为int[8][8]
,当您向其添加整数时,它会自动转换为int (*)[8]
,如matrix + i
。那么*(matrix + i)
的类型为int[8]
,当您添加j
时,它再次转换为int *
,因此*(matrix + i) + j
的类型为int *
,因此*(*(matrix + i) + j)
的类型为int
,正如预期的那样。
所以重点是,数组不是指针,只是它们可以隐式转换为指向其第一个元素的指针。
因此,如果您像上面那样分配数组的数组 (int matrix[8][8];
),那么所有元素在内存中都是连续的。
【讨论】:
以上是关于C中的多维数组:它们是锯齿状的吗?的主要内容,如果未能解决你的问题,请参考以下文章
csharp 如何声明,分配和初始化一维,多维和锯齿状数组的示例。