C語言中的指標以及二級指標
很多初學者都對C中的指標很迷糊,希望這篇blog能幫助到大家:
1.什麼是“指標”:
在執行C程式的時候,由於我們的資料是儲存在記憶體中的。所以對於C程式本身來說,如果想找到相應被呼叫的資料,就要知道儲存該資料的記憶體地址是多少,換言之,C程式通過已知的記憶體地址到相應的記憶體位置儲存資料。
這裡簡單說一下記憶體管理(對於初學者來說。為了避免專業術語引發的理解問題,下面的敘述儘量避免專業定義:),對於現代計算機系統來說,記憶體空間分為兩個區域,一個是“資料區”,一個是“地址區”,“資料區”儲存的是使用者資料,比如我們要把一個數字“5”儲存到計算機(因為一個單純的自然數“5”,是沒有任何意義的,然後對於計算機來說它需要知道你要把什麼定義為“5”,你就不得不定義“x=5")對於計算機而言,這個過程分為以下幾個部分:
1.在”棧區(stack)(這個定義實在不能避免,初學者的話就請暫時記住這個名字)“開闢一個空間,用來存放”5“
2.另存存放”5“的記憶體的地址。
3.將步驟2中的記憶體地址存在另一個區域(專門用來存放地址的指標區),並記下當前存放步驟2中的記憶體地址的記憶體地址(好拗口,這裡其實是二級指標的概念)
3.建立一個”索引“將x與步驟3中的記憶體地址關聯,存放在”索引區“(請注意,x和5不是存在一起的,而是有一個“對映表”,並且 指向 x的指標不會直接指向5,而是直接指向x,再通過“對映表”找到x的值‘5’,這個概念非常重要,後面例子會講到利用指標交換兩個變數的值,就是基於“x和5不是存在一起的”這個基本概念)。
這裡再多說一嘴:為什麼要以這種方式存放資料?
記憶體的儲存區就像一池湖水,資料就像池水裡面的魚,如果不用記憶體定址的方式,那麼當你找某個特定資料的時候,就相當於在一池湖水裡找某一條叫做“張三”的魚一樣--你得一條一條撈出來辨認。
如果有記憶體定址,就像把一池湖水用漁網分成若干網格,每個網格里面放一兩條魚並且把每個網格都編號(編號和魚的對應關係假如你用一個小本子記起來),這樣當你想找某條叫“張三“的魚時,你只要開啟小本子(指標地址)找相應的網格就可以了。
那麼,儲存資料的記憶體地址(有點拗口)或者說是上面例子裡面記載編號和魚的對應關係的小本子就叫指標。
舉個例項吧,如下圖所示,我們將記憶體儲存空間實體化:假設途中兩條平行線夾的空間是記憶體可以儲存資料的空間,途中C的位置儲存的是資料,那麼P的位置儲存的就是指標。
如何定義指標?
int *p;
注意:
1.這裡的int,指的是指標p對應的儲存區的資料格式,並不是指標p的資料格式。你可以理解為指標的資料格式只有一種。
2.*不僅僅是單純的運算子,它還是宣告符。可以把“*”理解為像“int,float,double”等等這樣的格式宣告。
3.在使用指標p的時候,經常會用到地址運算子“&”,請注意“&”是運算子,運算操作是取地址,可以把p直接賦上一個地址值:
int i=5,*p;
p=&i;
於是 *p 的值就是5了。
上面還可以這麼寫
int i=5;
int *p=&i;
從這兩個例子的區別可以看出“*”具有型別宣告的作用。
再寫一個交換兩個變數的值的程式碼:
#include <stdio.h>
void swap(int *a, int *b)
{
int temp;//建立一箇中間變數用於交換位置。
temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int m=10,n=22;
swap(&m,&n);//這裡對於理解記憶體管理原則非常重要,正如前面所說,變數m和其值10不是存在一個記憶體儲存區,而是兩個,它們通過一個對映表對映起來,所以在這裡交換m和n的地址值可以理解為交換了m和n的對映表指向位置。
printf("m=%d,n=%d\n",m,n);
return 1;
}
上面是通過改變兩個變數地址的方式交換了變數m,n的值。在這個過程用到了記憶體地址和記憶體以及指標的定義,如果沒有看懂請回頭再仔細研究指標的定義。
二級指標:
如果你熟悉了指標的定義,那麼二級指標應該很好理解,所謂的二級指標,就是指標的指標。
具體解釋一下:因為任何一個變數值(包括指標地址)最後都是要放入到記憶體中去的,回到之前舉的“池子裡的魚”那個例子,所謂的二級指標就是存放那個寫著網格和編號的小本子的位置資訊(比如你把這個本子放到某個抽屜裡了,那麼二級指標記載的內容就是“如何找到這個抽屜”)。
二級指標的定義也很簡單粗暴,一個指標變數 *p存放這個指標變數地址的二級指標就是 *(*p),你可以直接簡單粗暴地簡寫為**p(編譯器是認這個的)。
其他問題:
1.定義一個指標變數*p,那麼p到底是什麼?
你可以簡單粗暴地把的值p理解為 這個指標變數儲存的地址。切記千萬不要寫成:
int *p=5;
原因就是我之前說過的,這裡再重複一次:*p 只是宣告變數p儲存的內容是地址。*p並不是一個可以賦值的變數,而是一個”特殊的“ 型別定義+變數。
2.該如何在指標中賦值?
下面說幾個合法的賦值:
int *temp = *p;//這是二級指標常用的操作,作用是將指標P的值(指標p的值是地址值,指向的是另外一個地址空間)賦給指標temp指向的值,等價於 int *temp;temp=*p;
int i=115,j=116,*r=&i,*s=&j;//將i的地址賦給 r</span>
3.對指標的定義迷糊?
你是不是很奇怪:
int i=5,j=6, *a=&i,*b=&j;
在這裡 *a=&i,*b=j是可以的,但是如果你這麼寫:
int i=5,j=6,*a,*b;
*a=&i,*b=j;
這個時候 *a=&i,*b=j就要報錯。
為什麼?
原因就是我之前說的:你不可以把在 ”int float這樣的格式聲明後的“”*“理解成為運算子,而是要理解成為一個像”int“這樣的格式宣告。”int *a,double * n,float *c“這樣的搭配含義是”a/b/c是一個指向int/double/float的指標,諸如 int *a=&i這樣的宣告實際上是 (int *)a=&i。請一定記住這個特殊情況,這樣你就不會再迷糊。
4.謹記*a中的*是取(指標a指向的)值運算,&b是取(b所在的記憶體的)地址運算。在這裡字元a儲存的是地址,而b儲存的是資料,這裡再次宣告,一定不要被3中提出的“特殊情況”搞混,那只是一個特例,其他情況不可以那樣用。