9.3.2 通过指针引用数组元素

上面可以看到,把a[0]元素的地址赋给指针变量p,即p指向数组下标为0的元素(第0号元素)。现在请看下面一些语句的执行效果,请认真分析,仔细观察。

图9.17 指针变量p+1之后的内存布局示意图

(1)*p=19;

表示对p当前所指向的数组元素赋值19,也就是“a[0]=19;”,可以设置断点调试跟踪看一下。

(2)p=p+1;

C语言规定p+1并不是简单地将p值+1,具体加几取决于指针变量p的类型,如果指针变量p为整型,则因为整型占4字节内存,所以p+1相当于增加了4,例如p原来指向内存1000,则p+1后,p指向了1004。在这里,p指向数组下标为0的元素,p+1就使p指向了数组的下一个元素a[1]。内存布局示意图如图9.17所示。

(3)p+i;或a+i;

注意,这里i是数组元素的下标。

根据上一条,可以推出一个结论:假如现在p指向数组首地址,a是数组名(代表数组首地址),那么p+i或者a+i就是数组元素a[i]的地址,也就是说,它们指向数组a的第i个元素。例如,p+3和a+3的值都是&a[3],都指向a[3],它们的实际地址是a+i*4(一个int占4字节),所以,结论就是p+i或者a+i都是地址,那么既然是地址,就可以赋给指针变量,所以如下范例中的语句就完全没问题:

内存布局示意图如图9.18所示。内存布局方块左侧的都是地址,地址可以赋值给指针;右侧的都是值,值可以赋值给其他普通变量,也可以赋值给数组元素等。

(4)*(p+i);或*(a+i);

在理解了上一条之后,这里的内容就好理解了。这两个是p+i或者a+i所指向的数组元素,也就是a[i],例如*(p+2)或者*(a+2)就是a[2]。内存布局示意图如图9.19所示。

图9.18 p+i或者a+i内存布局示意图

图9.19 *(p+i)或者*(a+i)内存布局示意图

(5)p[i];

指向数组的指针变量p,可以带个下标,跟数组元素一样,如p[i],与*(p+i)等价,如果p指向数组a的首地址,那么p[i]就和a[i]等价了。内存布局示意图如图9.20所示。

图9.20 p[i]内存布局示意图

说到这里,引用一个数组元素,目前有如下几种方法,分别是a[i]、p[i]、*(p+i)、*(a+i)。

看看如下范例,读者可以自己设置断点跟踪分析,好好理解这些结果。

上面这个范例中,系统会将a[i]转换成*(a+i)处理,所以三个for循环语句中,前两个for循环语句找数组元素耗费的时间比第三个for循环语句要多,第三个for循环语句属于用指针变量直接指向元素,不用每次都重新计算地址,而且p++这种自增操作也比较快。

不过上面这个范例如果从直观性的角度来讲,还是第一个for循环语句最直观。其他两个for循环语句都不那么直观,都得分析分析才知道结果。

上面这个范例有几个注意事项说一下:

①上面第三个循环使用的是p++,这是不断改变指针变量自身的值,从而使指针变量指向不同的数组元素,这是合法的。但有没有人见过a++这种写法呢?这是不合法的,a是数组名,代表数组元素首地址,它自己不能不断自加,它的值是固定不变的,所以它不可以自加1,如果写成a++那么编译肯定会报错。

②定义了5个元素的数组,能引用的数组元素是a[0]~a[4]。上面范例第三个for循环中,因为p是自加的,整个for循环完成之后,p已经指向了整个数组后面的内存,虽然p指向了这段内存,但不代表能使用这段内存,因为这段内存已经跳过了整个数组,已经不属于能操作的内存范围了。就好像如果在程序中使用a[5],系统会把它按照*(a+5)来处理,也就是先找出(a+5)的值(是一个地址),然后找出它指向的单元的内容,这个地址存在,但这个地址不能去操作,也就是说,能操作的就是a[0]~a[4],其他的a[5]甚至a[6]、a[7]、a[8]等确实有对应的实际内存地址,但这些内存地址不能操作(例如不能去读,更不能去修改其中的内容),否则程序就可能会运行不稳定、出错,甚至运行崩溃。

(6)*p++;

在前面曾经说过,这里再进行一次详细和扩展说明。

自增运算符和指针运算符优先级相同,并且是自右至左结合的,所以等价于“*(p++);”,++在变量名p的后面表示先用后加,所以整个语句的作用是得到p指向的变量的值(*p),然后再使p指针自加1,指向下一个数组元素。看看如下范例:

那既然说到*p++;,就顺便再说一下“*++p;”,这相当于“*(++p);”。

*(p++)和*(++p)不同:

++在变量名p的后面,表示先用后加,++在变量名p的前面表示先加后用。整个的作用是先使p指针自加1,然后再使用p所指向的变量的值,所以如果原来p指向的是a[0],那么此时实际上输出的是a[1]的值,当然p也指向a[1]了。看看如下范例,只需要把上面范例中的printf语句行做如下改变:

(7) (*p) ++;

这条语句在前面也曾经说过,表示p所指向的元素加1,如果p指向数组首地址,那么就等价于“a[0]++;”。这里注意,实际上这个是元素值加1,不是指针值加1。看看如下范例,只需要把上面范例的printf语句行做如下改变:

上面(6)和(7)谈论了自加运算符++,其实自减运算符--也是一样的,只不过对于指针变量来讲,指针值-1,代表的不再是指针变量p往前指(指向下一个元素),而是往回指(指向前一个元素)。同时也看到,指针变量的书写形式灵活多样,用的时候一定要小心,各种写法不必要死记硬背,建议可以画一张图供随时查阅参考即可。