指针提供一种以符号形式使用地址的方法。因为计算机的硬件指令非常依赖地址,指针在某种程度上把程序员想要传达的指令以更接近机器的方式表达。因此,使用指针的程序更有效率。尤其是,指针能有效地处理数组。我们很快就会学到,数组表示法其实是在变相地使用指针。
我们举一个变相使用指针的例子:数组名是数组首元素的地址。也就是说,如果flizny是一个数组,下面的语句成立:
flizny == &flizny[0]; // name of array is the address of the first element
flizny和&flizny[0]都表示数组首元素的内存地址(&是地址运算符)。两者都是常量,在程序的运行过程中,不会改变。但是,可以把它们赋值给指针变量,然后可以修改指针变量的值,如程序01所示。注意指针加上一个数时,它的值发生了什么变化(转换说明%p通常以十六进制显示指针的值)。
// pnt_add.c -- pointer addition #include <stdio.h> #define SIZE 4 int main(void) { short dates [SIZE]; short * pti; short index; double bills[SIZE]; double * ptf; pti = dates; // assign address of array to pointer ptf = bills; printf("%23s %15sn", "short", "double"); for (index = 0; index < SIZE; index ++) printf("pointers + %d: %10p %10pn", index, pti + index, ptf + index); return 0; }
输出结果为:
: short double
: pointers + 0: 0x7ffd793b8568 0x7ffd793b8570
: pointers + 1: 0x7ffd793b856a 0x7ffd793b8578
: pointers + 2: 0x7ffd793b856c 0x7ffd793b8580
: pointers + 3: 0x7ffd793b856e 0x7ffd793b8588
第2行打印的是两个数组开始的地址,下一行打印的是指针加1后的地址,以此类推。注意,地址是十六进制的,因此dd比dc大1,a1比a0大1。但是,显示的地址是怎么回事?
0x7fff5fbff8dc + 1 is 0x7fff5fbff8de?
0x7fff5fbff8a0 + 1 is 0x7fff5fbff8a8?
我们的系统中,地址按字节编址,short类型占用2字节,double类型占用8字节。在C中,指针加1指的是增加一个存储单元。对数组而言,这意味着加1后的地址是下一个元素的地址,而不是下一个字节的地址(见图1)。这是为什么必须声明指针所指向对象类型的原因之一。只知道地址不够,因为计算机要知道存储对象需要多少字节(即使指针指向的是标量变量,也要知道变量的类型,否则*pt就无法正确地取回地址上的值)。
图01:An array and pointer addition.
现在可以更清楚地定义指向int的指针、指向float的指针,以及指向其他数据对象的指针。
指针的值是它所指向对象的地址。地址的表示方式依赖于计算机内部的硬件。许多计算机(包括PC和Macintosh)都是按字节编址,意思是内存中的每个字节都按顺序编号。这里,一个较大对象的地址(如double类型的变量)通常是该对象第一个字节的地址。在指针前面使用*运算符可以得到该指针所指向对象的值。指针加1,指针的值递增它所指向类型的大小(以字节为单位)。下面的等式体现了C语言的灵活性:
dates + 2 == &date[2] // same address *(dates + 2) == dates[2] // same value
以上关系表明了数组和指针的关系十分密切,可以使用指针标识数组的元素和获得元素的值。从本质上看,同一个对象有两种表示法。实际上,C语言标准在描述数组表示法时确实借助了指针。也就是说,定义ar[n]的意思是*(ar + n)。可以认为*(ar + n)的意思是“到内存的ar位置,然后移动n个单元,检索存储在那里的值”。
顺带一提,不要混淆 *(dates+2)和*dates+2。间接运算符(*)的优先级高于+,所以*dates+2相当于(*dates)+2:
*(dates + 2) // value of the 3rd element of dates *dates + 2 // 2 added to the value of the 1st element
明白了数组和指针的关系,便可在编写程序时适时使用数组表示法或指针表示法。运行程序清单10.9后输出的结果和程序02输出的结果相同。
/* 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)); // same as days[index] return 0; }
这里,days是数组首元素的地址,days + index是元素days[index]的地址,而*(days +index)则是该元素的值,相当于days[index]。for循环依次引用数组中的每个元素,并打印各元素的内容。
这样编写程序是否有优势?不一定。编译器编译这两种写法生成的代码相同。程序清单02要注意的是,指针表示法和数组表示法是两种等效的方法。该例演示了可以用指针表示数组,反过来,也可以用数组表示指针。在使用以数组为参数的函数时要注意这点。
还没有评论,来说两句吧...