程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++基礎之指針的詳細介紹(一)

C++基礎之指針的詳細介紹(一)

編輯:C++入門知識

在介紹C++中的指針開始之前,我們一定先要了解數組的概念以及用法,大家可以看看這篇文章,《淺析C++中的動態多維數組》,供參考。

數組

在C++中是通過變量來對內存進行訪問的,但根據前面的說明,C++中只能通過變量來操作內存,也就是說要操作某塊內存,就必須先將這塊內存的首地址和一個變量名綁定起來,這是很糟糕的。

比如有100塊內存用以記錄100個工人的工資,現在要將每個工人的工資增加5%,為了知道各個工人增加了後的工資為多少,就定義一個變量float a1;,用其記錄第1個工人的工資,然後執行語句a1 += a1 * 0.05f;,則a1裡就是增加後的工資。由於是100個工人,所以就必須有100個變量,分別記錄100個工資。因此上面的賦值語句就需要有100條,每條僅僅變量名不一樣。

上面需要手工重復書寫變量定義語句float a1;100遍每次變一個變量名),無謂的工作。因此想到一次向操作系統申請100*4=400個字節的連續內存,那麼要給第i個工人修改工資,只需從首地址開始加上4*i個字節就行了因為float占用4個字節)。

為了提供這個功能,C++提出了一種類型——數組。數組即一組數字,其中的各個數字稱作相應數組的元素,各元素的大小一定相等因為數組中的元素是靠固定的偏移來標識的),即數組表示一組相同類型的數字,其在內存中一定是連續存放的。在定義變量時,要表示某個變量是數組類型時,在變量名的後面加上方括號,在方括號中指明欲申請的數組元素個數,以分號結束。因此上面的記錄100個工資的變量,即可如下定義成數組類型的變量:

  1. float a[100]; 

上面定義了一個變量a,分配了100*4=400個字節的連續內存因為一個float元素占用4個字節),然後將其首地址和變量名a相綁定。而變量a的類型就被稱作具有100個float類型元素的數組。即將如下解釋變量a所對應內存中的內容類型就是如何解釋內存的內容):a所對應的地址標識的內存是一塊連續內存的首地址,這塊連續內存的大小剛好能容納下100個float類型的數字。

因此可以將前面的float b;這種定義看成是定義了一個元素的float數組變量b.而為了能夠訪問數組中的某個元素,在變量名後接方括號,方括號中放一數字,數字必須是非浮點數,即使用二進制原碼或補碼進行表示的數字。如a[ 5 + 3 ] += 32;就是數組變量a的第5 + 3個元素的值增加32.又:

  1. long c = 23;   
  2. float b = a[  c – 3 ) / 5 ] + 10, d = a[ c – 23 ]; 

上面的b的值就為數組變量a的第4個元素的值加10,而d的值就為數組變量a的第0個元素的值。即C++的數組中的元素是以0為基本序號來記數的,即 a[0]實際代表的是數組變量a中的第一個元素的值,而之所以是0,表示a所對應的地址加上0*4後得到的地址就為第一個元素的地址。

應該注意不能這樣寫:

  1. long a[0]; 

定義0個元素的數組是無意義的,編譯器將報錯,不過在結構或類或聯合中符合某些規則後可以這樣寫,那是C語言時代提出的一種實現結構類型的長度可變的技術。

還應注意上面在定義數組時不能在方括號內寫變量,即

  1. long b = 10;   
  2. float a[ b ];//是錯誤的 

因為編譯此代碼時,無法知道變量b的值為多少,進而無法分配內存。可是前面明明已經寫了b = 10;,為什麼還說不知道b的值?那是因為無法知道b所對應的地址是多少。

因為編譯器編譯時只是將b和一個偏移進行了綁定,並不是真正的地址,即b所對應的可能是Base - 54,而其中的Base就是在程序一開始執行時動態向操作系統申請的大塊內存的尾地址,因為其可能變化,故無法得知b實際對應的地址實際在 Windows平台下,由於虛擬地址空間的運用,是可以得到實際對應的虛擬地址,但依舊不是實際地址,故無法編譯時期知道某變量的值)。

但是編譯器仍然可以根據前面的long b = 10;而推出Base - 54的值為10啊?重點就是編譯器看到long b = 10;時,只是知道要生成一條指令,此指令將10放入Base - 54的內存中,其它將不再過問也沒必要過問),故即使才寫了long b = 10;編譯器也無法得知b的值。

上面說數組是一種類型,其實並不准確,實際應為——數組是一種類型修飾符,其定義了一種類型修飾規則。關於類型修飾符,後面將詳述。

字符串

要查某個字符對應的ASCII碼,通過在這個字符的兩側加上單引號,如'A'就等同於65.而要表示多個字符時,就使用雙引號括起來,如:"ABC".而為了記錄字符,就需要記錄下其對應的ASCII碼,而ASCII碼的數值在-128到127以內,因此使用一個 char變量就可以記錄一個ASCII碼,而為了記錄"ABC",就很正常地使用一個char的數組來記錄。如下:

程序無論執行多少遍,在申請內存時總是申請固定大小的內存,則稱此內存是靜態分配的。前面提出的定義變量時,編譯器幫我們從棧上分配的內存就屬於靜態分配。每次執行程序,根據用戶輸入的不同而可能申請不同大小的內存時,則稱此內存是動態分配的,後面說的從堆上分配就屬於動態分配。

很明顯,動態比靜態的效率高發票長度的利用率高),但要求更高——需要電腦和打印機,且需要收銀員的素質較高能操作電腦),而靜態的要求就較低,只需要已經印好的發票聯,且也只需收銀員會寫字即可。

