指针和数组虽然是不同的东西,但却有着千丝万缕的关系,下面就让我们逐一了解吧!
数组名原则上会被解释为指向该数组起始元素的指针。
也就是说。如果a是数组,那么表达式a的值就是a[0]的值,即与&a[0]一致;如果数组a的元素类型为Type型,那么不管有多少个元素,表达式a的类型都为Type*型。
将数组名视为指针,也催生出了数组与指针的密切关系,我们看下图:
这里声明了数组a和指针p,指针p的初始值是a,又因为数组名a会被解释为&a[0],所以存入p的值为&a[0],也就是说指针p会被初始化为指向数组起始元素的a[0],指针p指向的起始元素,不是数组中的全部元素。
围绕着指向数组元素的指针,有以下规则成立:
指针p指向数组的元素e时:
1.p+i为指向元素e后的第i个元素
2.p-i为指向元素e前的第i个元素
sizeof(数组名)不会生成指向起始元素的指针的长度,而是生成数组的整体长度
&+数组名不是指向起始元素的指针的指针,而是指向数组整体得指针
也就是说,指向各元素的指针p+i和&a[i]是等价的,其中&a[i]是指向元素a[i]的指针,其值是a[i]的地址
我们用一个代码来显示出它们各自的值:
#include<stdio.h>
int main()
{
int i = 0;
int a[5] = {1,2,3,4,5};
int *p = a;
for(;i<5;i++)
printf("&a[%d] = %p p+%d = %p\n", i, &a[i], i, p + i);
return 0;
}
如果在指向数组内元素的指针p+i前写上指针运算符*,会变成什么情况呢?
*(p+i)就是该元素的别名,因此如果p指向a[0],那么表达式*(p+i)就表示a[i]的本身
指针p指向数组的元素e时:
指向元素e后第i个元素*(p+i),可以写为p[i]
指向元素e前第i的元素*(p-i),可以写为p[-i]
?我们来看下它们之间的关系:
以下4个表达式都是访问各元素的表达式:
a[i] | *(a+i) | p[i] | *(p+i) |
一下4个表达式都是指向各元素的指针:
&a[i] | a+i | &p[i] | p+i |
Type*型指针p指向Type型数组a的起始元素a[0]时,指针p的行为就和数组a本身一样
表达式a[i]和p+i中的i,表示位于指针a和p所指的元素后第几个元素的位置,所以数组起始元素的下标必须是0
int *p;
int y[5];
p = y;
p是指向int型变量的指针,被赋给p的是y,即&y[0],赋值后,指针p指向y[0]
int a[5];
int b[5];
a = b;
指向a=b时会出现编译错误,虽然说a会被解释为指向数组起始元素的指针,但不可以改写其值
如果可以这样赋值,那么数组的地址就会被改变,变为别的地址,因此,赋值表达式的左操作数不可是数组名。
我们看下指针p和整数i相加的式子前写上指针运算符的表达式*(p+i),括号内的进行的是加法运算,与算数类型的数值间的加法运算等同,也就是*(i+p)
这样一来,访问数组元素的表达式p[i]也可以写成i[p],下表运算符[ ],是具有两个操作数的双目运算符,其中一个操作数的类型是:
指向Type型对象的指针
另一个操作数的类型是:
整数类型数据类型
下标运算符[ ]的操作数的顺序是随意的,例如a[3]也可以写为3[a]
下表运算符[ ]所生成值的类型是:Type型
但是在进行书写时,这种格式虽然没有错误,但还是尽量不要这样写
#include<stdio.h>
/*将数组v开头的n个元素赋值给val*/
void ary_set(int v[], int n, int val)
{
int i = 0;
for(; i < n; i++)
v[i] = val;
}
int main()
{
int i = 0;
int a[] = {1,2,3,4,5};
ary_set(a, 5, 99);
for(;i < 5; i++)
printf("a[%d] = %d\n", i, a[i]);
return 0;
}
对于ary_set函数中数组v的声明可以写为一下几种形式:
a和b都可以解释为c,也就是说形参v的类型不是数组而是指针,即使像b中那样声明元素的个数,也是无用的。
但是我们可以在传递数组时,将数组元素个数作为别的参数来处理。
在调用ary_set函数时,,int*型的形参v将使用实参a,即&a[0]进行初始化,由于指针v指向数组的起始元素a[0],因此在ary_set函数内指针v的行为就像数组本身一样
如果改写所接受的元素的值,结果就会反映到调用方的数组元素上。
函数间数组的传递是以指向第一元素的指针的形式进行的,在被调用的函数中作为指针接收的数组,实际上就是调用方传递的数组。