一維數組和指針具有如第一篇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 1245036 2 1245040 1245040 3 1245044 1245044 4 1245048 1245048 4 1245052 1245052 1245036 1245056 1245120 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 5 5 5 4 4 3 3 2 2 1 1 1
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 5 5 5 4 4 3 3 2 2 1 1 1 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