我正在学习C语言中的指针和数组,这是一个让我感到困惑的问题:
因此,一维int数组的名称是指向 int 的常量指针,该指针指向该数组中的第一个元素。因此,当我们评估一维数组的名称时,我们应该获取数组中第一个元素的地址。
对于2D int数组,该数组名称是指向 int 的第一个数组的指针。那么,二维int数组名称的值是什么?我认为它应该是此2D数组中第一个数组的地址。但是,如何在C中定义数组的地址?仅仅是那个数组中第一个元素的地址吗?
答案 0 :(得分:4)
所以一维int数组的名称是指向int的常量指针
这是错误的,并且常常被错误地教导。数组是一个数组。这是一些类似的代码:
int x = 5;
double d = x + 1.2;
在第二行中,x
被转换为 double
以用于添加。这不会更改仍为x
的{{1}},转换结果是“临时”的,并且仅在添加完成之前存在。 int
运算符的条件要求必须将两个算术操作数都转换为通用类型(在这种情况下为+
)进行转换。
在数组的情况下,假设我们有double
,那么char *p = arrayname + 1
仍然是数组。但是它会转换为临时指针,以便可以进行加法操作(arrayname
运算符需要此操作,它可以添加一个指针和一个整数)。临时指针指向数组的第一个元素,但是说临时指针是是数组是不正确的。
大多数运算符会调用将数组转换为临时指针的转换,但有些不会。因此说+
是指针是不正确的,因为它可能与不会将数组转换为指针的运算符一起使用,例如arrayname
。
将数组转换为指针的结果是指向该数组第一个元素的指针。即使第一个元素本身是数组,也始终如此。
但是在C中如何定义数组的地址?仅仅是那个数组中第一个元素的地址吗?
不。每个变量都有一个地址,这适用于数组和非数组。如果您了解sizeof arrayname
的地址,那么您也了解int
的2x2数组的地址。
答案 1 :(得分:3)
数组名称不是指针。在大多数情况下,使用数组名称时,它会隐式*转换*为指向其第一个元素的指针,据说该数组会衰减为指针。
当数组的名称是地址运算符(&
),sizeof
运算符的参数以及字符串文字(即某些字符类型的数组)用于初始化数组*)。
也就是说,二维数组
T arr[COLS][ROWS];
第一个元素是T[ROWS]
类型的数组。因此arr
会衰减到类型为T(*)[ROWS]
的指针,该指针指向arr
的第一个元素。
*)如果您想添加该数组,它们在作为_Alignof
运算符的操作数或在其他地方读取时也不会衰减:
@EricPostpischi:数组不能为_Alignof
的操作数。在C 2011标准中,在数组转换例外中包含_Alignof
是一个错误。 _Alignof
操作数只能是类型,不能是表达式。
答案 2 :(得分:2)
当2D数组衰减到指针时,您将具有指向数组的指针。这是一个如下所示的示例:
int arr[5][6];
int (*p)[6] = arr;
答案 3 :(得分:2)
让我们清理一些东西:
int a = 24;
以上是很多事情:
a
的名为int
的变量。int
类型的对象。24
让我们来回顾一下:创建了一个int
类型的对象,其值是24
和变量a
命名。
现在让我们将其应用于以下内容:
int a1[3] = {0, 1, 2};
a1
的变量int[3]
(又称3个整数的数组)。{0, 1, 2}
初始化对象变量a1
命名该对象。
因此,一维int数组的名称是指向int的常量指针,其中 指向该数组中的第一个元素。
错了。我知道您可能已经被告知或阅读了此书,但这是不正确的。数组不是是指针!数组和指针是不同的类型。话虽如此,出于方便和历史原因,在大多数情况下(但不是全部!),数组会衰减为指向第一个元素的指针:
int a1[3] = {0, 1, 2};
int* p = a1; // here a1 decays to a pointer to its first element
上面的代码段p
指向数组0
的元素a1
您可以用相同的方式查看2D或3D或nD阵列:
T a2[3] = {l0, l1, l2};
假设T
是一种类型。上面是“ 3 T
s的数组”。
如果T
是int
,则我们有int a2[3] = {0, 1, 2}
-3个整数的数组。我们称其为一维int数组。
,但是如果T
是int[2]
,则上面的内容将变成int a2[3][2] = {{00, 01}, {10, 11}, {20, 21}}
-您可以将其视为“由3个T
s组成的数组”或“ 3 int[2]
”或“ 3个2的整数数组组成的数组”。
我们可以应用相同的衰减规则:
int a2[3][2] = {{00, 01}, {10, 11}, {20, 21}};
int (*p2)[2] = a2; // a2 decays to a pointer to its first element.
// Its first element is an array of 2 int.
// So a2 decays to `int (*)[2]` - a pointer to an array of two elements.
上面的a2
指向数组的元素{00, 01}
。
答案 4 :(得分:1)
数组不是指针。在表达式中使用数组名称时,它会“衰减”到第一个元素的指针中。
严格来说,C仅具有一维数组,而二维数组实际上只是数组的数组。
一维数组:
int arr [x]
的第一个元素是int
。 arr
时,将获得指向该元素int*
的指针。sizeof(arr[0])
。2D数组:
int arr [x][y]
的第一个元素是int [y]
。 arr
时,将获得指向该元素int (*)[y]
的指针。 sizeof(arr[0])
。所以这是相同的规则。 int(*)[y]
数组指针遵循与普通指针相同的指针算术规则。但是您可以进一步取消引用它,以在数组数组中获得单个int
。
答案 5 :(得分:0)
“那么二维int数组名称的值是什么?”
“我实际上理解数组不是指针。在我的问题中, 我真正的意思是当在数组中使用数组名称时 表达式,编译器将生成指针常量。”
您必须在这里小心。作为对问题下方的评论的后续内容,数组/指针转换规则如何应用会影响转换产生的type
会产生细微差别。这将决定是否以及如何在表达式中使用数组名称。
“ ...编译器将生成指针常量。”
否。编译器不会生成指针常量,编译器将遵循C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)。在表达式中使用数组名称时,将使用数组转换为指针所得到的地址对表达式进行求值(以第3段中所述的4个例外为准)。
关于数组/指针转换的规则不取决于维数,无论如何,该规则都适用。 但是,转换产生的指针的type
确实取决于数组维数。这很关键,这将决定您对阵列名称的使用是否有效。
帮助巩固转换中发生的事情的一种方法是逐步进行。从一维阵列开始,然后逐步向上。
6.3.2.1-访问时将一维数组转换为指针
例如,当您有一个简单的数组时
int array[10];
在访问数组时,将转换为指向第一个元素的指针,例如,元素&array[0]
的地址。 (它只是指向 {em> int
的指针,或具有正式类型int *
的指针)
6.3.2.1-2D数组转换为访问时指针
对于2D数组,规则适用相同,例如
int array[10][10];
这里array
是2D数组,本质上是10-int[10]
数组(一维数组的数组)的数组。在访问时,array[10][10]
会以完全相同的方式10-int
转换为指向&array[0][0]
的第一个数组的指针(这导致指向的数组的指针int[10]
或正式类型为int (*)[10]
)不是 pointer-to-pointer (例如int**
),而是一个非常具体的指向数组的指针int[10]
。
(请注意:int *[10]
(10个指针的数组 中的重要区别,访问时它们将成为指向-pointer)和int (*)[10]
( pointer 指向10的数组 int
))
答案
“所以...在表达式中使用的2D int数组名称的值” –是第一个1D 数组的整数的地址,形式类型为int (*)[N]
的2D数组(其中N
是每行元素的数量)。
该标准的应用
类型对于正确使用数组名称至关重要。对于2D数组,结果地址是指向 array 的指针。如果取消引用该指针会产生什么结果? (答案:一个数组)当您通过取消引用的指针访问该 array 时会发生什么? (提示:访问规则的转换再次适用)。您必须知道转换后的指针类型是什么,以便在表达式中正确使用数组名称。
可能有用的示例
也许不是,但是使用由数组访问和指针转换产生的指针类型可能有助于解决问题。在下面的示例中,声明了4x3
的简单int
2D数组。然后,它声明适当类型的指针(p
),以允许在将数组地址分配给指针的表达式中使用数组名称。然后,使用使用数组名称初始化的指针来进一步初始化指向第一个数组中第一个元素的整数指针(ip
)。
该示例然后输出每个元素的地址,然后使用指针p
输出组成2D数组的每个行数组的开头的地址。最后,代码进入验证循环,将每个元素的地址与(1)数组索引,(2)指针p
所保存的地址(使用偏移量)和(3)由ip
所保存的地址进行比较。目的是使用由表达式产生的每个不同的指针,该表达式将分配数组名称,然后引用每个元素,并确保每个指针持有的地址一致。
#include <stdio.h>
int main (void) {
int array[ ][3] = { {1, 2, 3}, /* 2D array values */
{3, 4, 5},
{5, 6, 7},
{7, 8, 9} },
(*p)[3] = array, /* pointer to array */
*ip = *p; /* integer poiner */
size_t size = sizeof array,
nele = size / sizeof **array,
nrow = size / sizeof *array,
ncol = sizeof *array / sizeof **array;
printf ("2D array statistics:\n\n"
" size: %zu (bytes)\n nele: %zu (ints)\n"
" nrow: %zu\n ncol: %zu\n",
size, nele, nrow, ncol);
puts ("\naddress of each array element:\n");
for (size_t i = 0; i < nrow; i++) {
for (size_t j = 0; j < ncol; j++)
printf (" %p", (void*)&array[i][j]);
putchar ('\n');
}
puts ("\naddress of each 1D array:\n");
for (size_t i = 0; i < nrow; i++)
printf (" %p\n", (void*)p[i]);
puts ("\nvalidating each array element address by index & pointer:\n");
for (size_t i = 0; i < nrow; i++) {
for (size_t j = 0; j < ncol; j++) {
if (ip != &array[i][j] || ip != *p + j) {
fprintf (stderr, "address validation failed for "
"array[%zu][%zu]\n(%p != %p || %p != %p)\n",
i, j, (void*)ip, (void*)&array[i][j],
(void*)ip, (void*)(p + j));
return 1;
}
ip++;
}
p++;
}
puts (" done!");
return 0;
}
使用/输出示例
$ ./bin/array_2d_access
2D array statistics:
size: 48 (bytes)
nele: 12 (ints)
nrow: 4
ncol: 3
address of each array element:
0x7ffe7c9a9780 0x7ffe7c9a9784 0x7ffe7c9a9788
0x7ffe7c9a978c 0x7ffe7c9a9790 0x7ffe7c9a9794
0x7ffe7c9a9798 0x7ffe7c9a979c 0x7ffe7c9a97a0
0x7ffe7c9a97a4 0x7ffe7c9a97a8 0x7ffe7c9a97ac
address of each 1D array:
0x7ffe7c9a9780
0x7ffe7c9a978c
0x7ffe7c9a9798
0x7ffe7c9a97a4
validating each array element address by index & pointer:
done!
让我知道这是否有帮助,以及您是否还有其他疑问。