雖然C語言只有一維數組,但它的數組元素可以是任何類型的對象,這也包括是另外一個數組。因此,通過這個特性可以很容易「仿真」出一個多維數組。C語言一般很少使用多於三維的數組,最常用的是一維和兩維數組。所以這裡僅以二維數組為例。
18.4.1 二維數組的界限
假設二維數值數組a[m][n],其起點是a[0][0],上邊界是a[m][n]。越界就是m行n列。假設有指針p,指向首地址是「p=a[0]」或「p=&a[0][0]」,特別要預防這個設置錯誤。
【例18.9】先輸出二維數組中的全部元素,再輸出為負值的元素,最後按序號輸出全部元素。找出程序中的錯誤。
#include <stdio.h> int main ( ) { int a[3][3]={21 ,17 ,65 ,-96 ,-58 ,31 ,-99 ,-3 ,8} ; int i ,j ,k=0 ,*p=a[0] ; for ( i=0 ; i<9 ; i++ ) { if (i !=0&&i%3==0 ) printf (\"n\" ); printf (\"%4d\" ,p[i] ); } printf (\"n\" ); for ( i=0 ; i<9 ; i++ ) { if (i !=0&&i%3==0 ) printf (\"n\" ); if (* (p+i )<0 ) printf (\"%4d\" ,* (p+i )); } printf (\"n\" ); for ( i=0 ; i<3 ; i++ ){ for ( j=0 ; j<3 ; j++ ) printf ( \"a[%d][%d]=%4d \" ,i ,j ,* (p+i+j ) ); printf (\"n\" ); } return 0 ; }
可以像一維數組那樣使用指針的下標和偏移量,這時只需要一個for循環。這種方法的缺點是沒有數組的標識符號。如果要使用數組標識,則需要使用雙重for循環,這時使用
printf ( \"a[%d][%d]=%4d \" , i , j , a[i][j] );
語句輸出數組元素的值最直觀方便。
程序如果使用指針配合雙重循環語句輸出,則要換算偏移量。這個程序存在標號計算錯誤。i=1時,偏移量的計算與標號不對應,從如下程序的輸出可以看出它的問題。
21 17 65 -96 -58 31 -99 -3 8 -96 -58 -99 -3 a[0][0]= 21 a[0][1]= 17 a[0][2]= 65 a[1][0]= 17 a[1][1]= 65 a[1][2]= -96 a[2][0]= 65 a[2][1]= -96 a[2][2]= -58
在第2行,應該從3+j開始,第3行應從6+j開始。計算公式為3*i+j。這條語句修改為
printf ( \"a[%d][%d]=%4d \" ,i ,j ,* (p+i*3+j ) );
即可。
如果使用指針讀入數據,也要注意換算。使用二重for循環,在指針變量指向數組首地址之後,引用該數組第i行第j列的元素的方法如下:
*(指針變量+i*列數+j)
使用scanf賦值時,需要使用地址。相應地址的表示方法如下:
(指針變量+i*列數+j)
在指針變量指向數組尾地址之後,引用該數組第i行第j列的元素的方法如下:
*(指針變量-i*列數-j)
使用scanf賦值時,需要使用地址。相應地址的表示方法如下:
(指針變量-i*列數-j)
【例18.10】下面的程序對數組a採用正序讀入和輸出,對數組b採用反序讀入和輸出,找出程序中的錯誤。
#include <stdio.h> int main ( ) { int i ,j ; double a[2][3] ,b[2][3] ,*p=a[0] ; printf (\" 輸入數組a :\" ); for ( i=0 ; i<2 ; i++ ) for ( j=0 ; j<3 ; j++ ) scanf (\"%lf\" ,(p+i*3+j ) ); for ( i=0 ; i<2 ; i++ ) for ( j=0 ; j<3 ; j++ ) printf (\"%lf \" ,* (p+i*3+j ) ); printf (\"n\" ); printf (\" 輸入數組b :\" ); p=&b[2][3] ; for ( i=0 ; i<2 ; i++ ) for ( j=0 ; j<3 ; j++ ) scanf (\"%lf\" ,(p-i*3-j ) ); for ( i=0 ; i<2 ; i++ ) for ( j=0 ; j<3 ; j++ ) printf (\"%lf \" ,* (p-i*3-j ) ); printf (\"n\" ); return 0 ; }
數組b的最後一個元素是b[1][2],不是b[2][3]。將指針初始化改為
p=&b[1][2] ;
即可。程序運行示範如下:
輸入數組a : 1.1 2.2 3.3 4.4 5.5 6.6 1.100000 2.200000 3.300000 4.400000 5.500000 6.600000 輸入數組b : 1.11 2.22 3.33 4.44 5.55 6.66 1.110000 2.220000 3.330000 4.440000 5.550000 6.660000
結論:m行n列的二維數組是從0行0列到m-1行n-1列。元素個數是m×n個,其界限是處於m行和n列上的位置。
這與數學上的行列式定義不一樣,要特別注意,以免越界。
18.4.2 二維數組的一維特性
因為二維數組是在一維數組的基礎上構造的,所以下標是連續的,可以直接使用一維數組的方式讀入和輸出數據。關鍵問題與一維數組一樣,就是不要混淆0號單元的標識。
【例18.11】編寫程序使用兩種方法為二維數組中的部分元素賦值。
假設實數數組a[2][3]和b[2][3],對a數組使用指針偏移量讀入數據,然後正序和反序輸出其內容。因為沒有移動指針,所以指針的下標是正數。對b數組使用指針偏移量讀入數據,然後將指針調整到p[0]處,使用負下標正序和反序輸出其內容。
// 完整的程序 #include <stdio.h> int main ( ) { int i ; double a[2][3] ,b[2][3] ,*p=a[0] ; printf (\" 輸入數組a :\" ); for ( i=0 ; i<6 ; i++ ) scanf (\"%lf\" ,p+i ); for ( i=0 ; i<6 ; i++ ) { if (i !=0&&i%3==0 ) printf (\"n\" ); printf (\"%lf \" ,p[i] ); } printf (\"n\" ); for ( i=5 ; i>-1 ; i-- ) { printf (\"%lf \" ,p[i] ); if (i%3==0 ) printf (\"n\" ); } printf (\"n\" ); printf (\" 輸入數組b :\" ); p=b[0] ; for ( i=0 ; i<6 ; i++ ,p++ ) scanf (\"%lf\" ,p ); for ( --p ,i=0 ; i>-6 ; i-- ) { if (i !=0&&i%3==0 ) printf (\"n\" ); printf (\"%lf \" ,p[i] ); } printf (\"n\" ); for ( i=-5 ; i<1 ; i++ ) { printf (\"%lf \" ,p[i] ); if (i%3==0 ) printf (\"n\" ); } printf (\"n\" ); return 0 ; }
程序運行示範如下:
輸入數組a : 1.1 2.2 3.3 4.4 5.5 6.6 1.100000 2.200000 3.300000 4.400000 5.500000 6.600000 6.600000 5.500000 4.400000 3.300000 2.200000 1.100000 輸入數組b : 1.11 2.22 3.33 4.44 5.55 6.66 6.660000 5.550000 4.440000 3.330000 2.220000 1.110000 1.110000 2.220000 3.330000 4.440000 5.550000 6.660000
與一維數組一樣,千萬不能混淆p[0]。
【例18.12】使用一維數組的讀寫方法,演示二維數組的賦值和輸出。
這個程序不使用雙重循環,直接使用一維數組的方式讀入和輸出數據。只要注意到這時a數組的存儲首地址是a[0],就可以很容易寫出它們的程序。
#include <stdio.h> int main ( ) { int i ; double a[2][3] ; printf (\" 輸入數組a :\" ); for ( i=0 ; i<6 ; i++ ) scanf (\"%lf\" ,a[0]+i ); for ( i=0 ; i<6 ; i++ ) { if (i !=0&&i%3==0 ) printf (\"n\" ); printf (\"%lf \" ,* (a[0]+i )); } printf (\"n\" ); for ( i=5 ; i>-1 ; i-- ) { printf (\"%lf \" ,* (a[0]+i )); if (i%3==0 ) printf (\"n\" ); } printf (\"n\" ); return 0 ; }
程序示範運行如下:
輸入數組a : 1.1 2.2 3.3 4.4 5.5 6.6 1.100000 2.200000 3.300000 4.400000 5.500000 6.600000 6.600000 5.500000 4.400000 3.300000 2.200000 1.100000
由此可見,如果不需要輸出數組下標,直接使用一維數組的形式進行操作,反而簡單。
18.4.3 指向二維數組的指針
上面都是使用普通的指針指向數組,所以產生連續的標識運算。通過下面的例子可以比較幾種方法的優缺點。
【例18.13】引入指向二維數組的一維指針概念。
對於二維數組a[3][5],固定首行地址,移動列序號得到如下對應關係:
a[0]+j j=0,1,2,3,4 *(a[0]+j)遍歷第0行,i=0,j=0~4
a[1]+j j=0,1,2,3,4 *(a[1]+j)遍歷第1行,i=1,j=0~4
a[2]+j j=0,1,2,3,4 *(a[2]+j)遍歷第2行,i=2,j=0~4
顯然,這些表達式比用*(a[0]+i*5+j)的含義明確。
如果將a[i]使用一維指針p[i]表示,顯然有:
p[0]+j j=0,1,2,3,4 *(p[0]+j)遍歷第0行,i=0,j=0~4
p[1]+j j=0,1,2,3,4 *(p[1]+j)遍歷第1行,i=1,j=0~4
p[2]+j j=0,1,2,3,4 *(p[2]+j)遍歷第2行,i=2,j=0~4
當j固定,則按列輸出。以第1列為例,則有
a[i]+1 i=0,1,2 *(a[i]+1)遍歷第1列,i=0~2
p[i]+1 i=0,1,2 *(p[i]+1)遍歷第1列,i=0~2
按此方法,讀者可以自行給出其他4列的表示方法。
如果使用i行和j列表示,則有:*(*(p+i)+j)。顯然前者含義較準確。
假設語句
int *p ;
聲明的是整型指針變量。一維數組使用
p=a ;
的格式。而
p=a[0] ;
是二維數組首地址。指向二維數組的一維數組指針的格式與二維數組的列數有關。假設二維數組的列數為m,應聲明為
int (*p )[m] ;
指向二維數組首地址的格式與一維數組的一樣,即
p=a ;
下面的程序比較幾種輸出方式,編程時可以根據實際情況靈活選擇。
#include <stdio.h> int main ( ) { int i ,j ; int a[3][5] ; int (*p )[5] ; // 聲明一維指針 for ( i=0 ; i<15 ; i++ ) * (a[0]+i )=i+10 ; // 直接使用數組首地址 // 注意不要錯為a for ( i=0 ; i<3 ; i++ ){ for ( j=0 ; j<5 ; j++ ) printf (\"%d \" ,* (a[0]+i*5+j ) ); // 換算 printf (\"n\" ); } printf (\"n\" ); for ( i=0 ; i<3 ; i++ ){ for ( j=0 ; j<5 ; j++ ) printf (\"%d \" ,* (a[i]+j ) ); // 使用a[i] 形式 printf (\"n\" ); } printf (\"n\" ); p=a ; // 指向二維數組首地址 //p 指向a 的第一個元素,也就是數組a 的3 個有著5 個元素的 // 數組類型元素之一 for ( i=0 ; i<3 ; i++ ){ for ( j=0 ; j<5 ; j++ ) printf (\"%d \" ,* (p[i]+j ) ); // 指針下標 printf (\"n\" ); } printf (\"n\" ); for ( i=0 ; i<3 ; i++ ){ for ( j=0 ; j<5 ; j++ ) printf (\"%d \" ,* (* (p+i )+j ) ); // 換算 printf (\"n\" ); } // 按列輸出 for ( j=0 ; j<5 ; j++ ){ for ( i=0 ; i<3 ; i++ ) printf (\"%d \" ,* (p[i]+j ) ); // 指針下標 printf (\"n\" ); } printf (\"n\" ); return 0 ; }
程序運行輸出結果如下:
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 10 15 20 11 16 21 12 17 22 13 18 23 14 19 24
對二維字符串來說,專業的使用方法就是直接使用列,所以與定義一個一維字符指針的用法一樣。區別是字符串數組能將一行字符串作為整體輸出。
【例18.14】找出下面程序中的錯誤。
#include <stdio.h> #include <string.h> int main ( ) { int i ,a[5]={85 ,80 ,88 ,98 ,80} ; char c[5][5] ; char *p ; // 賦值 strcpy (c[0] ,\" 數學\" ); strcpy (c[1] ,\" 物理\" ); strcpy (c[2] ,\" 外語\" ); strcpy (c[3] ,\" 政治\" ); strcpy (c[4] ,\" 體育\" ); p=c[0] ; for (i=0 ;i<5 ;i++ ) printf (\"%s :%dn\" ,p[i] ,* (a+i )); return 0 ; }
從二維數值數組的使用方法來看。這裡好像沒有錯誤。其實仔細想一想就會發現問題。字符指針加1,是移動存儲一個字符的位置。這裡每個字符串是4位,連結束符在內,共佔5個字節,所以指針要移動5個位置,才能到第2個字符串。將循環語句改為
for (i=0 ; i<5 ; i++ , p=p+5 ) printf (\"%s :%dn\" , p , * (a+i ));
一般的二維字符數組的字符串長度並不相等,本程序的方法也就失效了。由此可見,使用字符變量指針不適合二維字符串數組。
其實,聲明
char c[5][5] ;
就隱含一維字符串指針
char (* )[5] ;
下面使用一維字符指針編製這個程序。程序中也給出使用數組名指針的方法,以便對照理解。
#include <stdio.h> #include <string.h> int main ( ) { int i ,a[5]={85 ,80 ,88 ,98 ,80} ; char c[5][5] ; char (*p )[5] ; // 賦值 strcpy (c[0] ,\" 數學\" ); strcpy (c[1] ,\" 物理\" ); strcpy (c[2] ,\" 外語\" ); strcpy (c[3] ,\" 政治\" ); strcpy (c[4] ,\" 體育\" ); for (i=0 ;i<5 ;i++ ) // 使用數組名c 的下標 printf (\"%s :%dn\" ,c[i] ,* (a+i )); printf (\"n\" ); p=c ; for (i=0 ;i<5 ;i++ ) // 使用一維字符指針的下標 printf (\"%s :%dn\" ,p[i] ,* (a+i )); return 0 ; }
運行結果如下:
數學:85 物理:80 外語:88 政治:98 體育:80 數學:85 物理:80 外語:88 政治:98 體育:80