我們已經明白變量其實是可以由標識來存取的內存單元。但這些變量實際上是存儲在內存中具體的位置上的。對我們的程序來說,計算機內存只是一串連續的單字節單元(1byte cell),即最小數據單位,每一個單元有一個唯一地址。
計算機內存就好像城市中的街道。在一條街上,所有的房子被順序編號,每所房子有唯一編號。因此如果我們說芝麻街27號,我們很容易找到它,因為只有一所房子會是這個編號,而且我們知道它會在26號和28號之間。
同房屋按街道地址編號一樣,操作系統(operating system)也按照唯一順序編號來組織內存。因此,當我們說內存中的位置1776,我們知道內存中只有一個位置是這個地址,而且它在地址1775和1777之間。
當我們聲明一個變量的同時,它必須被存儲到內存中一個具體的單元中。通常我們並不會指定變量被存儲到哪個具體的單元中—幸虧這通常是由編譯器和操作系統自動完成的,但一旦操作系統指定了一個地址,有些時候我們可能會想知道變量被存儲在哪裡了。
這可以通過在變量標識前面加與符號ampersand sign (&)來實現,它表示"...的地址" ("address of"),因此稱為地址操作符(adress operator),又稱去引操作符(dereference operator)。例如:
ted = &andy;
將變量andy的地址賦給變量ted,因為當在變量名稱andy 前面加ampersand (&) 符號,我們指的將不再是該變量的內容,而是它在內存中的地址。
假設andy 被放在了內存中地址1776的單元中,然後我們有下列代碼:
andy = 25;其結果顯示在下面的圖片中:
我們將變量andy 的值賦給變量fred,這與以前我們看到很多例子都相同,但對於ted,我們把操作系統存儲andy的內存地址賦給它,我們想像該地址為1776 (它可以是任何地址,這裡只是一個假設的地址),原因是當給ted 賦值的時候,我們在andy 前面加了ampersand (&) 符號。
存儲其它變量地址的變量(如上面例子中的ted ),我們稱之為指針(pointer)。在C++ 中,指針pointers 有其特定的優點,因此經常被使用。在後面我們將會看到這種變量如何被聲明。
使用指針的時候,我們可以通過在指針標識的前面加星號asterisk (*)來存儲該指針指向的變量所存儲的數值,它可以被翻譯為“所指向的數值”("value pointed by")。因此,仍用前面例子中的數值,如果我們寫: beth = *ted; (我們可以讀作:"beth 等與ted所指向的數值") beth 將會獲得數值25,因為ted 是1776,而1776 所指向的數值為25。
你必須清楚的區分ted 存儲的是1776,但*ted (前面加asterisk * ) 指的是地址1776中存儲的數值,即25。注意加或不加星號*的不同(下面代碼中注釋顯示了如何讀這兩個不同的表達式):
beth = ted;// beth 等於 ted ( 1776 )
地址或反引用操作符Operator of address or dereference (&)
它被用作一個變量前綴,可以被翻譯為“…的地址”("address of"),因此:&variable1 可以被讀作 variable1的地址("address of variable1" )。
引用操作符Operator of reference (*)
它表示要取的是表達式所表示的地址指向的內容。它可以被翻譯為“…指向的數值” ("value pointed by")。
* mypointer 可以被讀作 "mypointer指向的數值"。
繼續使用上面開始的例子,看下面的代碼:
andy = 25;現在你應該可以清楚的看到以下等式全部成立:
andy == 25第一個表達式很容易理解,因為我們有賦值語句andy=25;。第二個表達式使用了地址(或反引用)操作符(&) 來返回變量andy的地址,即 1776。第三個表達式很明顯成立,因為第二個表達式為真,而我們給ted賦值的語句為ted = &andy;。第四個表達式使用了引用操作符 (*),相當於ted指向的地址中存儲的數值,即25。
由此你也可以推斷出,只要ted 所指向的地址中存儲的數值不變,以下表達式也為真:
*ted == andy
由於指針可以直接引用它所指向的數值,因此有必要在聲明指針的時候指明它所指向的數據類型。指向一個整型int或浮點型float數據的指針與指向一個字符型char數據的指針並不相同。
因此,聲明指針的格式如下:
type * pointer_name;
這裡,type 是指針所指向的數據的類型,而不是指針自己的類型。例如:
int * number;它們是3個指針的聲明,每一個指針指向一種不同數據類型。這三個指針本身其實在內存中占用同樣大小的內存空間(指針的大小取決於不同的操作系統),但它們所指向的數據是不同的類型,並占用不同大小的內存空間,一個是整型int,一個是字符型char ,還有一個是浮點型float。
需要強調的一點是,在指針聲明時的星號asterisk (*) 僅表示這裡聲明的是一個指針,不要把它和前面我們用過的引用操作符混淆,雖然那也是寫成一個星號 (*)。它們只是用同一符號表示的兩個不同任務。
// my first pointer注意變量value1 和 value2 是怎樣間接的被改變數值的。首先我們使用 ampersand sign (&) 將value1的地址賦給mypointer 。然後我們將10 賦給 mypointer所指向的數值,它其實指向value1的地址,因此,我們間接的修改了value1的數值。
為了讓你了解在同一個程序中一個指針可以被用作不同的數值,我們在這個程序中用value2 和同一個指針重復了上面的過程。
下面是一個更復雜一些的例子:
// more pointers上面每一行都有注釋說明代碼的意思:ampersand (&) 為"address of",asterisk (*) 為 "value pointed by"。注意有些包含p1 和p2 的表達式不帶星號。加不加星號的含義十分不同:星號(*)後面跟指針名稱表示指針所指向的地方,而指針名稱不加星號(*)表示指針本身的數值,即它所指向的地方的地址。
另一個需要注意的地方是這一行:
int *p1, *p2;
聲明了上例用到的兩個指針,每個帶一個星號(*),因為是這一行定義的所有指針都是整型int (而不是 int*)。原因是引用操作符(*) 的優先級順序與類型聲明的相同,因此,由於它們都是向右結合的操作,星號被優先計算。我們在section 1.3: Operators中已經討論過這些。注意在聲明每一個指針的時候前面加上星號asterisk (*)。
數組的概念與指針的概念聯系非常解密。其實數組的標識相當於它的第一個元素的地址,就像一個指針相當於它所指向的第一個元素的地址,因此其實它們是同一個東西。例如,假設我們有以下聲明:
int numbers [20];下面的賦值為合法的:
p = numbers;
這裡指針p 和numbers 是等價的,它們有相同的屬性,唯一的不同是我們可以給指針p賦其它的數值,而numbers 總是指向被定義的20個整數組中的第一個。所以,p只是一個普通的指針變量,而與之不同,numbers 是一個指針常量(constant pointer),數組名的確是一個指針常量。因此雖然前面的賦值表達式是合法的,但下面的不是:
numbers = p;
因為numbers 是一個數組(指針常量),常量標識不可以被賦其它數值。
由於變量的特性,以下例子中所有包含指針的表達式都是合法的:
// more pointers在數組一章中我們使用了括號[]來指明我們要引用的數組元素的索引(index)。中括號[]也叫位移(offset)操作符,它相當於在指針中的地址上加上括號中的數字。例如,下面兩個表達式互相等價:
a[5] = 0;// a [offset of 5] = 0不管a 是一個指針還是一個數組名, 這兩個表達式都是合法的。
當聲明一個指針的時候我們可能需要同時指定它們指向哪個變量,
int number;這相當於:
int number;當給一個指針賦值的時候,我們總是賦給它一個地址值,而不是它所指向數據的值。你必須考慮到在聲明一個指針的時候,星號 (*) 只是用來指明它是指針,而從不表示引用操作符reference operator (*)。記住,它們是兩種不同操作,雖然它們寫成同樣的符號。因此,我們要注意不要將以上的代碼與下面的代碼混淆:
int number;這些代碼也沒有什麼實際意義。
在定義數組指針的時候,編譯器允許我們在聲明變量指針的同時對數組進行初始化,初始化的內容需要是常量,例如:
char * terry = "hello";
在這個例子中,內存中預留了存儲"hello" 的空間,並且terry被賦予了向這個內存塊的第一個字符(對應’h’)的指針。假設"hello"存儲在地址1702,下圖顯示了上面的定義在內存中狀態:
這裡需要強調,terry 存儲的是數值1702 ,而不是'h' 或 "hello",雖然1702 指向這些字符。
指針terry 指向一個字符串,可以被當作數組一樣使用(數組只是一個常量指針)。例如,如果我們的心情變了,而想把terry指向的內容中的字符'o' 變為符號'!' ,我們可以用以下兩種方式的任何一種來實現:
terry[4] = '!';記住寫 terry[4] 與*(terry+4)是一樣的,雖然第一種表達方式更常用一些。以上兩個表達式都會實現以下改變:
對指針進行數學運算與其他整型數據類型進行數學運算稍有不同。首先,對指針只有加法和減法運算,其它運算在指針世界裡沒有意義。但是指針的加法和減法的具體運算根據它所指向的數據的類型的大小的不同而有所不同。
我們知道不同的數據類型在內存中占用的存儲空間是不一樣的。例如,對於整型數據,字符char 占用1 的字節(1 byte),短整型short 占用2 個字節,長整型long 占用4個字節。
假設我們有3個指針:
char *mychar;而且我們知道他們分別指向內存地址1000, 2000 和3000。
因此如果我們有以下代碼:
mychar++;就像你可能想到的,mychar的值將會變為1001。而myshort 的值將會變為2002,mylong的值將會變為3004。原因是當我們給指針加1時,我們實際是讓該指針指向下一個與它被定義的數據類型的相同的元素。因此,它所指向的數據類型的長度字節數將會被加到指針的數值上。以上過程可以由下圖表示:
這一點對指針的加法和減法運算都適用。如果我們寫以下代碼,它們與上面例子的作用一抹一樣: mychar = mychar + 1;
myshort = myshort + 1;這裡需要提醒你的是,遞增 (++) 和遞減 (--) 操作符比引用操作符reference operator (*)有更高的優先級,因此,以下的表達式有可能引起歧義:
*p++;第一個表達式等同於*(p++) ,它所作的是增加p (它所指向的地址,而不是它存儲的數值)。
在第二個表達式中,因為兩個遞增操作(++) 都是在整個表達式被計算之後進行而不是在之前,所以*q 的值首先被賦予*p ,然後q 和p 都增加1。它相當於:
*p = *q;像通常一樣,我們建議使用括號()以避免意想不到的結果。
C++ 允許使用指向指針的指針。要做到這一點,我們只需要在每一層引用之前加星號(*)即可:
char a;假設隨機選擇內存地址為7230, 8092 和10502,以上例子可以用下圖表示:
(方框內為變量的內容;方框下面為內存地址)
這個例子中新的元素是變量c,關於它我們可以從3個方面來討論,每一個方面對應了不同的數值:
c 是一個(char **)類型的變量,它的值是8092
*c 是一個(char*)類型的變量,它的值是7230
**c 是一個(char)類型的變量,它的值是'z'
指針void 是一種特殊類型的指針。void 指針可以指向任意類型的數據,可以是整數,浮點數甚至字符串。唯一個限制是被指向的數值不可以被直接引用(不可以對它們使用引用星號*),因為它的長度是不定的,因此,必須使用類型轉換操作或賦值操作來把void 指針指向一個具體的數據類型。
它的應用之一是被用來給函數傳遞通用參數:
// integer increasersizeof 是C++的一個操作符,用來返回其參數的長度字節數常量。例如,sizeof(char) 返回 1,因為 char 類型是1字節長數據類型。
C++ 允許對指向函數的指針進行操作。它最大的作用是把一個函數作為參數傳遞給另外一個函數。聲明一個函數指針像聲明一個函數原型一樣,除了函數的名字需要被括在括號內並在前面加星號asterisk (*)。例如:
// pointer to functions在這個例子裡, minus 是一個全局指針,指向一個有兩個整型參數的函數,它被賦值指向函數subtraction,所有這些由一行代碼實現:
int (* minus)(int,int) = subtraction;
這裡似乎解釋的不太清楚,有問題問為什麼(int int)只有類型,沒有參數,就再多說兩句。
這裡 int (*minus)(int int)實際是在定義一個指針變量,這個指針的名字叫做minus,這個指針的類型是指向一個函數,函數的類型是有兩個整型參數並返回一個整型值。
整句話“int (*minus)(int,int) = subtraction;”是定義了這樣一個指針並把函數subtraction的值賦給它,也就是說有了這個定義後minus就代表了函數subtraction。因此括號中的兩個int int實際只是一種變量類型的聲明,也就是說是一種形式參數而不是實際參數。