11-指针和数组
10.3 指针和数组
第9章介绍过指针,指针提供一种以符号形式使用地址的方法。因为计算机的硬件指令非常依赖地址,指针在某种程度上把程序员想要传达的指令以更接近机器的方式表达。因此,使用指针的程序更有效率。尤其是,指针能有效地处理数组。我们很快就会学到,数组表示法其实是在变相地使用指针。
我们举一个变相使用指针的例子:数组名是数组首元素的地址。也就是说,如果 flizny 是一个数组,下面的语句成立:
flizny == &flizny[0]; // 数组名是该数组首元素的地址
flizny 和 &flizny[0] 都表示数组首元素的内存地址( & 是地址运算符)。两者都是常量,在程序的运行过程中,不会改变。但是,可以把它们赋值给指针变量,然后可以修改指针变量的值,如程序清单 10.8 所示。注意指针加上一个数时,它的值发生了什么变化(转换说明 %p 通常以十六进制显示指针的值)。
程序清单10.8 pnt_add.c 程序
// pnt_add.c -- 指针地址
#include <stdio.h>
#define SIZE 4
int main(void)
{
short dates[SIZE];
short * pti;
short index;
double bills[SIZE];
double * ptf;
pti = dates; // 把数组地址赋给指针
ptf = bills;
printf("%23s %15s\n", "short", "double");
for (index = 0; index < SIZE; index++)
printf("pointers + %d: %10p %10p\n", index, pti + index, ptf + index);
return 0;
}
下面是该例的输出示例:
short double
pointers + 0: 0x7fff5fbff8dc 0x7fff5fbff8a0
pointers + 1: 0x7fff5fbff8de 0x7fff5fbff8a8
pointers + 2: 0x7fff5fbff8e0 0x7fff5fbff8b0
pointers + 3: 0x7fff5fbff8e2 0x7fff5fbff8b8
第2行打印的是两个数组开始的地址,下一行打印的是指针加1后的地址,以此类推。注意,地址是十六进制的,因此 dd 比 dc 大 1 , a1 比 a0 大 1 。但是,显示的地址是怎么回事?
0x7fff5fbff8dc + 1是否是0x7fff5fbff8de?
0x7fff5fbff8a0 + 1是否是0x7fff5fbff8a8?
我们的系统中,地址按字节编址, short 类型占用2字节, double 类型占用8字节。在C中,指针加 1 指的是增加一个存储单元。对数组而言,这意味着加 1 后的地址是下一个元素的地址,而不是下一个字节的地址(见图10.3)。这是为什么必须声明指针所指向对象类型的原因之一。只知道地址不够,因为计算机要知道存储对象需要多少字节(即使指针指向的是标量变量,也要知道变量的类型,否则* pt 就无法正确地取回地址上的值)。

现在可以更清楚地定义指向 int 的指针、指向 float 的指针,以及指向其他数据对象的指针。
- 指针的值是它所指向对象的地址。地址的表示方式依赖于计算机内部的硬件。许多计算机(包括PC和Macintosh)都是按字节编址,意思是内存中的每个字节都按顺序编号。这里,一个较大对象的地址(如
double类型的变量)通常是该对象第一个字节的地址。 - 在指针前面使用
*运算符可以得到该指针所指向对象的值。 - 指针加1,指针的值递增它所指向类型的大小(以字节为单位)。
下面的等式体现了C语言的灵活性:
dates + 2 == &dates[2] // 相同的地址
*(dates + 2) == dates[2] // 相同的值
以上关系表明了数组和指针的关系十分密切,可以使用指针标识数组的元素和获得元素的值。从本质上看,同一个对象有两种表示法。实际上,C语言标准在描述数组表示法时确实借助了指针。也就是说,定义 ar[n] 的意思是 (ar + n) 。可以认为 (ar + n) 的意思是“到内存的 ar 位置,然后移动 n 个单元,检索存储在那里的值”。
顺带一提,不要混淆 (dates+2) 和 dates+2 。间接运算符()的优先级高于 + ,所以 dates+2 相当于 ( * dates)+2 :
*(dates + 2) // dates第3个元素的值
*dates + 2 // dates第1个元素的值加2
明白了数组和指针的关系,便可在编写程序时适时使用数组表示法或指针表示法。运行程序清单10.9后输出的结果和程序清单10.1输出的结果相同。
程序清单10.9 day_mon3.c 程序
/* day_mon3.c -- uses pointer notation */
#include <stdio.h>
#define MONTHS 12
int main(void)
{
int days[MONTHS] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int index;
for (index = 0; index < MONTHS; index++)
printf("Month %2d has %d days.\n", index + 1,
*(days + index)); //与 days[index]相同
return 0;
}
这里, days 是数组首元素的地址, days + index 是元素 days[index] 的地址,而* (days + index) 则是该元素的值,相当于 days[index] 。 for 循环依次引用数组中的每个元素,并打印各元素的内容。
这样编写程序是否有优势?不一定。编译器编译这两种写法生成的代码相同。程序清单 10.9 要注意的是,指针表示法和数组表示法是两种等效的方法。该例演示了可以用指针表示数组,反过来,也可以用数组表示指针。在使用以数组为参数的函数时要注意这点。