指針
1.在日常生活中指針是儀器或鐘錶上的,可動的,一般狹長的,往往在一段尖的用來指示測量的數據的裝置。
2.在信息工程中指針是一個用來指示一個內存地址的計算機語言的變量或中央處理器(CPU)中寄存器(Register)。指針一般出現在比較近機器語言的語言,如彙編語言或C語言。面向對象的語言如Java一般避免用指針。指針一般指向一個函數或一個變量。在使用一個指針時,一個程序既可以直接使用這個指針所儲存的內存地址,又可以使用這個地址里儲存的變量或函數的值。大家都認為,c語言之所以強大,以及其自由性,很大部分體現在其靈活的指針運用上。因此,說指針是c語言的靈魂,一點都不為過。同時,這種說法也讓很多人產生誤解,似乎只有c語言的指針才能算指針。basic不支持指針,在此不論。其實,pascal語言本身也是支持指針的。從最初的pascal發展至今的object pascal,可以說在指針運用上,絲毫不會遜色於c語言的指針。
指針1、內存分配表 計算機中的內存都是編址的,就像你家的地址一樣。在程序編譯或者運行的時候,系統(可以不關心具體是什麼,可能是編譯器,也可能是作業系統)開闢了一張表。每遇到一次聲明語句(包括函數的傳入參數的聲明)都會開闢一個內存空間,並在表中增加一行紀錄。記載著一些對應關係。----------------------------------------------------Declaration | ID Name Address Length----------------------------------------------------Int nP; | 1 nP 2000 2Bchar myChar; | 2 myChar 2002 1Bint *myPointer; | 3 myPointer 2003 2Bchar *myPointer2; | 4 myPointer2 2005 2B----------------------------------------------------
2、指針就是一個整數。 指針,是一個無符號整數(unsigned int,下簡寫為int),它是一個以當前系統尋址範圍為取值範圍的整數。32 位系統下尋址能力(地址空間)是4G-bit(0~232-1)二進位表示長度為32bit,也就是4B。 int 類型也正好如此取值。 例證就是程序1 得到的答案和程序2 的答案一致。(不同機器可能需要調整一下pT 取值。)----------------------------------------------------程序1#include main(){ char *pT; char t='h'; pT= putchar(*pT);}----------------------------------------------------程序2#include main(){ char *pT; char t='h'; pT=(char *)1245048; putchar(*pT);}---------------------------------------------------- 加上(char *)是因為畢竟int 和char *不是一回事,需要強制轉換,否則會有個警告。因為char *聲明過的類型,一次訪問1 個sizeof(char)長度,double *聲明過的類型,一次訪問1個sizeof(double)長度。 在彙編里int 類型和指針就是一回事了。因為不論是整數還是指針,執行自增的時候,都是其值加一。如果上文聲明char *pT;,彙編語言中pT 自增之後值為1245049,可是C語言中pT++之後pT 值為1245049。如果32 位系統中, s 上文聲明int *pT;,彙編語言中pT 自增之後值為1245049,可是C 語言中pT++之後pT 值為1245052。 為什麼DOS下面的Turbo C,和Windows 下VC 的int 類型不一樣長。因為DOS是16位的,Windows 是32 位的,可以預見,在64 位Windows 中編譯,上文聲明int *pT;,pT++之後pT 值為1245056。 那麼,複雜的結構怎麼分配空間呢?C 語言的結構體(彙編語言對應為Record 類型)按順序分配空間。----------------------------------------------------int a[20];----------------------------------------------------typedef struct st{ double val; char c; struct st *next;} pst;----------------------------------------------------pst pT[10];---------------------------------------------------- 在32 位系統下,內存裡面做如下分配(單位:H,16 進位);----------------------------------------------------變量 2000 2001 2002 2003 2004 2005 2006 … 204C 204D 204E 204F地址 a[0] a[1] … a[19]----------------------------------------------------變量 2050 2051 … 2057 2058 2059 205A 205B 205C 205D 205E 205F地址 pst.val pst.c pst.next 無效 無效 無效---------------------------------------------------- 這就說明了為什麼sizeof(pst)=16 而不是8。編譯器把結構體的Size 規定為結構體成員中Size 最大的那個類型的整數倍。 至於pT 的存儲,可以依例推得。總長為160,此不贅述。 有個問題,如果執行pT++,答案是什麼?是自增16,還是160?別忘了,pT 是常量,不能加減。 所以,我們就可以聲明:----------------------------------------------------typedef struct BinTree{ int value; struct BinTree *LeftChild; struct BinTree *RightChild;} BTree;---------------------------------------------------- 用一個整數,代表一棵樹的結點。把它賦給某個結點的LeftChild/RightChild 值,就形成了上下級關係。只要無法找到一個路徑,使得A-LC/RC-LC/RC...-LC/RC==A,這就構成了一棵二叉樹。反之就成了圖。
3、C 的按值傳遞 C 中函數調用是按值傳遞的,傳入參數在子函數中只是一個初值相等的副本,無法對傳入參數作任何改動。但實際編程中,經常要改動傳入參數的值。這一點我們可以用傳入參數的地址而不是原參數本身,當對傳入參數(地址)取(*)運算時,就可以直接在內存中修改,從而改動原想作為傳入參數的參數值。----------------------------------------------------#include void inc(int *val){ (*val)++;}main(){ int a=3; inc(&a); printf("%d" , a);}---------------------------------------------------- 在執行inc(&a);時,系統在內存分配表里增加了一行「inc 中的val」,其地址為新地址,值為&a。操作*val,即是在操作a 了。
4、*和&運算 為了(*p)操作是這樣一種運算,返回p 的值作為地址的那個空間的取值。(&p)則是這樣一種運算,返回當時聲明p 時開闢的地址。顯然可以用賦值語句對內存地址賦值。我們假設有這麼兩段內存地址空間,他們取值如下:(單位:H,16 進位)----------------------------------------------------地址 0000 ... 2000 2001 2002 2003 2004 ... 3000 3001 3002 3003 ...取值 ???? ... 01 30 00 00 30 00 03 20 9A---------------------------------------------------- 假設有這麼一段代碼:(假設開闢空間時p 被分配給了3001H、3002H 兩個位置)----------------------------------------------------int *p;p=2003;*p=3001H---------------------------------------------------- **p 的值為多少? **p=*(*(p))=*(*(2003H))=*(3000H)=0300H。 那麼&&p、*(&p)和&(*p)又等於多少? &&p=&(&(p))=&(3001H),此時出錯了,3001H 是個常數怎麼可能有地址呢? *&p=*(&(p))=*(3001H)=2003H,也就是*&p=p。 &*p=&(*(p))=&(3001H),讀者可能以為&*p=p 此時出錯了,3001H 是個常數怎麼可能有地址呢?
4'、另類的*和& 兩個地方要注意: 在程序聲明變量的時候的*,只是表明「它是一個整數,這個整數指向某個內存地址,一次訪問sizeof(type)長度」。這點不要和(*)操作符混淆; 在C++程序聲明變量的時候的&,只是表明「它是一個引用,這個引用聲明時不開闢新空間,它在內存分配表加入新的一行,該行內存地址等於和調用時傳入的對應參數內存地址」。 這點不要和(*)聲明符,(&)操作符混淆。 清華大學軟體學院免試研究生面試就有問過這個問題。
5、雙重指針(指向指針的指針) 雙重指針又是怎麼一回事兒呢?綜合2 的BTree 定義,和3 的說法。對於一棵樹,我們通常用它的根節點地址來表示這棵樹。這就使「擒賊先擒王」。找到了樹的根,其每個節點都可以找到。 但是有時候我們需要對樹進行刪除節點,增加節點操作,往往考慮到刪除根節點,增加的節點取代原來的根節點作為新根節點的情況。為了修改根節點這個「整數」,我們需要退一步,使用這個「整數」的內存地址,也就是指向這個「整數」的指針。在聲明時,我們用2 個*號,聲明指向指針的指針。它的意思是「它是一個整數,這個整數指向某個內存地址,一次訪問sizeof(int)長度,其值是一個整數,那個整數值指向某個內存地址,一次訪問sizeof(BTree)長度。」。詳見「數據結構.rar」有關「樹」(06 文件夾)的程序代碼。
6、指針數組、數組指針和指向函數的指針 指針數組:就是一個整數數組,那個數組的各個元素都是整數,指向某個內存地址。 數組指針:數組名本身就是一個指針,指向數組的首地址。注意這是一個常數。 指向函數的指針:從二進位角度考慮,數組名是該數組數據段首地址,函數名就是該代碼段的首地址,可以用「int *fun()」。在二進位層面,代碼段和數據段什麼區別?沒什麼區別。----------------------------------------------------#include void inc(int *val){ (*val)++;}main(){ void (*fun)(int *); int a=3; fun=inc; (*fun)(&a); printf("%d" , a);}