很多朋友在初學C語言的時候,難免都會在指針這塊停留許久,包括我本人。久久不能釋懷,對其愛恨交織。靜下心來,想總結一下自己在學指針的時候的一點心得,也是第一次寫博客,激動萬分,希望朋友們能多多指正、多多批評! 首先呢,針對指針,給初學指針的朋友一個最感性的認識:所謂指針,指的是,一個量,是一個儲存的內 ...
很多朋友在初學C語言的時候,難免都會在指針這塊停留許久,包括我本人。久久不能釋懷,對其愛恨交織。靜下心來,想總結一下自己在學指針的時候的一點心得,也是第一次寫博客,激動萬分,希望朋友們能多多指正、多多批評!
首先呢,針對指針,給初學指針的朋友一個最感性的認識:所謂指針,指的是,一個量,是一個儲存的內容是地址的一個量,這個概念包含兩個點:
一、指針是個量,對應著一塊記憶體區域;
二、指針存儲的信息是某個記憶體單元的地址。
如上圖所示,為了存儲32位的地址數據,指針占據了4個位元組,每個位元組8個二進位位,該指針中存儲的是某個類型在記憶體的地址數據,此處以double類型為例,double類型的數據占據8個記憶體位元組,將其首位元組的地址存儲在指針中,這樣,通過指針可以訪問該double數據。這類似於現實生活中的地址和名片,習慣於把地址印在名片上,這裡名片的作用和指針相同,存儲的都是地址數據。
很明顯, 在引入指針的時候,多次提到了記憶體,那麼很自然地,接下來先簡單說一下記憶體的一些概念!
關於記憶體
大家都知道,記憶體是平時接觸較多的一個概念,從硬體上說,記憶體是一個物理設備,從功能上講,記憶體是一個數據倉庫,程式在執行前都要裝載到記憶體中才能被中央處理器(CPU)執行。
1、電腦中的記憶體
以Windows系統為例,執行安裝在硬碟上的某個程式,實際上是將該程式的指令和數據讀入記憶體,供CPU執行的過程。記憶體是由按順序編號的一系列存儲單元組成的,在記憶體中,每個存儲單元都是唯一的地址,通過地址可以方便地在記憶體單元中存取信息。記憶體中的數據要靠電源維持,當電腦關機或者意外斷電時,其中的所有數據就永遠消失了。
2、記憶體地址
記憶體地址的引入是同樣的道理,為了正確訪問每個記憶體單元,要對其進行編址,以32位電腦為例,其地址空間為32位,採用32為地址編碼,諸如0X87654321的形式。記憶體地址是連續的,相鄰記憶體單元空間的地址差1,可以把記憶體看成一個平坦連續的一維空間。
3、記憶體中保存的內容
在電腦中,以二進位數據的形式存放的,每個記憶體單元的容量是1B,即8bit。記憶體是CPU唯一可以直接訪問的大容量設備,使用Windows的朋友都清楚,雙擊某個可執行的程式,CPU會執行它,這實際上是複雜的記憶體載入過程:
① 程式所要進行操作的對應代碼裝載到代碼區
② 全局和靜態數據等裝載到數據區
③ 開闢堆棧、供臨時變數等使用
4、記憶體與操作系統
程式在運行前,需要向操作系統申請存儲空間,在記憶體空間足夠空閑的時候,操作系統將分配一段記憶體空間並將外存中軟體拷貝一份到記憶體中,並啟動軟體運行,在運行期間,該軟體分配的空間不再分配給其他軟體,當軟體運行結束後,將回收該軟體記憶體空間(但並不清楚遺留數據,Java局部變數必須初始化,原因就在此,防止被之前的遺留數據影響)
扯了這麼多記憶體的內容,只是為了讓初學的朋友有點感覺,也是為學習指針打一劑預防針!
關於指針
指針就是地址,地址就是指針。
指針變數是存放記憶體單元地址的變數
指針的本質是一個操作受限 的非負整數
下麵從兩個方面來淺談一下指針的問題:
1、基本類型的指針
指針的定義:類型 * 指針變數名;
比如int * p; double * q;
1 include<stdio.h> 2 int main() { 3 int *p; 4 int i = 10; 5 int j; 6 // j = *p; 7 // p = &i; 8 // *p = i < -> i = i 9 // j = *p; 10 // char ch = 'a'; 11 // p = &ch /*類型不一致 錯*/ 12 // printf("i = %d,j = %d,*p = %d",i,j,*p);// 10 10 10 13 return 0; 14 }
在上面的代碼3~5行定義了三個變數(如下圖),分別是:int *形的變數p、int形的變數i、int形變數j。需要清楚的是在第三行中,p是變數名字,int * 表示該變數只能存儲int類型變數的地址。
如果在指針變數聲明之初確實不知道將此指針指向何處,最簡單的方式是將其置為“0”,C語言中提供了關鍵字NULL: int * pInt = NULL;
如果放開第6行註釋。會出現什麼結果呢,勢必會報錯!因為*p保存的是地址,但這句話上面的代碼中並沒有給p賦值。p不知道哪個單元的,p指向的是一個不確定單元,垃圾數據!如果放開第9行就不會有這種結果了,因為在第7行對p進行了賦值,p變數保存了i的地址了!
在第七行中,有三句話:
① p保存了i的地址值,p指向了i。
② 但並不說明p等於i或者說i等於p。修改i的值並不影響p的值,修改p的值也不影響i的值。
③ i能代表什麼,*p就能代表什麼。(如第8行)
再看第10~11行,這個賦值對嗎?定義一個char類型字元,將他的地址保存在整形指針p中,毫無疑問,這也是錯的,前面已經說了,int * 表示該變數只能存儲int類型變數的地址。
接下來再看指針在函數中的作用:
1 #include<stdio.h> 2 3 void f(int i) 4 { 5 i = 100; 6 } 7 8 int main(void) 9 { 10 int i = 5; 11 f(i); 12 printf("%d\n",i); 13 return 0; 14 }
1 #include<stdio.h> 2 void f(int * p) // 不是定義一個名字叫*p的形參,而是定義了一個p的形參,它的類型是int 3 { 4 *p = 100; 5 } 6 7 int main(void) 8 { 9 int i = 5; 10 f(&i); 11 printf("%d\n",i); 12 return 0; 13 }View Code
在第一個代碼中,如果想通過函數改變i的值,能成功嗎?很明顯是不行的,i是局部變數,i輸出的最終值將仍是5。
那麼如何根據被調函數改變主調函數的值呢,第二段代碼就實現了這個問題,通過使用”*指針”的形式,可以直接訪問指針所指向的記憶體空間。換言之,因為通過參數傳遞,p保存了i的地址,那麼*p 就等價於i,同時可以通過間接引用“*p = 100;”可以改寫指針指向的區域!註意在第二段代碼中的第二行,形參為 int * p ,不是定義一個名字叫*p的形參,而是定義了一個名字為p的形參,它的類型是int *!
2、指針與數組的關係
首先要明白,數組名就是一個指針,並且指向數組的首地址!數組元素的地址是連續的。
數組名的含義:
① 是一個指針變數
② 存放的是一維數組第一個元素的地址
③ 值不能改變
④ 指向第一個元素
下標和指針的關係:
a[i] = *(a+i)
1 #include<stdio.h> 2 void printArray(int * p,int len) { 3 p[2] = -3; 4 /* p[0] = *p p[2] == *(p+2) == *(a+2) == a[2] 5 p[i]就是主函數的a[i] 6 */ 7 int i = 0; 8 for(i = 0; i<len; ++i) { 9 printf("%d\n",p[i]); 10 } 11 } 12 int main(void) 13 { 14 int arr[5] = {1,2,3,4,5}; 15 printArray(arr,5); 16 return 0 ; 17 }
有了上面的理論再看這段代碼,就不難理解,p[0] = * p;的含義了,因為數組名是一個指針,指向數組第一個元素的地址,所以數組的第一個元素p[0]就等同於*p了!
因為"a[i] = *(a+i)",可以知道p[2] == *(p+2),因為p指向第一個元素,那麼p+2就自然指向第三個元素了,所以p[2] = 3是給數組的第三個元素賦值,這事沒問題的!但註意*(p+2)不等同於*p+2,後者是在*p指向的元素上,給這個元素的值加上2,前者並非如此,而是給他的地址“+2”。
今天就先到這裡,指針的內容多多,日後還應繼續總結,繼續和大家討論!
初次寫博,紕漏多多!望朋友們不吝賜教!