一维数组和指针具有如第一篇5.4节表5-1所示的关系。但要注意不要用错。
18.1.1 使用数组偏移量造成数组越界
【例18.1】有如下程序:
#include <stdio.h>int main( ){ int i, a={1,2,3,4},*p=a; for ( i=0; i<5; i++,++p ) printf(/"%dt%ut%un/",*(a+i),a+i,p); printf(/"%ut%u n/",a,p); printf(/"%dt%dt%dt%dn/",*(a+5),a+5,p,*p); return 0;}
编译没有出错信息。输出结果如下:
1 1245036 12450362 1245040 12450403 1245044 12450444 1245048 12450484 1245052 12450521245036 12450561245120 1245056 1245056 1245120
找出程序中的错误。
其实,C语言中一维数组的大小必须在编译期间确定下来。也就是说,在定义数组时,数组的大小就是一个确定的常数。即使用语句
int a={1,2,3,4};
定义数组,在编译时也会将数组的大小确定下来(这里数组的大小为4),不允许再变动。
数组的下标是从0开始到4结束。所以循环语句的i应使用“i<4”,最后一个有效的数组元素是a[3],输出语句超出边界,而p则越界两个元素的存储地址。第5行的输出都是第1次越界的信息。这时,指针还要执行一次加1操作,所以它的指向是1245056,而a是数组名,所以仍然是存储数组的首地址,也就是a[0]的存储首地址1245036,这就是第6行的输出内容。
将a执行a+5,从而验证了它和p的内容一样,而*(a+5)则和*p的一致,这就是第7行的输出。
由此可见,必须知道数组的边界,如果越界,就会像指针越界一样,造成错误甚至使系统崩溃。
由以上分析知,应删除最后一个输出语句并将循环改为如下形式:
for ( i=0; i<4; i++, ++p )
18.1.2 使用数组名进行错误运算
【例18.2】找出下面程序的错误。
#include <stdio.h>int main( ){ int i, a={1,2,3,4,5},*p=a; p=a; for ( i=0; i<5; i++,++a,++p) printf(/"%d %d /",*a,*p); printf(/"n/"); p=&a[4]; for ( i=0; i<5; i++,--p) printf(/"%d %d /",*(a-i),*p); printf(/"n/"); a+2; printf(/"%dn/",*a); return 0;}
错在混淆了数组和指针。对指针p来说,它可以是--p和++p。但对数组来说,a是数组名,始终代表数组存储的首地址。它虽然也相当于指针,但只是用来表示指向数组存储首地址的指针,本身不能作为左值,即“a=a+1”和“a=a-1”都是错误的。至于表达式“*(a+i)”,只是取“a[i]”数组的内容,i出现在表达式a+i中,只是表示相对a的地址偏移量,a的值并没有变化,所以是正确的。这个循环语句可以修改为:
for ( i=0; i<5; i++,++p) printf(/"%d %d /",*(a+i),*p);
显然,第2个循环语句也是错的。a始终是数组名,所以a-1就越界了。从后面反序输出的起始数组是a[4],地址是&a[4],所以偏移量-i,正确的形式为:
for ( i=0; i<5; i++,--p) printf(/"%d %d /", *(&a[4]-i), *p);
语句“a+2;”是无意义的,对程序运行的结果没有影响,但编译系统给出警告信息。
//改正后的完整程序
#include <stdio.h>int main( ){ int i, a={1,2,3,4,5},*p=a; p=a; for ( i=0; i<5; i++,++p) printf(/"%d %d /",*(a+i),*p); printf(/"n/"); p=&a[4]; for ( i=0; i<5; i++,--p) printf(/"%d %d /",*(&a[4]-i),*p); printf(/"n/"); printf(/"%dn/",*a); return 0;}
程序运行结果如下:
1 1 2 2 3 3 4 4 5 55 5 4 4 3 3 2 2 1 11
18.1.3 错误使用数组下标和指向数组指针的下标
【例18.3】找出下面程序的错误。
#include <stdio.h>int main( ){ int i, a={1,2,3,4,5},*p=a; p=a; for ( i=0; i<5; i++) printf(/"%d %d /", a[i], p[i]); printf(/"n/"); p = &a[4]; for ( i=0; i<5; i++) printf(/"%d %d /", *a[4-i], p[4-i]); printf(/"n/"); printf(/"%d %dn/",*a,*p); return 0;}
第1个循环没有问题。第2个循环的关键是它们起始的下标。执行语句
p=&a[4];
之后,对指针而言,p[0]对应的是a[4],而p[1]则越界。p[-1]是a[3]。所以它的下标的计算方法是错的,应该使用p[-i]。a的表示方法与指针不一样,使用a[4-i]是正确的。计算时一定要注意,C语言的数组下标是从0开始。这里的错误是把a[4-i]误认为数组元素的指针,其实这里是标准的数组表示方法,不能使用“*”号。
执行完循环语句之后,p本身的值没有发生变化,仍然指向最后一个数组元素a[4],所以*p是最后一个数组元素的值,而a始终是数组名,*a就是第1个数组元素的值。
//修改后的正确程序#include <stdio.h>int main( ){ int i, a={1,2,3,4,5},*p=a; p=a; for ( i=0; i<5; i++) printf(/"%d %d /",a[i],p[i]); printf(/"n/"); p=&a[4]; for ( i=0; i<5; i++) printf(/"%d %d /",a[4-i],p[-i]); printf(/"n/"); printf(/"%d %dn/",*a,*p); return 0;}
程序运行结果如下:
1 1 2 2 3 3 4 4 5 55 5 4 4 3 3 2 2 1 11 5
18.1.4 小结
从上面几个例子可以看出,使用数组和指针是相辅相成的,如果设计得好,能使程序简洁有效,达到事半功倍的效果。
1.不对称边界
C语言数组a[n]共有n个有效元素,其下标从0开始(这是有效元素的下标),至n结束,但n不是数组的有效元素,而是它不能达到的上界。有效上界是n-1。
元素个数=n-1-0+1=n
这就带来一个便利,声明数组时就给出了数组的个数,例如double b[10]就是具有10个实数的数组。而n是不可能达到的上界,区间为[0,10)。而在循环输出或赋值时,循环值小于这个n值,从而使计算简化为:
元素个数=n
对定义的数组a[n]而言,a[i](包括元素a[i])前面有i个元素,后面有n-i个元素,一共有n个元素。
2.指针的下标
a是数组的名字,也就是指向数组存储首地址的指针,a[0]是起点,a[-1]越界,下标不能为负值。
如果定义一个指向数组的指针p,则p的下标可正可负。P[0]就是初始化指针指向的数组元素,p[-1]越界。如果执行
p=&a[2];
语句,则p[0]=a[2],p[-1]=a[1],p[1]=a[3]。简言之,大于0是数组从该元素开始的正序,小于0是逆序。指针就像一朵云,可以到处飘荡,指针使用稍有不慎,就会出错。
3.灵活运用这些特征
编程中利用这些特征既可提高效率,又可避免错误。
【例18.4】有数组a[20]的值分别为:
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
现在编制了如下程序,目的是把前10个的值修改为
1 2 3 4 5 6 7 8 9 10
并把这十个值输出以检查程序是否正确。下面的程序对吗?
#include <stdio.h>int main ( ){ int *p, i, a[20]; p = a; for (i=0; i<20; i++) a[i]=11+i; for (i=1; i<=10; i++,*p++) *p=i; for (i=0; i<10; i++, p++) printf(/"%4d/", *p); printf(/"n/"); return 0;}
【解答】不对。对p操作会改变指向,但这是必要条件,不是充分条件。所以不要以为必须对p操作才会改变指向。指针变量的移动,使指针指向的地址也同步变化,即
for (i=1; i<=10; i++,*p++) *p=i;
语句“*p++”的作用与“p++”一样,都移动了指针的指向。由此可见,在读入数据时,指针变量已经指向数组a[20]的第十一个元素的地址,即a[10]的地址。所以输出结果是
21 22 23 24 25 26 27 28 29 30
应先把指针的初始值回到&a[0],即把指针修改为指向a[0]。在输出之前简单地使用
p=a;
语句即可实现。正确的程序在最后两句之前增加一句,即:
p=a;for (i=0; i<10; i++, p++) printf(/"%4d/", *p);
实际上,直接使用偏移量的概念编制程序,因为不移动指针指向,实现起来就非常简单。下面是完整的程序。
#include <stdio.h>int main ( ){ int *p, i, a[20]; p = a; for (i=0; i<20; i++) a[i]=11+i; for (i=0; i<=10; i++) *(p+i)=1+i; for (i=0; i<10; i++) printf(/"%4d/", *(p+i)); printf(/"n/"); return 0;}
程序输出结果如下:
1 2 3 4 5 6 7 8 9 10