数组名称是指针吗?

时间:2009-10-29 06:38:05

标签: c arrays pointers

数组的名称是C中的指针吗? 如果没有,数组的名称和指针变量之间有什么区别?

10 个答案:

答案 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] 是来自数组元素aa[0]等的单独指针对象Ritchie希望保留B的数组语义,但他不想混淆存储单独的指针对象。所以他摆脱了它。相反,编译器将根据需要在转换期间将数组表达式转换为指针表达式。

请记住,我说数组不存储任何有关其大小的元数据。只要该数组表达式“衰减”到指针,您所拥有的就是指向单个元素的指针。该元素可以是元素序列中的第一个,也可以是单个对象。根据指针本身无法知道。

当你将一个数组表达式传递给一个函数时,所有接收的函数都是一个指向第一个元素的指针 - 它不知道数组有多大(这就是为什么a[1]函数是如此的威胁和最终从图书馆中删除了)。要使函数知道数组有多少元素,必须使用sentinel值(例如C字符串中的0终止符),或者必须将元素数作为单独的参数传递。


  1. * *可能*影响地址值的解释 - 取决于机器。

答案 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;
}

即使ca“指向”同一内存,您也可以获取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的函数调用中,对象被复制为:

  1. 指针line_as_pointer仍包含指向的地址
  2. 将数组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指针。