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 是指向mith 子数组的指针。 m[i] 等价于*(m+i),取消引用指向mith 子数组的指针。由于这是一个数组类型的表达式,它会衰减为指向 m[i][0] 的指针。 m[i][j] 等价于 *(*(m+i)+j),解引用指向 mith 子数组的 jth 元素的指针。

请注意,指向数组的指针不同于指向其第一个元素的指针。 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 int main(void) int i, j;整数矩阵[3][2] = 1, 2, 3, 4, 5, 6 ; for (i = 0; i 所以我可能很困惑。不是多维数组吗?如果是这样,如果我没有像你说的那样使用指针,为什么会出现锯齿状?【参考方案4】:

如果你声明一个多维数组,你会得到“长一维数组”(它是按顺序存储在内存中的)。

如果你声明一个指向指针的指针(指向指针......),你会得到数组的数组。

这种差异是 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 ints 远离 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是一个指向matrixitharray of B int的指针,它的类型是int (*)[B]。取消引用此表达式会产生 array of B int。表达式*(matrix+i)+j) 产生一个指向该数组的jth 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 的引用都可以隐式转换为&amp;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 如何声明,分配和初始化一维,多维和锯齿状数组的示例。

csharp 如何声明,分配和初始化一维,多维和锯齿状数组的示例。

如何在多维数组上使用 LINQ 来“展开”数组?

在维度上求和多维数组[关闭]

从javascript中的多维数组中删除一列

如何找到多维数组的模式?