我们在日常练习中,经常会写这样的代码*p++,*p--,从这一点,我们就可以看出,指针是可以进行运算的,只不过指针可以进行的运算并不多。
我们知道,指针加上一个整数的结果是另一个指针,那么它指向哪里呢?如果将一个字符型指针加1,运算产生的结果是指向下一个字符指针。但如果是其他类型呢?如果是float类型呢?我们都知道,float所占据的内存空间并不是1,那么给一个指向float类型的指针加上1,它会指向哪里呢?
事实上,当一个指针与一个整数进行算术运算时,整数在执行加法运算前会根据合适的大小进行调整。这个合适的大小就是指针所指向类型的大小,调整就是把整数与合适的大小相乘。比如说,在某台机器上,float在内存中占4个字节,给float指针加上3,这个3将根据float类型的大小进行调整即相乘,这样,实际加到指针上的整型值为12。
下面以一个整型指针为例:
<span style="font-size:12px;"><span style="font-size:12px;"><span style="font-size:12px;">#include<stdio.h> #include<string.h> int main() { int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int *p = arr; printf("%d\n", *p); printf("%d\n", *p+3); system("pause"); return 0; }</span></span></span>
从这个例子中我们可以看出,指针在进行加法运算确实是由“调整”这一过程的,这里的加法跳过的是一个整型类型的大小,也就是说*p+3跳过了3个整型类型的大小即12个字节。
一.算数运算
C的指针算数运算只限于两种形式。
第一种形式是:指针 +/- 整数
标准定义这种形式只能用于指向数组中某个元素的指针,并且这种表达式的结果也是个指针。
数组中的元素存储于连续的内存位置中,后面元素的地址大于前面元素的地址。因此对一个指针加1使它指向数组中下一个元素,减2使它向左移动2个位置。对指针进行加减法运算后,如果指针所指向的位置在数组第一个元素的前面或最后一个元素的后面,这种现象是未定义的,让指针指向数组最后一个元素的后面的那个位置是合法的,但对其进行间接访问可能会失败。
第二种形式是:指针 - 指针
只有当两个指针都指向同一个数组中的元素时,才允许一个指针减去另一个指针。
两个指针相减的结果的类型是ptrdiff_t,它是一种有符号整数类型,减法运算的值两个数组元素在内存中的距离(不是以字节为单位),减法运算的结果将除以数组元素类型的长度。例如,p1指向arry[i],p2指向arry[j],那么p1-p2记得结果就是i-j。这个过程我们可以类比加法中的“调整”,假定arry是整型数组,起始位置为1000,p1的值为1004,p2的值为1024,那么p2-p1的结果是5,两个指针的差值(1024-1004=20)除以每个元素的长度(4)。
二.关系运算
对指针进行关系运算也是有限制的,用下列关系运算符对指针进行比较是可能的。
< <= > >=
前提是它们都指向同一个数组中的元素。根据你使用的操作符,比较表达式将告诉你那个指针指向数组中更前或更后的元素,标准并未定义两个任意的指针比较会产生什么样的结果。
但是,你可以在两个任意的指针之间进行相等或不相等的的测试,因为这类比较的结果和编译器选择在何处存储数据并无关系,指针要么指向同一个地址,要么指向不同的地址。
观察以下程序,它用于清除一个数组中的元素。
#define N_VALUES 5
float values[N_VALUES];
float *vp;
for (vp = &values[0]; vp < &values[N_VALUES];)
*vp++ = 0;
这个程序是合法的,vp和指针常量都指向同一数组中的元素,指针常量指向数组最后一个元素后面的那个位置,而在最后一次比较时,vp也指向最后一个元素的后面的位置,但我们并未对其进行间接引用操作,所以是安全的,我们也可以用!=操作符来代替<,因为vp未达到最后一个值,所以表达式的结果总是假的。
再来看下面这个循环,它与上面循环的功能是一样的,都是清除数组中的元素,不过,它是从后往前清除的。
#define N_VALUES 5;
float values[N_VALUES];
float *vp;
for (vp = &values[N_VALUES; vp>values[0];)
*- -vp = 0;
vp开始指向的是数组最后一个元素后面的那个位置,不过我们再对其进行解引用之前先让vp自减了,所以是安全的,而最后一次循环结束时,也是在将values[0]清零之后的。
但是,我们有时并不习惯写上面的代码,觉得其可读性太差,将其进行“简化”,如下面的循环:
#define N_VALUES 5;
float values[N_VALUES];
float *vp;
for (vp = &values[N_VALUES-1; vp>=&values[0];vp- -)
*vp = 0;
事实上,这个程序是有问题的,在数组第一个元素也就是values[0]被清除之后,vp的值还将减去1,那下一次循环判断的条件是数组第一个元素之前的位置与&values[0]的比较,而此时 vp>=&values[0]的值时未定义的,因为vp移到了边界之外。标准允许指向数组元素的指针与指向数组最后一个元素后面那个内存位置的指针进行比较,但不允许与指向数组第一个元素之前的那个内存位置的指针进行比较。
注:文章参考文献:《C和指针》。