数组和指针联系
转载:http://www.cnblogs.com/yanlingyin/
数组和指针经常出现于编程语言中、也许上课的时候老师也说过数组和指针有区别、参考书上也应该讲过,你是不是也不曾透彻的理清过?
这篇博文主要从内存和编译的角度指出了数组和指针在访问方式上的区别、至于他们在函数调用的区别、以及它们的联系将在下一篇中详细讨论。
为了说的清楚些、会先说一些基础的部分、如果你已经掌握大可跳过
What's a Declaration? What's a Definition? 声明和定义
c语言的对象必须有且只有一个定义,但可以有多个声明(extern)这里说的对象和面向对象中的对象没有关系。
A definition is the special kind of declaration that creates an object; a declaration indicates a name
that allows you to refer to an object created here or elsewhere。
定义是一种特殊的声明、它创建了一个对象;声明简单的说明了在其他地方创建的对象的名字,它允许你使用这个名字。
可以简单的这样理解:
声明Declaration:描述在其他地方创建的对象,并不分配内存。(可以出现在多个地方)
定义Definition:产生一个新的对象,并分配内存。(只能出现一次)
How Arrays and Pointers Are Accessed -数组和指针是如何访问的
数组和指针在内存中的访问方式是不一样的。这里先要注意一下“地址y”和“地址y的内容”的区别。“地址y”表示变量y在内存中的地址,而“地址y的内容”指的是
位于这个地址中的内容,也就是变量y的值。大多数编程语言中用同一个符号来表示这两个东西,而由编译器根据上下文环境判断它的含义。
以一个简答的赋值为例:
上文中的x指的是x所代表的地址,而y的含义是y的内容。
出现在赋值符号左边的值称为左值、赋值符号右边的称为右值。编译器为每个变量分配地址(左值)。这个地址在编译时可知且一直存在,而它的右值在运行时
才能知道。通俗的说:每个变量都有一个地址,这个地址在编译时可以知道,而地址里存储的内容(也就是变量的值)只有在运行时才能知道。如果需要用到变量
的值,(也就是已知地址存储的值)那么编译器发出指令从指定地址读入变量值并放入相应寄存器中。
这里的关键是地址在编译时可知、如果要对进行一些操作(比如说加上偏移量之类的)可以直接操作。相反、对于指针,必须在运行时取得它的地址,然后才能
对它进行接触引用操作。下图展示了对数组下标的引用:
这样我们就可以解释为什么extern char a[]和extern char a[100]相同的原因了。这两个什么都是表名a是一个数组,也就是一个内存地址,
数组内的字符可以由这个地址找到。
和上面不同的是,如果声明的是一个指针,如 extern char *p,它表示p指向一个字符,为了取得这个字符,必须知道地址p的内容,把它作为字符的地址
并从这个地址中取得这个字符。
如果是数组a[],那么可以直接用数组名来访问数组中的元素,因为它的内容就是第一个元素, 他的下一个地址也就对应了下一个数组元素的地址。
如果是指针*a,先要取出地址a的内容,再把它作为变量的地址并从这个地址中取得变量的内容。
数组和指针的其他区别:
定义指针时,编译器并不为它所指向的对象分配空间,只为指针本身分配空间。除非在定义同时付给一个指针一字符窜常量进行初始化。
如:char *p = "breadfruit";
一般情况下初始化指针时创建的字符串变量被定义为只读。如果试图修改就会出现未定义的行为。
这篇文章主要是从访问形式上对数组和指针的区别做了些小的总结,而对于数组和指针在函数调用中、已经更本质的区别、什么时候数组
和指针又是等同的、将在下一篇博文中给出。如果完全弄清楚了、对今后的编程也会有不小的帮助。
什么时候数组和指针相同(When an Array Is a Pointer )
在实际应用中,他们可以互换的情形要大大多于不能互换的情形。首先再回顾一下声明和定义,(上一篇中有提到这里在深入一下)
声明本身还可以进一步分为三种情况:
1)外部数组的声明(external array)
2)数组的定义(它是声明的一种特殊情况,它分配内存空间,并可能提供一个初值)
3)函数参数的声明
所有作为函数参数的声明在编译时总是会转换为指针(指向数组第一个元素),而其他情况的声明,数组就是数组 ,指针就是指针
可以用如下的图来说明他们的关系:
稍微总结一下:
对于编译过程中数组会转化为指针的情况,数组和指针可以互换,比如:声明为函数参数的时候 fun(int a [])和fun(int *a )是等同的。因为编译的过程中fun(int a [])
中的数组会转化为指针形式,也就和fun(int *a )的效果一样了。
如果编译到时候数组不被当做指针处理,那么声明的时候不能等同。对于这种情况,数组和指针是不一样的的,在运行时的表示形式也不一样,并可能产生不同的代码。
对编译器而言,一个数组就是一个地址,一个指针就是地址的地址。
c语言标准对此做了如下说明:
1)An array name in an expression (in contrast with a declaration) is treated by the compiler as a pointer to the first element of the array
(paraphrase, ANSI C Standard, paragraph 6.2.2.1).
2)A subscript is always equivalent to an offset from a pointer (paraphrase,
ANSI C Standard, paragraph 6.3.2.1).
3)An array name in the declaration of a function parameter is treated by
the compiler as a pointer to the first element of the array (paraphrase, ANSI
C Standard, paragraph 6.7.1).
即:
1)表达式中的数组名(与声明不同)被编译器当做一个指向该数组第一个元素的指针
2)下标总是与偏移量相同
3)在函数参数的声明中,数组名被编译器当做指向该数组第一个元素的指针
我觉得有必要对上文中出现的“表达式”做一个解释
int arry[10]={,,,,,};
int a=arry[2];
那么第二句中的int a=arry[2];就是所谓的表达式中出现的数组名了,这个时候编译器会把数组名arry当做指向数组第一个元素的指针,也就是arry[0]的地址
下标总是与偏移量相同,也就是arry[2]中的下标2和arry[2]这个元素在内存中相对于第一个元素的偏移量也是2它们是相同的。这样就能解释用数组下标可以取得
相应的数组中的某个元素了。(当然,在内存中还要考虑步长因素)
有了上面的分析,下面的容易弄懂了
如果声明: int a[10] ,*p , i=2
那么我们可以通过下面的任一种方式来访问a[i](每一列为一组,共三种)
p=a; p=a; p=a+i;
p[i]; *(p+i); *p;
在表达式中,数组和指针是可以互换的, 因为它们在编译器里都是指针形式,并且都能进行去下标操作。
数组和指针的遍历
为了更好的理解,下面通过一个例子说明数组和指针的联系和区别。(有点难理解~哦)
将在内存访问的角度来讨论数组和指针遍历
数组遍历:
for(i=0;i<10;i++)
a[i]=0
遍历过程:
1)把a的左值放入寄存器R1(也就是把a的物理地址也即数组的首地址存入R1) 可提到循环外
2)把i 的左值放入R2,同上,就是吧i 的物理地址放入R2 可移到循环外
3)把 [R2] 的右值放入 R3 也就是把变量i 的大小放入R3,(这里有点汇编的味道)
4)如果需要,调整R3 的步长,把R1 +R3 的值放入R4 解释:R1为数组的首地址,R3为偏移量,所以R4 就是当前操作数的地址
5)把0放入 [R4]
注:上面的R1-R4 看看做是寄存器,符号 [n] 表示的是:内存地址为n的值
”可以移到循环外“说明它在整个过程中不会改变,比如数组的首地址,变量 i 地址
左值和右值的概念在上面链接给出的博文中有阐述
指针遍历:
p=a
for(i =0 ;i < 10 ; i ++)
*p++=0
遍历过程:
1)p 所指对象的大小放入R5 可移到循环外
2)左值 p 放入R1 可移到循环外
3)[R0]放入R1
4)0存到[ R1]
5) R5+R1的结果存入R1
6)R1 存到[R0]
其实,这两两种访问方式也可看做是对上一篇博客中的访问方式的一个更深的理解。
要操作一个变量就要得到这个变量的地址,取得地址的方式数组和指针的区别和联系。这里就不在啰嗦,有兴趣的朋友可以参见上一篇博文
http://www.cnblogs.com/yanlingyin/archive/2011/11/29/2268391.html
为什么C把数组形参当做指针
把作为形参的数组当做指针来考虑其实是出于效率考虑。C中,所有非数组形式的数据实参均为值传递形式,值传递也就是调用函数的时候,把实参
拷贝一份给调用函数,就是说函数操作的是实参的拷贝而不是实参本身。(所以值传递的时候如果在函数中改变参数值,等调用结束后对实际的实参没有
影响,因为值传递中函数操作的只是实参的一份拷贝而并不是实参本身)
而对于数组,如果每次执行函数都要拷贝整个数组的话,就会花费大量的时间和空间开销,所有对于数组,C开用的机制是告诉函数数组的首地址,直接对
数组进程操作。
了解C++的朋友对于这应该就能更好地理解了,C++中参数传递分为值传递和引用传递,有兴趣的可以自行查阅资料、这不是本文终点不在复数
数组和指针的可交换性总结:Arrays and Pointers Interchangeability Summary
1. An array access a[i] is always "rewritten" or interpreted by the compiler as a pointer access *(a+i);
2. Pointers are always just pointers; they are never rewritten to arrays. You can apply a
subscript to a pointer; you typically do this when the pointer is a function argument,
and you know that you will be passing an array in.
3. An array declaration in the specific context (only) of a function parameter can equally
be written as a pointer. An array that is a function argument (i.e., in a call to the
function) is always changed, by the compiler, to a pointer to the start of the array.
4. Therefore, you have the choice for defining a function parameter which is an array,
either as an array or as a pointer. Whichever way you define it, you actually get a
pointer inside the function.
5. In all other cases, definitions should match declarations. If you defined it as an array,
your extern declaration should be an array. And likewise for a pointer.
1)对于a [i]这种形式的访问数组,通常被解释为指针形式*(a + i) 也就是上文中所说的“表达式”的情形
2)指针就是指针,没有说指针转化为数组的情况,你可以用下标的形式去访问指针,但一般都是指针作为函数参数时,而且传入的是一个数组
3)在函数参数的声明中,数组可以看做指针,(也只有这种情况)
4)当把一个数组定义为函数参数时,可以定义为数组,也可以是指针
5)其他的所有情况,声明和定义必须匹配。如果定义了一个数组,在其他文件中对它也必须声明为数组。指针也一样。