**叁**
***數組與指針(一)***
指針是C的精華,如果未能很好地掌握指針,那C也基本等于沒學。
關于指針、數組、字符串,本人當年也是有過一段“慘絕人寰”的痛。好在多看書,多思考,多總結,多實踐,方才有些心得。現在把當年的筆記摘錄如下,希望能給初學者一些啟發。
**先附上兩句話:**
**第一句話:指針就是存放地址的變量。(就是這么簡單。)**
**第二句話:指針是指針,數組是數組。(只是它們經常穿著相似的衣服來逗你玩罷了。)**
*輕松一下*:(見識一下數組和指針的把戲)
1、引用一維數組某個值的方式:(先定義指針p=a)
① a[2] ? ② *(a+2) ?③ (&a[1])[1] ? ④ *(p+2) ?⑤ p[2]
2、引用二維數組某個值的方式:
~~~
例:int a[4][5];
⑴ a[i][j]
⑵ *(a[i]+j)
⑶ *(*(a+i)+j)
⑷ (*(a+i))[j]
⑸ *(&a[0][0]+i*5+j)
又定義:int * p[4], m ;
for(m=0; m<4;m++) p[m] = a[m] ;
⑹ p[i][j]
⑺ *(p[i]+j)
⑻ *(*(p+i)+j)
⑼ (*(p+i))[j] //請與⑴-⑷對比
又定義 int (*q)[5]; q=a ;
⑽ q[i][j]
⑾ *(q[i]+j)
⑿ *(*(q+i)+j)
⒀ (*(q+i))[j] //請與⑴-⑷ ⑹-⑼對比
~~~
好了,看到這的朋友,如果你還沒被搞暈的話,那么這篇文章可能不適合你。如果你已經暈頭轉向、意亂情迷的話,那么也請不要害怕,我不是猥瑣的大叔。下面我會帶著你揭穿數組和指針之間的把戲。
**進入正題:**
**數組:**
數組是指具有相同類型的數據組成的序列,是有序集合。(國內教科書上的定義)
(即:數組就是內存中一段連續的存儲空間。那么我們怎么使用它呢?用數組名。也就是我們用數組名可以在內存中找到對應的數組空間,即數組名對應著地址。
那么數組中有這么多元素,對應的是哪個元素的地址呢?對應著首元素的地址。? 故:我們可以通過數組的首元素地址來找到數組 )
故:**數組名是一個地址(首元素地址),即是一個指針常量。(不是指針變量)**
**只有在兩種場合下,數組名并不用指針常量來表示:**
⑴**sizeof(數組名) **; sizeof返回整個數組的長度,而不是指向數組的指針長度。
⑵**&數組名**; 產生的是一個指向整個數組的指針,而不是一個指向某個指針常量的指針。
&a[0] 與 &a 的區別:
兩者的值相同,但意義不同。&a[0]是指數組首元素的地址。&a是整個數組的地址。
(問題來了,整個數組跨越幾個存儲單位,怎么表示這幾個存儲單位組成的整體呢?如果你是編譯器,你會怎么做?呃,取其第一個存儲單位的值來代表會比較好點。沒錯,編譯器是這么做的。 所以兩者的值相同)
a+1 與 &a+1 的區別:
**數組名a除了在上述兩種情況下,均用&a[0]來代替。**(實際上編譯器也是這么做的)
a+1即等同于&a[0]+1。
注意:**指針(地址)與常數相加減,不是簡單地算術運算,而是以當前指針指向的對象的存儲長度為單位來計算的。**即:指向的地址+常數*(指向的對象的存儲長度)
&a[0]為數組首元素的地址,故&a[0]+1 越過一個數組元素長度的位置。即:&a[0]+1*sizeof(a[0])
&a為整個數組的地址,(只是用首元素地址來表示,其實際代表的意義是整個數組)?????? 故&a+1 越過整個數組長度的位置,到達數組a后面第一個位置。 即:&a+1*sizeof(a)
**指針**:
例:int *p=NULL; 與 *p=NULL ; 的區別
指針的定義與解引用都用到* ,這是讓人暈的一個地方。
(不妨這樣理解:在定義時 星號只是表示這是一個指針,int * 表示這是一個int型的指針,把int * 放在一起看,表示這是一個整型指針類型。 如果我是C的設計者,那么用$符號來定義指針類型 會不會讓大家少些迷惑)
向指針變量賦值,右值必須是一個地址。例:int * p=&i ; 這樣,編譯器在變量表里查詢變量i對應的地址,然后用地址值把&i替換掉。 那么我們能不能直接把地址值寫出來作為右值呢?當然。指針不就是存儲地址的變量嘛,直接把數字型的地址值賦給它有什么問題。(前提是這個地址值必須是程序可訪問的)
例:**int * p=(int *)0x12ff7c ;**
????? *p= 0x100 ?? ;
這里的0x12ff7c可看做某個變量的地址。
需要注意的是:將地址0x12ff7c賦值給指針變量p的時候必須強制轉換。(我們要保證賦值號兩邊的數據類型一致)
***地址的強制轉換:***
~~~
例:double * p ;假設p的值為 0x100000
求下列表達式的值:
p + 0x1 =___
(unsigned long)p + 0x1 = ___
(unsigned int *)p + 0x1 = ___
~~~
**注意:一個指針與一個整數相加減。這個整數的單位不是字節,而是指針所指向的元素的實際存儲大小。**
故:p + 0x1:p指向的是一個double型變量,故值應為:0x100000+0x1*8=0x100008
(unsigned long)p則意為:將表示地址值的p強制轉換成無符號的長整型。(即:告訴編譯器,以前變量p里存儲的是內存中的某個地址,現在變量p里存儲的是一個長整型。即讓編譯器看待變量p的眼光改變一下,以后p是一個整型變量了,不是指針了,不要把它里面的值當做某個變量的地址了,不能根據這個地址去找某變量了。)
任何數值一旦被強制轉換,其類型就變了。(即編譯器解釋其值代表的含義就變了)
故:(unsignedlong)p + 0x1 是一個長整型值加一個整型值,結果為:0x100001
(unsigned int *)p則意為:將一個表示double型變量的地址值的指針,轉換成一個表示unsigned int型變量地址的指針。
故(unsigned int*)p + 0x1值為:0x100000+sizeof(unsignedint)*0x1 等于 0x100004
【**強制轉換指針類型的目的是為了:改變指針的步長(偏移的單位長度)**】
注意:兩個指針直接相加是不允許的。(你要真想把兩個地址值相加,把它們先都強制轉換為int型即可)
?兩個指針直接相減在語法上是允許的。(但必須相對于同一個數組,結果是兩指針指向位置相隔的元素個數)
**指針表達式:**
**注意:*與++優先級相同,且它們的結合性都是從右向左的。**
例:char ch ;char *cp=&ch ;
指針表達式:
①*++cp?? 先運算++cp,再解引用*。
當其為右值時,是ch下一個存儲單元的值(是一個垃圾值)
當其為左值時,是ch的下一個存儲單元
②(*cp)++
當其為右值時,表達式的值等于ch的值,(但它使ch值自增1)
當其為左值時,非法。
【**注意:++,--的表達式(及大部分的表達式,數組的后綴表達式除外)的值都只是一種映像(暫存于寄存器),不在內存區中,故無法得到它們的地址,它們也無法做左值**】
★故:(*cp)++表達式的值雖與ch相同,但它只是ch值的一份拷貝,不是真正的ch
③++*cp++
當其為右值時:表達式的值等于ch+1,這個值只是一個映像(寄存器中)。(但這個表達式實際做了一些工作:使cp指向ch的下一個單元,使ch中的值增1)
當其為左值時:非法。
【++,--,與*組合的指針表達式只是把幾個工作融合在一個表達式中完成,使代碼簡潔,但可讀性差】
例:對于 *cp++ ; 我們可以把它分解為: *cp 之后再 cp++
????? 對于 *++cp ; 我們可以把它分解為:++cp 之后再*cp