原著:http://www.codeproject.com/cpp/pointers.asp
什麼是指標?
和其它變數一樣,指標是基本的變數,所不同的是指標包含一個實際的資料,該資料代表一個可以找到實際資訊的記憶體位址。這是一個非常重要的概念。許多程式和思想依靠指標作為他們設計的基礎。
開始
怎樣定義一個指標呢?除了你需要在變數的名稱前面加一個星號外,其它的和別的變數定義一樣。舉個例子,以下代碼定義了兩個指標變數,它們都指向一個整數。
int* pNumberOne;
int* pNumberTwo;
注意到兩個變數名稱前的首碼’p’了嗎?這是一個慣例,用來表示這個變數是個指標。
現在,讓我們將這些指標實際的指向某些東西:
pNumberOne = &some_number;
pNumberTwo = &some_other_number;
‘&’符號應該讀作”什麼什麼的地址”,它返回一個變數在記憶體中的地址,設定到左側的變數中。因此,在這個例子中,pNumberOne設定和some_number的地址相同,因此pNumberOne現在指向some_number。
現在,如果我們想訪問some_number的地址,可以使用pNumberOne。如果我們想通過pNumberOne訪問some_number的值,那麼應該用*pNumberOne。這個星號表示解除指標的參照,應該讀作“什麼什麼指向的記憶體地區”。
到現在我們學到了什嗎?舉個例子
喲,有許多東西需要理解。我的建議是,如果你有哪個概念沒有弄清楚的話,那麼,不妨再看一遍。指標是個複雜的對象,可能需要花費一段時間來掌握它。
這兒有一個例子示範上面所將的概念。這是用C寫的,沒有C++擴充。
#include <stdio.h>
void main()
{
// 申明變數
int nNumber;
int *pPointer;
//賦值
nNumber = 15;
pPointer = &nNumber;
// 輸出nNumber的值
printf("nNumber is equal to : %d/n", nNumber);
// 通過pPointer修改nNumber的值
*pPointer = 25;
// 證明nNumber已經被改變了
// 再次列印nNumber的值
printf("nNumber is equal to : %d/n", nNumber);
}
通讀一遍,並且編譯範例代碼,確信你理解了它為什麼這樣工作。如果你準備好了,那麼繼續。
一個陷阱!
看看你能否發現下面這段程式的毛病:
#include <stdio.h>
int *pPointer;
void SomeFunction();
{
int nNumber;
nNumber = 25;
//將pPointer指向nNumber
pPointer = &nNumber;
}
void main()
{
SomeFunction(); //用pPointer做些事情
// 為什麼會失敗?
printf("Value of *pPointer: %d/n", *pPointer);
}
這段程式先調用SomeFunction函數,該函數建立一個叫做nNumber的變數,並將pPointer指向它。那麼,問題是,當函數退出時,nNumber被刪除了,因為它是一個局部變數。當程式執行到局部變數定義的程式塊以外時,局部變數總是被刪除了。這就意味著,當SomeFunction函數返回到main函數時,局部變數將被刪除,因此pPointer將指向原先nNumber的地址,但這個地址已經不再屬於這段程式了。如果你不理解這些,那麼重新閱讀一遍關於局部變數和全域變數的作用範圍是明智的選擇。這個概念也是非常重要的。
那麼,我們如何解決這個問題呢?答案是使用大家都知道的一個方法:動態分配。請明白C和C++的動態分配是不同的。既然現在大多數程式員都使用C++,那麼下面這段代碼就是常用的了。
動態分配
動態分配可以說是指標的關鍵所在。不需要通過定義變數,就可以將指標指向分配的記憶體。也許這個概念看起來比較模糊,但是確實比較簡單。下面的代碼示範如何為一個整數分配記憶體:
int *pNumber;
pNumber = new int;
第一行申明了一個指標pNumber,第二行分配一個整數記憶體,並且將pNumber指向這個新記憶體。下面是另一個例子,這次用一個浮點數:
double *pDouble;
pDouble = new double;
動態分配有什麼不同的呢?當函數返回或者程式運行到當前塊以外時,你動態分配的記憶體將不會被刪除。因此,如果我們用動態分配重寫上面的例子,可以看到現在能夠正常工作了。
#include <stdio.h>
int *pPointer;
void SomeFunction()
{
// make pPointer point to a new integer
pPointer = new int;
*pPointer = 25;
}
void main()
{
SomeFunction(); // make pPointer point to something
printf("Value of *pPointer: %d/n", *pPointer);
}
通讀一遍,編譯上面的代碼,確信你已經理解它是如何工作的。當調用SomeFunction時,分配了一些記憶體,並且用pPointer指向它。這次,當函數返回時,新記憶體就完整無缺了。因此pPointer仍舊指向有用的東西。這是因為使用了動態分配。確信你已經理解它了。那麼繼續向下看,瞭解為什麼上面的程式還會有一系列的錯誤。
記憶體配置和記憶體釋放
這裡有一個問題,可能會變得十分嚴重,雖然它很容易補救。這個問題就是,雖然你用動態分配可以方便的讓記憶體完整無缺,確實不會自動刪除,除非你告訴電腦,你不再需要這塊記憶體了,否則記憶體將一直被分配著。因此結果就是,如果你不告訴電腦你已經使用完這塊記憶體,那麼它將成為被浪費的空間,因為其它程式或者你的應用程式的其它部分不能使用這塊記憶體。最終將導致系統因為記憶體耗盡而崩潰。因此這個問題相當重要。記憶體使用量完後釋放非常容易:
delete pPointer;
需要做的就是這些。但是你必須確定,你刪除的是一個指向你實際分配的記憶體的指標,而不是其它任何垃圾。嘗試用delete已經釋放的記憶體是危險的,並且可能導致程式崩潰。
這裡再次舉個例子,這次修改以後就不會有記憶體浪費了。
#include <stdio.h>
int *pPointer;
void SomeFunction()
{
// make pPointer point to a new integer
pPointer = new int;
*pPointer = 25;
}
void main()
{
SomeFunction(); // make pPointer point to something
printf("Value of *pPointer: %d/n", *pPointer);
delete pPointer;
}
只有一行不同,但這行是要點。如果你不刪除記憶體,就會導致“記憶體流失”,記憶體將逐漸減少,除非應用程式重新啟動,否則將不能再生。
向函數傳遞指標
傳遞指標給函數非常有用,但不容易掌握。如果我們寫一個程式,傳遞一個數值並且給它加上5,我們也許會寫出如下的程式:
#include <stdio.h>
void AddFive(int Number)
{
Number = Number + 5;
}
void main()
{
int nMyNumber = 18;
printf("My original number is %d/n", nMyNumber);
AddFive(nMyNumber);
printf("My new number is %d/n", nMyNumber);
}
但是,程式中函數AddFive的參數Number只是變數nMyNumber的一個拷貝,而不是變數本身,因此,Number = Number + 5隻是為變數的拷貝增加了5,而不是最初的在main()函數中的變數。當然,你可以運行程式,以證明這一點。
為了將值傳遞出去,我們可以傳遞這個變數的指標到函數中,但我們需要修改一下函數,以便傳遞數值的指標而不是數值。因此將void AddFive(int Number)修改為void AddFive(int *Number),增加了一個星號。下面是修改了的函數,注意,我們必須確認傳遞了nMyNumber的地址,而不是它本身。這通過增加&符號來完成,通常讀作“什麼什麼的地址”。
#include <stdio.h>
void AddFive(int* Number)
{
*Number = *Number + 5;
}
void main()
{
int nMyNumber = 18;
printf("My original number is %d/n", nMyNumber);
AddFive(&nMyNumber);
printf("My new number is %d/n", nMyNumber);
}
大家可以試著自己做個例子來實驗一下。注意在AddFive函數中Number變數前那個重要的星號。只是必須的,用來告訴編譯器我們想將5加到變數Number指向的數值,而不是將5加到指標本身。
關於函數最後需要注意的是你也可以返回一個指標。比如:
int * MyFunction();
在這個例子中,MyFunction函數返回一個指向整數的指標。
類的指標
關於指標還有兩個需要注意的問題。其中一個是結構或者類。你可以如下定義一個類:
class MyClass
{
public:
int m_Number;
char m_Character;
};
然後,你可以如下方式定義一個類變數:
MyClass thing;
你應該已經知道這些了,如果還不知道的話,那麼再將上面的內容讀一遍。定義MyClass的指標應該這麼寫:
MyClass *thing;
然後你需要分配記憶體,並將指標指向這個記憶體
thing = new MyClass;
問題來了,你如何使用這個指標呢?一般的,我們寫thing.m_Number,但你不能對指標用’.’操作,因為thing不是一個MyClass對象。只是指向一個MyClass對象的指標。因此,指標thing不包含m_Number這個變數。只是它指向的結構中包含這個變數。因此,我們必須使用一個不同的協定,用->取代’.’。以下是一個例子:
class MyClass
{
public:
int m_Number;
char m_Character;
};
void main()
{
MyClass *pPointer;
pPointer = new MyClass;
pPointer->m_Number = 10;
pPointer->m_Character = 's';
delete pPointer;
}
數組的指標
你也可以構造一個指向數組的指標,如下:
int *pArray;
pArray = new int[6];
將建立一個叫做pArray的指標,指向一個包含6個元素的數組。另一種構造的方法是使用動態分配,如下:
int *pArray;
int MyArray[6];
pArray = &MyArray[0];
注意,你這裡也可以不用&MyArray[0],而直接使用&MyArray取代。當然,這僅僅適用於數組。
使用指向數組的指標
一旦你有了指向數組的指標,那麼如何使用它呢?現在假設你有一個指向整數數組的指標,那麼指標開始時將指向第一個整數。舉例如下:
#include <stdio.h>
void main()
{
int Array[3];
Array[0] = 10;
Array[1] = 20;
Array[2] = 30;
int *pArray;
pArray = &Array[0];
printf("pArray points to the value %d/n", *pArray);
}
將指標移到指向數組的下一個值,可以用pArray++。也許你也可以猜出來了,我們可以用pArray+2的方式將指標向後移動兩個位置。要注意的問題是,你自己必須知道數組的上限是多少(例子中是3),因為編譯器不能檢查你是否將指標移到了數組以外,因此你可以很容易的將系統搞崩潰了。以下是個例子,顯示我們設定的三個值:
#include <stdio.h>
void main()
{
int Array[3];
Array[0] = 10;
Array[1] = 20;
Array[2] = 30;
int *pArray;
pArray = &Array[0];
printf("pArray points to the value %d/n", *pArray);
pArray++;
printf("pArray points to the value %d/n", *pArray);
pArray++;
printf("pArray points to the value %d/n", *pArray);
}
你也可以使用pArray-2這樣的方式來向前移動2個位置。不管是加或者減,你必須保證不是對指標所指向的資料的操作。這種操作指標和數組的方式在迴圈中是最常用的。例如for和while迴圈。
另外要提的是,如果你有一個指標比如int pNumberSet,你也可以把它當成數組。例如pNumberSet[0]等於*pNumberSet,並且pNumberSet[1]等於*(pNumberSet+1)。
對於數組,還有一點要注意的,如果你用new為數組分配記憶體,比如:
int *pArray;
pArray = new int[6];
你必須用以下方式進行刪除:
delete[] pArray;
注意delete後的[],它告訴編譯器,這是刪除整個數組,而不僅僅是第一個元素。對於數組你必須使用這種方法,否則就會有記憶體流失。
總結
一條要注意的:你不能刪除不是用new分配的記憶體。比如以下例子:
void main()
{
int number;
int *pNumber = number;
delete pNumber; // wrong - *pNumber wasn't allocated using new.
}