首先我们举一个变相使用指针的例子:数组名是数组首元素的地址。也就是说,如果a是一个数组,下面语句成立:
a == &a[0]; //数组名是该数组首元素的地址
a和a[0]都表示数组首元素的内存地址。两者都是常量,在程序的运行过程中,不会改变。但是,可以把它们赋给指针变量,然后可以修改指针变量的值。如下面程序:
#include<stdio.h>
#include<stdlib.h>
#define size 4
int main() {
short dates[size];
short *pti;
short i;
double bills[size];
double *ptf;
pti = dates;//把数组地址赋给指针
ptf = bills;
printf("%23s %10s\n", "short", "double");
for (i = 0; i < size; ++i) {
printf("pointers + %d: %10p %10p\n", i, pti + i, ptf + i);
}
system("pause");
return 0;
}
运行结果:
注:地址是十六进制的,因此7D比7C大1,3D比3C大1。
在我们的系统中,地址按字节编址,short类型占用2字节,double类型占用8字节。在C中,指针加1指的是增加一个存储单元。对数组而言,这意味着加1后的地址是下一个元素的地址,而不是下一个字节的地址。这是为什么必须声明指针所指向对象类型的原因之一。只知道地址不够,因为计算机要知道存储对象需要多少字节(即使指针指向的是标量变量,也要知道变量的类型,否则*pt就无法正确地取回地址上的值)
现在可以更清楚地定义指向int的指针,指向float的指针,以及指向其他数据对象的指针。
- 指针的值是它所指向对象的地址。地址的表示方法依赖于计算机内部的硬件。许多计算机都是按字节编址,意思是内存中的每个字节都按顺序编号。这里,一个较大的对象的地址(如double类型的变量)通常是该对象第一个字节的地址。
- 在指针前面使用*运算符可以得到该指针所指向对象的值。
- 指针加1,指针的值递增它所指向类型的大小(以字节为单位)。
下面的等式体现了C语言的灵活性:
dates + 2 == &dates[2];//相同的地址
*(dates + 2) == dates[2];//相同的值
以上关系表明了数组和指针的关系十分密切,可以使用指针标识数组的元素和获得元素的值。从本质上看,同一个对象有两种表示法。实际上,C语言标准在描述数组表示法时确实借助了指针。也就是说,定义ar[n]的意思是*(ar+n)。
明白了数组和指针的关系,便可以在编写程序时适时使用数组表示法或指针表示法。如下面程序:
#include<stdio.h>
#include<stdlib.h>
int main() {
int days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
int i = 0;
for (i = 0; i < 12; ++i) {
printf("Month %2d has %d days.\n", i + 1, *(days + i));//与days[i]相同
}
system("pause");
return 0;
}
这里,days是数组首元素的地址,days+i是元素days[i]的地址,而*(days+i)则是该元素的值,相当于days[i]。