优秀的编程知识分享平台

网站首页 > 技术文章 正文

C语言中数组和指针的等价(统一)与不同

nanyue 2024-09-23 10:45:39 技术文章 9 ℃

数组是一个由(同一类型的)连续元素组成的预先分配的内存块。指针是一个对任何位置的(特定类型的)元素的引用。

数组自动分配空间,但是不能重新分配或改变大小。指针必须被赋值以指向分配的空间(可能使用malloc),但是可以随意重新赋值(即指向不同的对象),同时除了表示一个内存块的基址之外,还有许多其他的用途。(指向一块内存的指针本质上也就是一个数组)

数组和指针的统一是C语言的优势之一。

用指针可以很方便地访问数组和模拟动态分配的数组。

多数的数组引用(在求值上下文中对数组的任何提及)都会退化为数组第一个元素的指针(或者说,对数组的引用会变成指针),这是C语言中数组和指针“等价”的基础。因此。数组永远不能作为一个整体操作(如,复制或将它们传入函数),因为数组的名字只是一个指针,而不是整个数组。因为数组退化为指针,数组的下标操作符[]总能通过对指针的操作顺序找到自己,事实上,下标表达式[i]就是按照等价的指针表达式*((a)+(i))定义的。

在下述两种情况下,数组名不会被视为指向起始元素的指针。

I 作为sizeof运算符的操作数出现时,sizeof(数组名)不会生成指向起始元素的指针的长度,而是生成数组整体的长度。

II 作为取址运算符&的操作数出现时,&数组名不是指向起始元素的指针的指针,而是指向数组整体的指针。

除了上述两种情况以外,数组名都会被解释为指向该数组起始元素的指针。也就是说,如果a是数组,那么数组名a就是&a[0]。

当我们在一个源文件中定义了char a[6],在另一个源文件中声明了extern char *a。前面是定义了一个字符串,而在另一个文件中定义了指向字符的指针,extern char *a的声明不能和真正的定义char a[6]匹配。类型T的指针和类型T的数组并非同种类型。需要使用extern char a[]。

数组char[6]请求预留6个字符的位置,并用名称a表示。也就是说,有一个称为“a”的位置,可以放入6个字符。而指针声明char *a请求一个位置放置指针,用名称“a”表示,这个指针几乎可以指向任何位置:任何字符或任何连续的字符,或者哪里也不指。

一个图形胜过千言万语。声明

char a[] = “hello”;

char *p = “world”;

将会初始化下图所示的数据结果:

a:

h

e

l

l

o

\0

p:


w

o

r

l

d

\0

根据x是数组还是指针,像x[3]这样的引用会生成不同的代码,以上面的声明为例,当编译器看到表达式a[3]的时候,它生成的代码从a的位置开始跳过3个,然后取出那个字符。如果它看到p[3],它生成的代码找到p的位置,取出其中的指针值,在指针上加3然后取出指向的字符。换言之,a[3]是名为a的对象(的起始位置)之后3个位置的值,而p[3]是p指向的对象的3个位置之后的值。

数组和指针的等价不表示它们相同,甚至也不能互换。它的意思是说数组和指针的算法定义使得可以用指针方便地访问数组或者模拟数组。在C语言中只是指针算术和数组下标运算等价,也就是说,在C语言中,编译器并不那么严格区分数组下标操作符[]作用于数组和指针的不同。在形如a[i]的表达式中,数组退化为指针然后按照指针变量的方式如p[i]那样寻址(下标操作符[]实际上是指针操作符)。如果你把数组地址赋给指针:

p = a;

那么p[3]和a[3]将会访问同样的元素。

这种和谐的访问解释了指针如何访问数组、如何替代数组作为函数参数,以及如何模拟动态数组。

数组和指针虽然不同,但作为函数形参的数组和指针可以互换(仅限于函数形参的声明)。任何声明“看起来像”数组的参数,如

void f(char a[])

{…}

在编译器里都被当作指针来处理,因为在传入数组的时候,函数接收到的正是指针。

void f(char *a)

{…}

有些人将数组理解为常量指针,这是不对的,因为数组不是指针,数组名之所以为“常量”是因为它不能被赋值。(赋值表达式的左操作数不可以是数组名)

最近发表
标签列表