同樣,靜態分配的內存利用率不高或運用不夠靈活,但代碼容易編寫且運行速度較快;動態分配的內存利用率高,不過編寫代碼時要復雜些,需自己處理內存的管理分配和釋放)且由於這種管理的介入而運行速度較慢並代碼長度增加。

靜態和動態的意義不僅僅如此,其有很多的深化,如硬編碼和軟編碼、緊耦合和松耦合,都是靜態和動態的深化。

地址

前面說過“地址就是一個數字,用以唯一標識某一特定內存單元”,而後又說“而地址就和長整型、單精度浮點數這類一樣,是數字的一種類型”,那地址既是數字又是數字的類型?不是有點矛盾嗎?

如下:浮點數是一種數——小數——又是一種數字類型。即前面的前者是地址實際中的運用,而後者是由於電腦只認識狀態,但是給出的狀態要如何處理就必須通過類型來說明,所以地址這種類型就是用來告訴編譯器以內存單元的標識來處理對應的狀態。

指針

已經了解到動態分配內存和靜態分配內存的不同,現在要記錄用戶輸入的定單數據,用戶一次輸入的定單數量不定,故選擇在堆上分配內存。假設現在根據用戶的輸入,需申請1M的內存以對用戶輸入的數據進行臨時記錄,則為了操作這1M的連續內存,需記錄其首地址,但又由於此內存是動態分配的,即其不是由編譯器分配而是程序的代碼動態分配的),故未能建立一變量來映射此首地址,因此必須自己來記錄此首地址。

因為任何一個地址都是4個字節長的二進制數對32位操作系統),故靜態分配一塊4字節內存來記錄此首地址。檢查前面,可以將首地址這個數據存在unsigned long類型的變量a中,然後為了讀取此1M內存中的第4個字節處的4字節長內存的內容,通過將a的值加上4即可獲得相應的地址,然後取出其後連續的4個字節內存的內容。但是如何編寫取某地址對應內存的內容的代碼呢?

前面說了,只要返回地址類型的數字,由於是地址類型,則其會自動取相應內容的。但如果直接寫:a + 4,由於a是unsigned long,則a + 4返回的是unsigned long類型,不是地址類型,怎麼辦?

C++對此提出了一個操作符——“*”,叫做取內容操作符實際這個叫法並不准確)。其和乘號操作符一樣,但是它只在右側接數字,即* a + 4 )。此表達式返回的就是把a的值加上4後的unsigned long數字轉成地址類型的數字。

但是有個問題:a + 4所表示的內存的內容如何解釋?即取1個字節還是2個字節?以什麼格式來解釋取出的內容?如果自己編寫匯編代碼,這就不是問題了,但現在是編譯器代我們編寫匯編代碼,因此必須通過一種手段告訴編譯器如何解釋給定的地址所對內存的內容。

C++對此提出了指針,其和上面的數組一樣,是一種類型修飾符。在定義變量時,在變量名的前面加上“*”即表示相應變量是指針類型就如在變量名後接“[]”表示相應變量是數組類型一樣),其大小固定為4字節。如:

  1. unsigned long *pA; 

也就是說,某個地址的類型為指針時,表示此地址對應的內存中的內容,應該被編譯器解釋成一個地址。

因為變量就是地址的映射,每個變量都有個對應的地址,為此C++又提供了一個操作符來取某個變量的地址——“&”,稱作取地址操作符。其與“數字與”操作符一樣,不過它總是在右側接數字而不是兩側接數字)。

“&”的右側只能接地址類型的數字,它的計算Evaluate)就是將右側的地址類型的數字簡單的類型轉換成指針類型並進而返回一個指針類型的數字,正好和取內容操作符“*”相反。

上面正常情況下應該會讓你很暈,下面釋疑。

  1. unsigned long a = 10, b, *pA;   
  2. pA = &a;  
  3. b = *pA;   
  4.  *pA )++; 

上面的第一句通過“*pA”定義了一個指針類型的變量pA,即編譯器幫我們在棧上分配了一塊4字節的內存,並將首地址和pA綁定即形成映射)。然後“&a”由於a是一個變量,等同於地址,所以“&a”進行計算,返回一個類型為unsigned long*即unsigned long的指針)的數字。

應該注意上面返回的數字雖然是指針類型,但是其值和a對應的地址相同,但為什麼不直接說是unsigned long的地址的數字,而又多一個指針類型在其中攪和?因為指針類型的數字是直接返回其二進制數值,而地址類型的數字是返回其二進制數值對應的內存的內容。因此假設上面的變量a所對應的地址為2000,則a;將返回10,而&a;將返回2000.

再來看取內容操作符“*”,其右接的數字類型是指針類型或數組類型,它的計算就是將此指針類型的數字直接轉換成地址類型的數字而已因為指針類型的數字和地址類型的數字在數值上是相同的,僅僅計算規則不同)。

所以:

  1. b = *pA; 

返回pA對應的地址,計算此地址的值,返回類型為unsigned long*的數字2000,然後“*pA”返回類型unsigned long的地址類型的數字2000,然後計算此地址類型的數字的值,返回10,然後就只是簡單地賦值操作了。同理,對於++ *pA )由於“*”的優先級低於前綴++,所以加“)”),先計算“*pA”而返回unsigned long的地址類型的數字2000,然後計算前綴++,最後返回unsigned long的地址類型的數字2000.

如果你還是未能理解地址類型和指針類型的區別,希望下面這句能夠有用:地址類型的數字是在編譯時期給編譯器用的,指針類型的數字是在運行時期給代碼用的。如果還是不甚理解,在看過後面的類型修飾符一節後希望能有所幫助。

接本文續篇>>

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