数组的名称是C中的指针吗? 如果没有,数组的名称和指针变量之间有什么区别?
答案 0 :(得分:226)
数组是一个数组,指针是指针,但在大多数情况下,数组名称转换指向指针。经常使用的一个术语是它们衰减指针。
这是一个数组:
int a[7];
a
包含七个整数的空间,你可以在其中一个中赋值,如下所示:
a[3] = 9;
这是一个指针:
int *p;
p
不包含任何整数空格,但它可以指向整数的空格。例如,我们可以将其设置为指向数组a
中的一个位置,例如第一个:
p = &a[0];
可能令人困惑的是你也可以这样写:
p = a;
这不将数组a
的内容复制到指针p
中(无论这意味着什么)。而是将数组名a
转换为指向其第一个元素的指针。因此,分配与前一个分配相同。
现在,您可以以类似于数组的方式使用p
:
p[3] = 17;
这有效的原因是C中的数组解除引用运算符[ ]
是根据指针定义的。 x[y]
表示:从指针x
开始,步骤y
元素在指针指向之后前进,然后取出那里的任何内容。使用指针算术语法,x[y]
也可以写为*(x+y)
。
为了使用普通数组,例如我们的a
,必须首先将a
中的a[3]
名称转换为指针({{1}中的第一个元素}})。然后我们向前迈出3个元素,并采取任何在那里。换句话说:将元素放在数组中的第3位。 (这是数组中的第四个元素,因为第一个元素编号为0。)
因此,总而言之,C程序中的数组名称(在大多数情况下)转换为指针。一个例外是我们在数组上使用a
运算符。如果在此上下文中将sizeof
转换为指针,a
将给出指针的大小而不是实际数组的大小,这将是相当无用的,因此在这种情况下sizeof a
表示阵列本身。
答案 1 :(得分:31)
当数组用作值时,其名称代表第一个元素的地址 当数组未用作值时,其名称代表整个数组。
int arr[7];
/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */
/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */
答案 2 :(得分:13)
如果数组类型的表达式(例如数组名称)出现在更大的表达式中,并且它不是&
或sizeof
运算符的操作数,那么数组的类型表达式从“N元素数组T”转换为“指向T”,表达式的值是数组中第一个元素的地址。
简而言之,数组名称不是指针,但在大多数情况下,它被视为,就像它是一个指针一样。
修改强>
回答评论中的问题:
如果我使用sizeof,我是否只计算数组元素的大小?那么数组“head”也会占用空间,其中包含有关长度和指针的信息(这意味着它需要比普通指针更多的空间)?
创建数组时,唯一分配的空间是元素本身的空间;没有存储实现单独的指针或任何元数据。给定
char a[10];
你记忆中的内容是
+---+
a: | | a[0]
+---+
| | a[1]
+---+
| | a[2]
+---+
...
+---+
| | a[9]
+---+
表达式 a
指的是整个数组,但没有对象 a
与数组元素本身分开。因此,sizeof a
为您提供整个数组的大小(以字节为单位)。表达式&a
为您提供数组的地址,与第一个元素的地址相同。 &a
和&a[0]
之间的差异是第一种情况下结果 1 - char (*)[10]
和第二种情况下char *
的结果类型。
当事情变得奇怪的时候,你想要访问单个元素 - 表达式a[i]
被定义为*(a + i)
的结果 - 给定地址值a
,偏移{{1}来自该地址的元素( not bytes )并取消引用结果。
问题是i
不是指针或地址 - 它是整个数组对象。因此,C中的规则是每当编译器看到数组类型的表达式(例如a
,其类型为a
)和时该表达式不是操作数char [10]
或一元sizeof
运算符,该表达式的类型被转换(“衰减”)为指针类型(&
),表达式的值是该表达式的地址数组的第一个元素。因此,表达式 char *
与表达式a
具有相同的类型和值(并且扩展名为&a[0]
,其类型和值与表达式*a
)。
C源自一个名为B的早期语言,而在B a[0]
中是来自数组元素a
,a[0]
等的单独指针对象Ritchie希望保留B的数组语义,但他不想混淆存储单独的指针对象。所以他摆脱了它。相反,编译器将根据需要在转换期间将数组表达式转换为指针表达式。
请记住,我说数组不存储任何有关其大小的元数据。只要该数组表达式“衰减”到指针,您所拥有的就是指向单个元素的指针。该元素可以是元素序列中的第一个,也可以是单个对象。根据指针本身无法知道。
当你将一个数组表达式传递给一个函数时,所有接收的函数都是一个指向第一个元素的指针 - 它不知道数组有多大(这就是为什么a[1]
函数是如此的威胁和最终从图书馆中删除了)。要使函数知道数组有多少元素,必须使用sentinel值(例如C字符串中的0终止符),或者必须将元素数作为单独的参数传递。
答案 3 :(得分:5)
一个像这样声明的数组
int a[10];
为10 int
分配内存。您无法修改a
,但可以使用a
进行指针算术。
像这样的指针只为指针p
分配内存:
int *p;
它不会分配任何int
个。你可以修改它:
p = a;
并使用数组下标:
p[2] = 5;
a[2] = 5; // same
*(p+2) = 5; // same effect
*(a+2) = 5; // same effect
答案 4 :(得分:4)
数组名称本身会产生一个内存位置,因此您可以将数组名称视为指针:
int a[7];
a[0] = 1976;
a[1] = 1984;
printf("memory location of a: %p", a);
printf("value at memory location %p is %d", a, *a);
你可以对指针做的其他漂亮的东西(例如添加/减去偏移量),你也可以对数组做:
printf("value at memory location %p is %d", a + 1, *(a + 1));
语言方面,如果C没有将数组暴露为某种“指针”(迂腐地说它只是一个内存位置。它不能指向内存中的任意位置,也不能由程序员控制)。我们总是需要对此进行编码:
printf("value at memory location %p is %d", &a[1], a[1]);
答案 5 :(得分:1)
我认为这个例子揭示了这个问题:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
int **b = &a;
printf("a == &a: %d\n", a == b);
return 0;
}
它在gcc 4.9.2中编译好(带有2个警告),并输出以下内容:
a == &a: 1
oops: - )
所以,结论是否定的,数组不是一个指针,它不是作为指针存储在内存中(甚至不是只读的一个),即使它看起来像是,因为你可以获得它的地址和&amp;运营商。但是 - 哎呀 - 那个算子不起作用:-))无论哪种方式,你都被警告过:
p.c: In function ‘main’:
pp.c:6:12: warning: initialization from incompatible pointer type
int **b = &a;
^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
printf("a == &a: %d\n", a == b);
C ++在编译时拒绝任何此类错误尝试。
编辑:
这就是我要展示的内容:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
void *c = a;
void *b = &a;
void *d = &c;
printf("a == &a: %d\n", a == b);
printf("c == &c: %d\n", c == d);
return 0;
}
即使c
和a
“指向”同一内存,您也可以获取c
指针的地址,但无法获取a
的地址指针。
答案 6 :(得分:0)
下面的示例提供了数组名称和指针之间的具体区别。假设您要用给定的最大尺寸表示一维线,则可以使用数组或指针来做到这一点:
typedef struct {
int length;
int line_as_array[1000];
int* line_as_pointer;
} Line;
现在让我们看一下以下代码的行为:
void do_something_with_line(Line line) {
line.line_as_pointer[0] = 0;
line.line_as_array[0] = 0;
}
void main() {
Line my_line;
my_line.length = 20;
my_line.line_as_pointer = (int*) calloc(my_line.length, sizeof(int));
my_line.line_as_pointer[0] = 10;
my_line.line_as_array[0] = 10;
do_something_with_line(my_line);
printf("%d %d\n", my_line.line_as_pointer[0], my_line.line_as_array[0]);
};
此代码将输出:
0 10
这是因为在对do_something_with_line
的函数调用中,对象被复制为:
line_as_pointer
仍包含指向的地址line_as_array
复制到一个新地址,该地址未超出函数范围因此,当直接将数组输入函数时,数组不是由值给定的,但是当将它们封装在结构中时,数组是由值(即复制的)给定的,这概述了与使用指针的实现相比,行为的主要区别。 / p>
答案 7 :(得分:-3)
数组名称的行为类似于指针,并指向数组的第一个元素。例如:
int a[]={1,2,3};
printf("%p\n",a); //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0
两个print语句都会为机器提供完全相同的输出。在我的系统中它给出了:
0x7fff6fe40bc0
答案 8 :(得分:-4)
数组是内存中的secuential和conchaguous元素的集合。在C中,数组的名称是第一个元素的索引,应用偏移量可以访问其余元素。 “第一个元素的索引”确实是指向内存方向的指针。
与指针变量的区别在于你无法改变数组名称所指向的位置,因此类似于const指针(它类似,不一样。请参阅Mark的注释)。但是如果你使用指针aritmetic,你也不需要取消引用数组名来获取值:
char array = "hello wordl";
char* ptr = array;
char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'
所以答案有点'是'。
答案 9 :(得分:-5)
数组名称是数组第一个元素的地址。所以是数组名称是一个const指针。