讀古今文學網 > C語言解惑 > 18.4 二維數組和指針 >

18.4 二維數組和指針

雖然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