[英]Static mutlidimensional arrays in C vs pointers and addresses
我正在学习C并陷入下面的代码段:
int a[NUM_ROWS][NUM_COLS], (*p)[NUM_COLS], i;
for (p = &a[0]; p < &a[NUM_ROWS]; p++) {
(*p)[i] = 0;
}
根据作者,这应该将0分配给2d数组的第i列。
我知道实际上不是行和列,而是连续的内存块-几乎是一个1d数组,其中每个元素都是一个1d数组。 另外:数组名称等效于数组的第一个元素的地址(根据我正在阅读的书)。 对于一维数组,这对我来说很有意义,第一元素可以是int或char或其他类型。 但是,在2d数组中,每个元素都是一个数组,因此数组的第一个元素的地址也是-这次是“内部”一个吗? 这是否意味着“ a [0]”给出了数组第一个元素的地址,然后在其上使用“&”运算符? 地址给我一个什么地址? :/
有人可以一步一步解释这里发生了什么吗? 这里的地址是什么,指针是什么,等等。我遍历了各种C语言书籍中有关指针的许多章节,以比较作者的解释方式,但是看起来他们可以互换使用“指针”和“地址”。
我尝试比较所有3个内容,如下所示:
printf("%d ", a);
printf("%d ", a[0]);
printf("%d ", &a[0]);
但它们都具有相同的值:/
您对此非常正确。 记住, a[b]
等效于*(a+b)
, &*x
等效于x
(假设x
出现在表达式上下文中),所以&a[b]
等效于&*(a+b)
只是a+b
,所以&a[0]
只是a
(再次在表达式上下文中)。
在您的情况下, int a[NUM_ROWS][NUM_COLS]
是一块int
类型的NUM_ROWS*NUM_COLS
元素。 它们分为NUM_COLS
int
元素的NUM_ROWS
个块(行)。 如果写a[i][j]
等效于*(*(a + i) + j)
。 内部取消引用实际上并不执行内存访问,而是从数据类型中删除了一定程度的取消引用。 您可以将其视为类型转换。
当在表达式上下文中使用时, a
变为指向a
的第一行的int (*)[NUM_COLS]
指针。 指针加法根据a
元素的大小缩放,即sizeof(int [NUM_COLS])
NUM_COLS*sizeof(int)
为NUM_COLS*sizeof(int)
。
您通常会看到人们谈论在表达式上下文中使用数组名称时,它们会“衰减”为指针。 在一维情况下,如果您有int b[DIM]
,它将“分解”为一个int *
其值为b
的地址。 例如,当作为参数传递给函数时,不传递数组,而是传递其地址。
在二维情况下,如果您具有int a[NUM_ROWS][NUM_COLS]
,则它将“衰减”为一个int (*)[NUM_COLS]
指针,该指针是NUM_COLS
int
元素数组的指针。
在您的示例中,将以下内容传递给printf:
a
(对int (*)[NUM_COLS]
衰减) a[0]
(对int *
衰减) &a[0]
(这只是&*(a + 0)
或a
,衰减为int (*)[NUM_COLS]
) 第一种情况和最后一种情况基本相同。 第二种情况仅在数据类型上有所不同。 请注意,数据类型会影响指针添加。 在将int
添加到指针时, int
会根据指针指向的大小进行缩放。
还要注意,您的printf
格式并不是真正正确的,因为您是在需要int
值的地方传递指针值。 并非在所有平台上都适用,大多数编译器都会警告格式字符串的数据类型错误。
使用printf
格式化地址的安全方法是使用%p
。 这需要一个指针,因此如果指针和int
的大小不相同,您将很安全。
我正在学习C并陷入下面的代码段:
分解声明的每个部分并循环:
int a[NUM_ROWS][NUM_COLS], /* declare an array of int, size NUM_ROWS * NUM_COLS */
(*p)[NUM_COLS], /* delcare pointer to array of int NUM_COLS */
i; /* declare a single int */
for (p = &a[0]; /* assign 'p' the address of 'a' */
p < &a[NUM_ROWS]; /* while address 'p' < address a[NUM_ROWS] */
p++) { /* advance to next pointer 'p' */
(*p)[i] = 0; /* set value at 'p[i]' = 0 */
}
因此,您将每个NUM_COLS
ints数组中的i'th
列(或元素)的值设置为0
。 您需要初始化0 < i < NUM_COLS
否则行为将是不确定的,因为i
0 < i < NUM_COLS
初始化。
一个有效的示例可以帮助您逐步了解正在发生的事情。 基本上,示例代码只是提供一种使用指针和单个整数来隔离列值的方法来逐步浏览2D数组,而不是更常见的使用两个整数。 使用两个整数(使用i
作为列值将每一行中的第二列设置为0
),您将看到:
i = 1;
for (j = 0; j < NUM_ROWS; j++)
a[j][i] = 0;
上面的代码段和您的代码段都将完成相同的操作,不同之处是p
指向3个int
值的数组(将指针值指向)。 您取消引用p
(例如*p
)以获得任何单独行的起始地址。 您必须用括号将解引用的p
括起来,以便为行中的任何单个值建立索引,因为在C 运算符优先级中 , []
运算符的优先级高于'*'
运算符。 (例如,您需要(*p)[x]
而不是*p[x]
)。
#include <stdio.h>
#define NUM_COLS 3
int main (void) {
int a[][NUM_COLS] = {{1,2,3},
{4,5,6},
{7,8,9}};
int (*p)[NUM_COLS];
int i;
int NUM_ROWS = sizeof a/sizeof *a;
printf ("\noriginal array:\n\n");
for (p = a; p < &a[NUM_ROWS]; p++) {
for (i = 0; i < NUM_COLS; i++)
printf (" %2d", (*p)[i]);
putchar ('\n');
}
/* set col 1 (2nd col) to zero */
i = 1;
for (p = a; p < &a[NUM_ROWS]; p++) {
(*p)[i] = 0;
}
printf ("\nmodified array:\n\n");
for (p = a; p < &a[NUM_ROWS]; p++) {
for (i = 0; i < NUM_COLS; i++)
printf (" %2d", (*p)[i]);
putchar ('\n');
}
return 0;
}
输出量
$ ./bin/array_decl
original array:
1 2 3
4 5 6
7 8 9
modified array:
1 0 3
4 0 6
7 0 9
数组名称等效于数组第一个元素的地址
尽管通常都说这是不正确的。 实际上,与任何其他指示符一样, a[0]
指示类型为int[NUM_COLS]
的数组。
该规则的正确版本是可以将数组名称扩展转换为包含第一个元素地址的指针。 这在表达式中对数组名称的许多使用中都会隐式发生,但是在某些表达式中却没有发生。
&a[0]
是不进行此转换的表达式的示例。 它以int x;
之后的相同方式为您提供整个数组a[0]
的地址int x;
然后&x
给出int
的地址。
当数组表达式是&
, sizeof
或增量运算符的操作数时,转换不会发生。
在您的printf
行中,转换确实发生在前两个中,但没有发生在第三个中。 此外,由于使用了错误的格式说明符,它们均会导致未定义的行为。 假设您已解决此问题,请记住,当您输出这样的指针时,您仅输出故事的一部分; 在输出中没有指示符类型的指示器。 尽管前两种情况是不同的指针,但它们在printf中生成相同的输出。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.