程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 深入淺出C指針(一)基本概念

深入淺出C指針(一)基本概念

編輯:關於C語言

初學者在學習C語言時,通常會遇到兩個瓶頸,一個是“遞歸”,一個是“指針”。大學老師在講述這兩個知識點時通常都是照本宣科,而沒有站在一個初學者的角度來審視問題,更沒有剖析其內部機理。本人在此將發表一系列技術文章,希望能將C語言中“指針”這一概念講述清楚,希望初學者能從中收益。在此筆者也極力推薦Kenneth A.Reek寫的《Pointers On C》這本書。
 1.內存和地址
初學者面對內存一詞時總是有一種既陌生又熟悉的感覺。首先,在日常生活中大家總是會討論某某設備內存有多大,是不是該加一個內存條等等。但是,內存究竟是什麼?在此,筆者並不想深入探討內存的本質,以及內存在不同操作系統上的結構有什麼區別等等這一系列問題。這裡只希望初學者知道以下三個問題即可:
 (1)內存是計算機在運行過程中存儲數據的地方
 (2)內存被分割成了“無數”個小區域,每個區域的大小在不同的環境下有所不同,可能是1個字節,2個字節,或者多個字節。
 (3)每個小區域都有一個獨一無二的標識,即我們後面所說的地址(指針)。
 (4)每個小區中都有包含一個值,可以是整形,浮點型,字符型等等。
我們可以形象的用下圖表示內存的結構:
    100           104           108           112             116
     |                   |                 |                 |                   |
------------------------------------------------------------------------
   (542)         (3.14)         (‘A’)     (12323)        (-12)
如圖所示,上方表示內存的地址,下方括號中的內容表示該地址下內存中的值。我們想訪問這些內存中的值,只需要知道內存地址即可。但是,我們怎麼知道我們要訪問的內容其內存地址是多少?的確,要記住這些內存地址幾乎是不可能的,所以,編譯器可以讓我們用變量的形式訪問他們,於是這張圖可以變成如下所示:
 total_count      Pi            m_ch         money           number
     |                      |                  |                 |                       |
------------------------------------------------------------------------
   (542)         (3.14)         (‘A’)     (12323)        (-12)
我們將內存地址變成了我們更容易記住的變量,這樣我們在編寫程序的時候就可以方便的訪問內存中的數據。但是,請記住,這只是編譯器幫助我們進行了優化,而真正編譯後的機器碼則是通過真正的內存地址來訪問內存中的數據,即尋址,關於計算機的尋址過程,有興趣的讀者可以參考計算機組成原理或者匯編語言等書。


2.值和類型
我們首先來看一下下面這幾個表達式:
int  total_count = 542;
float Pi = 3.14;
int *p1 = &total_count;
char *p2 = &m_ch;
前兩個表達式很好理解,我們申明了一個整形和一個浮點型變量,並分別賦值為542和3.14。那後面兩個表達式是什麼意思呢?
我們姑且可以簡單記住:在申請變量的表達式中,如果類型的後面出現了*號,那麼這個變量就叫做指針,或者指針變量。指針變量分為很多種類型,例如整形指針,浮點型指針,字符型指針等等。
好了,我們知道了指針的概念,那麼,指針到底是什麼?
指針也是變量,即指針變量,它和其他的變量在本質上是沒有區別的。但是指針變量只能保存一種值,就是地址。
也許你會問了,既然指針保存的是地址,那麼為什麼要將指針分為那麼多的種類,整形值要有整形指針,字符型值要有字符型指針?為什麼不能用一種指針就把所有的地址都包含了呢?難道不同類型變量的地址也不同嗎?
這個問題,本人沒有查閱過官方的解釋,首先可以肯定,內存地址在理論上是沒有任何區別的,無論是用來保存什麼類型變量的內存,其本質都是01Bit構成的區域,內存地址當然也不會有任何區別,從純技術技術的角度上講,筆者認為編譯器完全可以建立一種制度,用統一的指針類型保存不同變量的地址,這完全不會影響程序的運行。但是編譯器沒有這樣做的原因,筆者分析主要是出於安全性的考慮。當程序員有意或無意的將兩種不同類型的指針所指向的內存內容進行賦值時,如果編譯器事先不能做出檢查,那麼也許會在程序運行過程中出現異常,例如產生非常嚴重的緩沖區溢出錯誤。


