(十六) 陣列和指標
指標和陣列有著密切的關係,任何能由陣列下標完成的操作也都可用指標來實現,但程式中使用指標可使程式碼更緊湊、更靈活。
一、指向陣列元素的指標
我們定義一個整型陣列和一個指向整型的指標變數:
int a[10], *p;
和前面介紹過的方法相同,可以使整型指標p指向陣列中任何一個元素,假定給出賦值運算
p=&a[0];
此時,p指向陣列中的第0號元素,即a[0],指標變數p中包含了陣列元素a[0]的地址,由於陣列元素在記憶體中是連續存放的,因此,我們就可以透過指標變數p及其有關運算間接訪問陣列中的任何一個元素。
Turbo C中,陣列名是陣列的第0號元素的地址,因此下面兩個語句是等價的
p=&a[0];
p=a;
根據地址運算規則,a+1為a[1]的地址,a+i就為a[i]的地址。
下面我們用指標給出陣列元素的地址和內容的幾種表示形式:
(1). p+i和a+i均表示a[i]的地址, 或者講,它們均指向陣列第i號元素, 即指向a[i]。
(2). *(p+i)和*(a+i)都表示p+i和a+i所指物件的內容,即為a[i]。
(3). 指向陣列元素的指標, 也可以表示成陣列的形式,也就是說,它允許指標變數帶下標, 如p[i]與*(p+i)等價。
假如: p=a+5;
則p[2]就相當於*(p+2), 由於p指向a[5], 所以p[2]就相當於a[7]。而p[-3]就相當於*(p-3), 它表示a[2]。
二、指向二維陣列的指標
1.二維陣列元素的地址
為了說明問題, 我們定義以下二維陣列:
int a[3][4]={{0,1,2,3}, {4,5,6,7}, {8,9,10,11}};
a為二維陣列名,此陣列有3行4列, 共12個元素。但也可這樣來理解,陣列a由三個元素組成:a[0],a[1],a[2]。而每個元素又是一個一維陣列, 且都含有4個元素(相當於4列),例如,a[0]所代表的一維陣列所包含的 4 個元素為a[0][0], a[0][1], a[0][2], a[0][3]。如圖所示:
______ _______________
a---| a[0] | ____ | 0 | 1 | 2 | 3 |
|______| |___|___|___|___|
| a[1] | ____ | 4 | 5 | 6 | 7 |
|______| |___|___|___|___|
| a[2] | ____ | 8 | 9 | 10| 11|
|______| |___|___|___|___|
但從二維陣列的角度來看,a代表二維陣列的首地址,當然也可看成是二維陣列第0行的首地址。a+1就代表第1行的首地址,a+2就代表第2行的首地址。如果此二維陣列的首地址為1000,由於第0行有4個整型元素,所以a+1為1008,a+2也就為1016。如圖所示
_______________
(1000) ____ | 0 | 1 | 2 | 3 |
|___|___|___|___|
(1008) ____ | 4 | 5 | 6 | 7 |
|___|___|___|___|
(1016) ____ | 8 | 9 | 10| 11|
|___|___|___|___|
既然我們把a[0],a[1],a[2]看成是一維陣列名,可以認為它們分別代表它們所對應的陣列的首地址,也就是講,a[0]代表第 0 行中第 0 列元素的地址,即&a[0][0], a[1]是第1行中第0列元素的地址,即&a[1][0],根據地址運算規則,a[0]+1即代表第0行第1列元素的地址,即&a[0][1],一般而言,a[i]+j即代表第i行第j列元素的地址, 即&a[i][j]。
另外,在二維陣列中,我們還可用指標的形式來表示各元素的地址。如前所述,a[0]與*(a+0)等價,a[1]與*(a+1)等價,因此a[i]+j就與*(a+i)+j等價,它表示陣列元素a[i][j]的地址。
因此,二維陣列元素a[i][j]可表示成*(a[i]+j)或*(*(a+i)+j),它們都與a[i][j]等價,或者還可寫成(*(a+i))[j]。
另外, 要補充說明一下, 果你編寫一個程式輸出列印a和*a,你可發現它們的值是相同的,這是為什麼呢? 我們可這樣來理解:
首先,為了說明問題,我們把二維陣列人為地看成由三個陣列元素a[0],a[1],a[2]組成,將a[0],a[1],a[2]看成是陣列名它們又分別是由4個元素組成的一維陣列。因此,a表示陣列第0行的地址, 而*a即為a[0], 它是陣列名, 當然還是地址,它就是陣列第0 行第0 列元素的地址。
2.指向一個由n個元素所組成的陣列指標
在Turbo C中, 可定義如下的指標變數:
int (*p)[3];
指標p為指向一個由3個元素所組成的整型陣列指標。在定義中,圓括號是不能少的, 否則它是指標陣列, 這將在後面介紹。這種陣列的指標不同於前面介紹的整型指標,當整型指標指向一個整型陣列的元素時,進行指標(地址)加1運算,表示指向陣列的下一個元素,此時地址值增加了2(因為放大因子為2),而如上所定義的指向一個由3個元素組成的陣列指標,進行地址加1運算時,其地址值增加了6(放大因子為2x3=6),
這種陣列指標在Turbo C中用得較少,但在處理二維陣列時, 還是很方便的。例如:
int a[3][4], (*p)[4];
p=a;
開始時p指向二維陣列第0行,當進行p+1運算時,根據地址運算規則,此時放大因子為4x2=8,所以此時正好指向二維陣列的第1行。和二維陣列元素地址計算的規則一樣,*p+1指向a[0][1],*(p+i)+j則指向陣列元素a[i][j]。
例:
int a[3][4]={
{1,3,5,7},
{9,11,13,15},
{17,19,21,23}
};
main()
{
int i,(*b)[4];
b=a+1; /* b指向二維陣列的第1行, 此時*b[0]是a[1][0] */
for(i=1;i<=4;b=b[0]+2,i++) /* 修改b的指向, 每次增加2 */
printf(%d\t,*b[0]);
printf(\n);
for(i=0; i<3; i++)
{
b=a+i; /* 修改b的指向,每次跳過二維陣列的一行 */
printf(%d\t,*(b[i]+1));
}
printf (\n);
}
程式執行結果如下:
9 13 17 21
3 11 19
三、字元指標
我們已經知道,字串常量是由雙引號括起來的字元序列,例如:
a string
就是一個字串常量,該字串中因為字元a後面還有一個空格字元,所以它由8個字元序列組成。在程式中如出現字串常量C編譯程式就給字串常量安排一存貯區域,這個區域是靜態的,在整個程式執行的過程中始終佔用, 平時所講的字串常量的長度是指該字串的字元個數, 但在安排存貯區域時, C 編譯程式還自動給該字串序列的末尾加上一個空字元'\0',用來標誌字串的結束,因此一個字串常量所佔的存貯區域的位元組數總比它的字元個數多一個位元組。
Turbo C中操作一個字串常量的方法有:
(1).把字串常量存放在一個字元陣列之中, 例如:
char s[]=a string;
陣列s共有9個元素所組成,其中s[8]中的內容是'\0'。實際上,在字元陣列定義的過程中,編譯程式直接把字串複寫到陣列中,即對陣列s初始化。
(2).用字元指標指向字串,然後透過字元指標來訪問字串存貯區域。當字串常量在表示式中出現時,
根據陣列的型別轉換規則,它被轉換成字元指標。因此,若我們定義了一字元指標cp:
char *cp;
於是可用:
cp=a string;
使cp指向字串常量中的第0號字元a, 如圖所示。
___________________________________
CP ----- | a | | s | t | r | i | n | g | \0|
|___|___|___|___|___|___|___|___|___|
以後我們可透過cp來訪問這一存貯區域, 如*cp或cp[0]就是字元a,而cp[i]或*(cp+i)就相當於字串的第i號字元,但企圖透過指標來修改字串常量的行為是沒有意義的。
四、指標陣列
因為指標是變數,因此可設想用指向同一資料型別的指標來構成一個數組, 這就是指標陣列。陣列中的每個元素都是指標變數,根據陣列的定義,指標陣列中每個元素都為指向同一資料型別的指標。指標陣列的定義格式為:
型別標識 *陣列名[整型常量表達式];
例如:
int *a[10];
定義了一個指標陣列,陣列中的每個元素都是指向整型量的指標,該陣列由10個元素組成,即a[0],a[1],a[2], ..., a[9],它們均為指標變數。a為該指標陣列名,和陣列一樣,a是常量,不能對它進行增量運算。a為指標陣列元素a[0]的地址,a+i為a[i]的地址,*a就是a[0],*(a+i)就是a[i]。
為什麼要定義和使用指標陣列呢?主要是由於指標陣列對處理字串提供了更大的方便和靈活,使用二維陣列對處理長度不等的正文效率低,而指標陣列由於其中每個元素都為指標變數,因此透過地址運算來操作正文行是十分方便的。
指標陣列和一般陣列一樣,允許指標陣列在定義時初始化,但由於指標陣列的每個元素是指標變數,它只能存放地址,所以對指向字串的指標陣列在說明賦初值時,是把存放字串的首地址賦給指標陣列的對應元素,
例如下面是一個書寫函式month_name(n),函式返回一個指向包含第n個名字的字元指標(關於函式指標和指標函式,下一節將專門介紹)。
例: 列印1月至12月的月名:
char *month_name(int n)
{
static char *name[]={
Illegal month,
January,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December
};
return((n<1||n>12)?name[0]:name[n]);
}
main()
{
int i;
for(i=0; i<13; i++)
printf(%s\n, month_name(i));
}
對於指標這一節,一定要多練習一些題。指標是一個很重要的概念,必須多接觸實際的問題才能掌握它。