這是一篇我所見過的關于指針的最好的入門級文章,它可使初學者在很短的時間内掌握複雜的指針操作。雖然,現在的Java、C#等語言已經取消了指針,但作為一個C++程式員,指針的直接操作記憶體,在資料操作方面有着速度快,節約記憶體等優點,仍是很多C++程式員的最愛。指針就像是一把良劍,就看你怎麼去應用它!
什麼是指針?
其實指針就像是其它變量一樣,所不同的是一般的變量包含的是實際的真實的資料,而指針是一個訓示器,它告訴程式在記憶體的哪塊區域可以找到資料。這是一個非常重要的概念,有很多程式和算法都是圍繞指針而設計的,如連結清單。
開始學習
如何定義一個指針呢?就像你定義一個其它變量一樣,隻不過你要在指針名字前加上一個星号。我們來看一個例子:
下面這個程式定義了兩個指針,它們都是指向整型資料。
int* pNumberOne;
int* pNumberTwo;
你注意到在兩個變量名前的“p”字首了嗎?這是程式員通常在定義指針時的一個習慣,以提高便程式的閱讀性,表示這是個指針。現在讓我們來初始化這兩個指針:
pNumberOne = &some_number;
pNumberTwo = &some_other_number;
&号讀作“什麼的位址”,它表示傳回的是變量在記憶體中的位址而不是變量本身的值。在這個例子中,pNumberOne 等于some_number的位址,是以現在pNumberOne指向some_number。 如果現在我們在程式中要用到some_number,我們就可以使用pNumberOne。
我們來學習一個例子:
在這個例子中你将學到很多,如果你對指針的概念一點都不了解,我建議你多看幾遍這個例子,指針是個很複雜的東西,但你會很快掌握它的。
這個例子用以增強你對上面所介紹内容的了解。它是用C編寫的(注:原英文版是用C寫的代碼,譯者重新用C++改寫寫了所有代碼,并在DEV C++ 和VC++中編譯通過!)
#include <iostream.h>
void main()
{
// 聲明變量:
int nNumber;
int *pPointer;
// 現在給它們指派:
nNumber = 15;
pPointer = &nNumber;
//列印出變量nNumber的值:
cout<<"nNumber is equal to :"<< nNumber<<endl;
// 現在通過指針改變nNumber的值:
*pPointer = 25;
//證明nNumber已經被上面的程式改變
//重新列印出nNumber的值:
cout<<"nNumber is equal to :"<<nNumber<<endl;
}
通讀一下這個程式,編譯并運作它,務必明白它是怎樣工作的。如果你完成了,準備好,開始下一小節。
陷井!
試一下,你能找出下面這段程式的錯誤嗎?
#include <iostream.h>
int *pPointer;
void SomeFunction();
{
int nNumber;
nNumber = 25;
//讓指針指向nNumber:
pPointer = &nNumber;
}
void main()
{
SomeFunction(); //為pPointer指派
//為什麼這裡失敗了?為什麼沒有得到25
cout<<"Value of *pPointer: "<<*pPointer<<endl;
}
這段程式先調用了SomeFunction函數,建立了個叫nNumber的變量,接着讓指針pPointer指向了它。可是問題出在哪兒呢?當函數結束後,nNumber被删掉了,因為這一個局部變量。局部變量在定義它的函數執行完後都會被系統自動删掉。也就是說當SomeFunction 函數傳回主函數main()時,這個變量已經被删掉,但pPointer還指着變量曾經用過的但現在已不屬于這個程式的區域。如果你還不明白,你可以再讀讀這個程式,注意它的局部變量和全局變量,這些概念都非常重要。
但這個問題怎麼解決呢?答案是動态配置設定技術。注意這在C和C++中是不同的。由于大多數程式員都是用C++,是以我用到的是C++中常用的稱謂。
動态配置設定
動态配置設定是指針的關鍵技術。它是用來在不必定義變量的情況下配置設定記憶體和讓指針去指向它們。盡管這麼說可能會讓你迷惑,其實它真的很簡單。下面的代碼就是一個為一個整型資料配置設定記憶體的例子:
int *pNumber;
pNumber = new int;
第一行聲明一個指針pNumber。第二行為一個整型資料配置設定一個記憶體空間,并讓pNumber指向這個新記憶體空間。下面是一個新例,這一次是用double雙精型:
double *pDouble;
pDouble = new double;
這種格式是一個規則,這樣寫你是不會錯的。
但動态配置設定又和前面的例子有什麼不同呢?就是在函數傳回或執行完畢時,你配置設定的這塊記憶體區域是不會被删除的是以我們現在可以用動态配置設定重寫上面的程式:
#include <iostream.h>
int *pPointer;
void SomeFunction()
{
// 讓指針指向一個新的整型
pPointer = new int;
*pPointer = 25;
}
void main()
{
SomeFunction(); // 為pPointer指派
cout<<"Value of *pPointer: "<<*pPointer<<endl;
}
通讀這個程式,編譯并運作它,務必了解它是怎樣工作的。當SomeFunction 調用時,它配置設定了一個記憶體,并讓pPointer指向它。這一次,當函數傳回時,新的記憶體區域被保留下來,是以pPointer始終指着有用的資訊,這是因為了動态配置設定。但是你再仔細讀讀上面這個程式,雖然它得到了正确結果,可仍有一個嚴重的錯誤。
配置設定了記憶體,别忘了回收
太複雜了,怎麼會還有嚴重的錯誤!其實要改正并不難。問題是:你動态地配置設定了一個記憶體空間,可它絕不會被自動删除。也就是說,這塊記憶體空間會一直存在,直到你告訴電腦你已經使用完了。可結果是,你并沒有告訴電腦你已不再需要這塊記憶體空間了,是以它會繼續占據着記憶體空間造成浪費,甚至你的程式運作完畢,其它程式運作時它還存在。當這樣的問題積累到一定程度,最終将導緻系統崩潰。是以這是很重要的,在你用完它以後,請釋放它的空間,如:
delete pPointer;
這樣就差不多了,你不得不小心。在這你終止了一個有效的指針(一個确實指向某個記憶體的指針)。
下面的程式,它不會浪費任何的記憶體:
#include <iostream.h>
int *pPointer;
void SomeFunction()
{
// 讓指針指向一個新的整型
pPointer = new int;
*pPointer = 25;
}
void main()
{
SomeFunction(); //為pPointer指派
cout<<"Value of *pPointer: "<<*pPointer<<endl;
delete pPointer;
}
隻有一行與前一個程式不同,但就是這最後一行十分地重要。如果你不删除它,你就會制造一起“記憶體漏洞”,而讓記憶體逐漸地洩漏。
(譯者:假如在程式中調用了兩次SomeFunction,你又該如何修改這個程式呢?請讀者自己思考)
傳遞指針到函數
傳遞指針到函數是非常有用的,也很容易掌握。如果我們寫一個程式,讓一個數加上5,看一看這個程式完整嗎?:
#include <iostream.h>
void AddFive(int Number)
{
Number = Number + 5;
}
void main()
{
int nMyNumber = 18;
cout<<"My original number is "<<nMyNumber<<endl;
AddFive(nMyNumber);
cout<<"My new number is "<<nMyNumber<<endl;
//得到了結果23嗎?問題出在哪兒?
}
問題出在函數AddFive裡用到的Number是變量nMyNumber的一個副本而傳遞給函數,而不是變量本身。是以, " Number = Number + 5" 這一行是把變量的副本加了5,而原始的變量在主函數main()裡依然沒變。試着運作這個程式,自己去體會一下。
要解決這個問題,我們就要傳遞一個指針到函數,是以我們要修改一下函數讓它能接受指針:把'void AddFive(int Number)' 改成 'void AddFive(int* Number)' 。下面就是改過的程式,注意函數調用時要用&号,以表示傳遞的是指針:
#include <iostream.h>
void AddFive(int* Number)
{
*Number = *Number + 5;
}
void main()
{
int nMyNumber = 18;
cout<<"My original number is "<<nMyNumber<<endl;
AddFive(&nMyNumber);
cout<<"My new number is "<<nMyNumber<<endl;
}
試着自己去運作它,注意在函數AddFive的參數Number前加*号的重要性:它告訴編譯器,我們是把指針所指的變量加5。而不并指針自己加5。
最後,如果想讓函數傳回指針的話,你可以這麼寫:
int * MyFunction();
在這句裡,MyFunction傳回一個指向整型的指針。
指向類的指針
指針在類中的操作要格外小心,你可以用如下的辦法定義一個類:
class MyClass
{
public:
int m_Number;
char m_Character;
};
接着你就可以定義一個MyClass 類的變量了:
MyClass thing;
你應該已經知道怎樣去定義一個指針了吧:
MyClass *thing;
接着你可以配置設定個記憶體空間給它:
thing = new MyClass;
注意,問題出現了。你打算怎樣使用這個指針呢,通常你可能會寫'thing.m_Number',但是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,讓它指向一個有六個元素的數組。另外一種方法,不用動态配置設定:
int *pArray;
int MyArray[6];
pArray = &MyArray[0];
注意,&MyArray[0] 也可以簡寫成 MyArray ,都表示是數組的第一個元素位址。但如果寫成pArray = &MyArray可能就會出問題,結果是 pArray 指向的是指向數組的指針(在一維數組中盡管與&MyArray[0]相等),而不是你想要的,在多元數組中很容易出錯。
在數組中使用指針
一旦你定義了一個指向數組的指針,你該怎樣使用它呢?讓我們來看一個例子,一個指向整型數組的指針:
#include <iostream.h>
void main()
{
int Array[3];
Array[0] = 10;
Array[1] = 20;
Array[2] = 30;
int *pArray;
pArray = &Array[0];
cout<<"pArray points to the value %d\n"<<*pArray<<endl;
}
如果讓指針指向數組元素中的下一個,可以用pArray++.也可以用你應該能想到的pArray + 1,都會讓指針指向數組的下一個元素。要注意的是你在移動指針時,程式并不檢查你是否已經移動地超出了你定義的數組,也就是說你很可能通過上面的簡單指針加操作而通路到數組以外的資料,而結果就是,可能會使系統崩潰,是以請格外小心。
當然有了pArray + 1,也可以有pArray - 1,這種操作在循環中很常用,特别是while循環中。
另一個需要注意的是,如果你定義了一個指向整型數的指針:int* pNumberSet ,你可以把它當作是數組,如:pNumberSet[0] 和 *pNumberSet是相等的,pNumberSet[1]與*(pNumberSet + 1)也是相等的。
在這一節的最後提一個警告:如果你用 new 動态地配置設定了一個數組,
int *pArray;
pArray = new int[6];
别忘了回收,
delete[] pArray;
這一句是告訴編譯器是删除整個數組而不一個單獨的元素。千萬記住了。
後話
還有一點要小心,别删除一個根本就沒配置設定記憶體的指針,典型的是如果沒用new配置設定,就别用delete:
void main()
{
int number;
int *pNumber = number;
delete pNumber; // 錯誤 - *pNumber 沒有用new動态配置設定記憶體.
}
常見問題解答
Q:為什麼我在編譯程式時老是在 new 和 delete語句中出現'symbol undefined' 錯誤?
A:new 和 delete都是C++在C上的擴充,這個錯誤是說編譯器認為你現在的程式是C而不C++,當然會出錯了。看看你的檔案名是不是.cpp結尾。
Q:new 和 malloc有什麼不同?
A:new 是C++中的關健字,用來配置設定記憶體的一個标準函數。如果沒有必要,請不要在C++中使用malloc。因為malloc是C中的文法,它不是為面向對象的C++而設計的。
Q:我可以同時使用free 和 delete嗎?
A:你應該注意的是,它們各自所比對的操作不同。free隻用在用malloc配置設定的記憶體操作中,而delete隻用在用new配置設定的記憶體操作中。
引用(寫給某些有能力的讀者)
這一節的内容不是我的這篇文章的中心,隻是供某些有能力的讀者參考。
有些讀者經常問我關于引用和指針的問題,這裡我簡要地讨論一下。
在前面指針的學習中,我們知道(&)是讀作“什麼的位址”,但在下面的程式中,它是讀作“什麼的引用”
int& Number = myOtherNumber;
Number = 25;
引用有點像是一個指向myOtherNumber的指針,不同的是它是自動删除的。是以他比指針在某些場合更有用。與上面等價的代碼是:
int* pNumber = &myOtherNumber;
*pNumber = 25;
指針與引用另一個不同是你不能修改你已經定義好的引用,也就是說你不能改變它在聲明時所指的内容。舉個例子:
int myFirstNumber = 25;
int mySecondNumber = 20;
int &myReference = myFirstNumber;
myReference = mySecondNumber;//這一步能使myReference 改變嗎?
cout<<myFristNumber<<endl;//結果是20還是25?
當在類中操作時,引用的值必須在構造函數中設定,例:
CMyClass::CMyClass(int &variable) : m_MyReferenceInCMyClass(variable)
{
// constructor code here
}
總結
這篇文章開始可能會較難掌握,是以最好是多讀幾遍。有些讀者暫時還不能了解,在這兒我再做一個簡要的總結:
指針是一個指向記憶體區域的變量,定義時在變量名前加上星号(*)(如:int *number)。
你可以得到任何一個變量的位址,隻在變量名前加上&(如:pNumber = &my_number)。
你可以用'new' 關鍵字動态配置設定記憶體。指針的類型必須與它所指的變量類型一樣(如:int *number 就不能指向 MyClass)。
你可以傳遞一個指針到函數。必須用'delete'删除你動态配置設定的記憶體。
你可以用&array[0]而讓指針指向一個數組。
你必須用delete[]而不是delete來删除動态配置設定的數組。
文章到這兒就差不多結束了,但這些并不就是指針所有的東西,像指向指針的指針等我還沒有介紹,因為這些東西對于一個初學指針的人來說還太複雜了,我不能讓讀者一開始就被太複雜的東西而吓走了。好了,到這兒吧,試着運作我上面寫的小程式,也多自己寫寫程式,你肯定會進步不小的!