3.指針的間接訪問符
好了,我們將問題回到這幾個表達式上來。現在的問題是p1和p2這兩個變量中保存的是什麼。拿p1為例,也許你可以這樣理解,因為p1是指針變量,所以他的值應該和他指向的內存中的值一樣,所以p1 = 542. 這樣理解看似非常符合邏輯,但卻是一個大的錯誤。雖然p1很特殊,但是指針變量也是變量,它不會聰明到自動去完成一個非常復雜的自動間接訪問操作。p1的值實際是100,即變量total_count的地址。但是讀者請注意,100並不是傳統意義上整形100,例如 int *p1 = 100;這是一個錯誤的表達式,因為不能直接將整形值賦值給指針變量,作如下變化即可 int *p1 = (int *)100;
也就是說,如果我們輸出p1的值,打印出來將會是一串莫名其妙的數字。當然,我們通常對這些莫名其妙的地址不感興趣,我們更感興趣的是這些地址背後影藏這怎樣的信息。所以我們可以這樣來訪問int count = *p1;
等等...讀者讀到這裡也許有些糊塗了,
int *p1 = &total_count; int count = *p1; 這麼多的*號和&號也許讀者有些搞不清了。我們來理清一下思路。
在int *p1 = &total_count;中:
 *p1只是一個標識,代表p1是一個指針,以後訪問這個指針時直接p1即可,訪問得到的值是一個地址編碼。
&total_count代表一個地址,在任意一個變量前(包括指針)加上&符號,都將代表這個變量所在內存的地址。&p1則代表指針所在內存的地址,即指針的指針(稍後詳細介紹)其實這麼有什麼可疑問的,因為指針本身也是變量,所以和其他變量本質上沒有任何區別。
在 int count = *p1;中,p1和前面說的一樣,訪問它的值是一個地址,而*號與之前的*號有些不同,之前的*號只是一個標識,表示當前申請的變量是一個指針,而這裡的*號我們給它一個新的名字,叫指針的間接訪問符。聽起來有些別扭,簡單地理解就是,在一個指針變量前加上*號則可以訪問該指針所所表示內存的實際值。
這裡可以教初學者一個小技巧,當利理解一個和指針有關的語句是,“指針”和“地址”這兩個詞可以互換,初學者姑且可以認為“指針”就是“地址”,“地址”就是“指針”,這樣理解不確切,但卻很實用。
好了,下面讓我們看一個更復雜的例子:
int *p1 = &total_count;
int count2 = *(*(&p1));
請讀者在10秒鐘之內告訴我count2的值是多少。哈哈,好吧,讓我們來一點點分析。
從括號最裡面開始:
p1是一個指針,他的值是total_count的地址;
&p1是一個地址,即指針p1的地址,我們用剛才的小技巧來看看,即地址p1地址,即指針p1的指針。
*(&p1)是一個值,這個值是&p1表示的地址所在內存的值實際上就是p1的值,&對p1進行了一次引用操作,*對其再解引用,實際上沒有變化。此時*(&p1)的值為total_count的地址。
*(*(&p1))為total_count的值,也就是542.
到此,該表達式的分析結束,不過在實際編程中,沒有人會用如此復雜的表達式進行編碼。


4.指針的指針
指針的指針,即地址的地址。第一個地址是狹義上的地址,該地址實際上已經被“值”化,第二個地址是我們傳統意義上的地址。這句話理解起來有些困難,我們先來看看下面的表達式;
int *p1 = &total_count;
int **p2 = &p1;
第一個表達式的意義我們已經清楚了,我們申請了一個指針變量,或者說是地址變量,它保存的是total_count的地址。
第二個表達式我們來一步步分析,p1是一個指針,即一個地址,&為地址符,加起來就是地址的地址,或者說指針的指針。而int **p2 我們可以這樣理解 int *X,我們定義了一個指針變量X ,X為*p2,即X也是一個指針變量,加起來的意思就是我們申請了一個p2指針變量,這個指針變量指向的也是一個指針變量。
同理: int ***p3 = &p2;
       int ****p4 = &p3;
      ......都是成立的表達式,不過在實際編碼中,遇到的最多情況是指針和指針的指針這兩種情況。
 


摘自  Kernel & UI 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved