如果两个指针向同一个数组,它们就可以相减,其为结果为两个指针之间的元素数目。仍以本章开头介绍的街道地址的比喻为例,假设我住在第五大街118号,我的邻居住在第五大街124号,每家之间的地址间距是2(在我这一侧用连续的偶数作为街道地址),那么我的邻居家就是我家往前第(124-118)/2(或3)家(我和我的邻居家之间相隔两家,即120号和122号)。指针之间的减法运算和上述方法是相同的。
在折半查找的过程中,同样会用到上述减法运算。假设p和q指向的元素分别位于你要找的元素的前面和后面,那么(q-p)/2+p指向一个位于p和q之间的元素。如果(q-p)/2+p位于你要找的元素之前,下一步你就可以在(q-p)/2+p和q之间查找要找的元素;反之,你可以停止查找了。
如果两个指针不是指向一个数组,它们相减就没有意义。假设有人住在梅恩大街110号,我就不能将第五大街118号减去梅恩大街110号(并除以2),并以为这个人住在我家往回第4家中。
如果每个街区的街道地址都从一个100的倍数开始计算,并且同一条街的不同街区的地址起址各不相同,那么,你甚至不能将第五大街204号和第五大街120号相减,因为它们尽管位于同一条街,但所在的街区不同(对指针来说,就是所指向的数组不同)。
C本身无法防止非法的指针减法运算,即使其结果可能会给你的程序带来麻烦,C也不会给出任何提示或警告。
指针相减的结果是某种整类型的值,为此,ANSIC标准<stddef.h>头文件中预定义了一个整类型ptrdiff_t。尽管在不同的编译程序中ptrdiff_t的类型可能各不相同(int或long或其它),但它们都适当地定义了ptrdiff_t类型。
例7.7演示了指针的减法运算。该例中有一个结构体数组,每个结构体的长度都是16字节。
如果是对指向结构体数组的指针进行减法运算,则a[0]和a[8]之间的距离为8;如果将指向结构体数组的指针强制转换成指向纯粹的内存地址的指针后再相减,则a[0]和aL8]之间的距离为128(即十六进制数0x80)。如果将指向a[8]的指针减去8,该指针所指向的位置并不是往前移了8个字节,而是往前移了8个数组元素。
注意:把指针强制转换成指向纯粹的内存地址的指针,通常就是转换成void *类型,但是,本例将指针强制转换成char *类型,因为void。类型的指针之间不能进行减法运算。
例 7.7 指针的算术运算
# include <stdio. h>
# include <stddef.h>
struct stuff {
char name[l6];
/* other stuff could go here, too */
};
struct stuff array [] = {
{ "The" },
{ "quick" },
{ "brown" >,
{ "fox" },
{ "jumped" },
{ "over" },
{ "the" },
{ "lazy" },
{ "dog. " },
/*
an empty string signifies the end;
not used in this program,
but without it, there'd be no way
to find the end (see FAQ IX. 4)
*/
{ " " }
};
main ( )
{
struct stuff * p0 = &.array[0];
struct stuff * p8 = &-array[8];
ptrdiff_t diff = p8-p0;
ptrdiff_t addr.diff = (char * ) p8 - (char * ) p0;
/*
cast the struct stuff pointers to void *
(which we know printf() can handles see FAQ VII. 28)
*/
printf ("&array[0] = p0 = %P\n" , (void* ) p0);
printf ("&. array[8] = p8 = %P\n" , (void* ) p8) ;
/*
cast the ptrdiff_t's to long's
(which we know printf () can handle)
*/
printf ("The difference of pointers is %ld\n" , (long) diff) ;
printf ("The difference of addresses is %ld\n" , (long) addr_diff);
printf ("p8-8 = %P\n" , (void*) (p8-8));
/* example for FAQ VII. 8 */
printf ("p0 + 8 = %P (same as p8)\n", (void* ) (p0 + 8));
return 0; /* see FAQ XVI. 4 */
}