何為指針?
指針基本上和其它的變量一樣,唯一的一點(diǎn)不同就是指針并不包含實(shí)際的數(shù)據(jù),而是包含了一個(gè)指向內(nèi)存位置的地址,你可以在這個(gè)地址找到某些信息。這是一個(gè)很重要的概念,并且許多程序或者思想都是將指針作為它們的設(shè)計(jì)基礎(chǔ),例如鏈表。
開(kāi)始
如何定義一個(gè)指針?呃,就像定義其它的變量一樣,不過(guò)你還需要在變量名之前添加一個(gè)星號(hào)。例如,下面的代碼創(chuàng)建了兩個(gè)指向整數(shù)的指針:
int* pNumberOne;
int* pNumberTwo;
注意到變量名的前綴“p”了嗎?這是編寫(xiě)代碼的一個(gè)習(xí)慣,用來(lái)表示這個(gè)變量是一個(gè)指針。
現(xiàn)在,讓我們把這些指針指向一些實(shí)際的值吧:
pNumberOne = &some_number;
pNumberTwo = &some_other_number;
“&”標(biāo)志應(yīng)該讀作“the address
of(……的地址)”,它的作用是返回一個(gè)變量的內(nèi)存地址,而不是這個(gè)變量本身。那么在這個(gè)例子中,pNumberOne就是some_number的地
址,亦稱作pNumberOne指向some_number。
現(xiàn)在,如果我們想使用some_number的地址的話,那么我們就可以使用
pNumberOne了。如果我們希望經(jīng)由pNumberOne而使用some_number的值的話,我們可以用*pNumberOne。“*”應(yīng)該讀
作“the memory location pointed to
by(由……指向的內(nèi)存位置)”,它用來(lái)取得指針?biāo)赶虻闹怠2贿^(guò)指針聲明的情況例外,如“int *pNumber”。
到現(xiàn)在都學(xué)到什么了(一個(gè)例子):
咻!要理解的東西太多了,所以在此我建議,如果你還是不理解以上的概念的話,那么最好再通讀一遍;指針是一個(gè)復(fù)雜的主題,要掌握它是要花些時(shí)間的。
這里有一個(gè)示例,解說(shuō)了上面討論的那些概念。它是由C編寫(xiě)成,并不帶有C++的那些擴(kuò)展。
#include <stdio.h>
void main()
{
// 聲明變量:
int nNumber;
int *pPointer;
// 現(xiàn)在,給它們賦值:
nNumber = 15;
pPointer = &nNumber;
// 打印nNumber的值:
printf("nNumber is equal to : %d\n", nNumber);
// 現(xiàn)在,通過(guò)pPointer來(lái)控制nNumber:
*pPointer = 25;
// 證明經(jīng)過(guò)上面的代碼之后,nNumber的值已經(jīng)改變了:
printf("nNumber is equal to : %d\n", nNumber);
}
請(qǐng)通讀并編譯以上代碼,并確信你已經(jīng)弄懂了它是如何工作的。然后,當(dāng)你準(zhǔn)備好了以后,就往下讀吧!
陷阱!
看看你是否能指出以下程序的缺陷:
#include <stdio.h>
int *pPointer;
void SomeFunction()
{
int nNumber;
nNumber = 25;
// 使pPointer指向nNumber:
pPointer = &nNumber;
}
void main()
{
SomeFunction(); // 讓pPointer指向某些東西
// 為什么這樣會(huì)失敗?
printf("Value of *pPointer: %d\n", *pPointer);
}
這個(gè)程序首先調(diào)用SomeFunction函數(shù),在其中創(chuàng)建了一個(gè)名為nNumber的變量,并且使pPointer指向這個(gè)變量。那么,這就是問(wèn)題之
所在了。當(dāng)函數(shù)結(jié)束的時(shí)候,由于nNumber是一個(gè)本地變量,那么它就會(huì)被銷(xiāo)毀。這是因?yàn)楫?dāng)語(yǔ)句塊結(jié)束的時(shí)候,塊中定義的本地變量都會(huì)被銷(xiāo)毀。這就意味
著當(dāng)SomeFunction返回到main()的時(shí)候,那個(gè)變量就已經(jīng)被銷(xiāo)毀了,所以pPointer將會(huì)指向一個(gè)不再屬于本程序的內(nèi)存位置。如果你不
懂這一點(diǎn),那么你應(yīng)該去讀一讀有關(guān)本地變量、全局變量以及作用域的東西,這些概念非常重要。
那么,如何解決這個(gè)問(wèn)題呢?答案是使用一種名為動(dòng)態(tài)分配的技術(shù)。請(qǐng)注意:在這一點(diǎn)上,C和C++是不同的。既然大多數(shù)開(kāi)發(fā)者正在使用C++,那么下面的代碼就使用C++來(lái)編寫(xiě)。
動(dòng)態(tài)分配
動(dòng)態(tài)分配也許可以算是指針的關(guān)鍵技術(shù)了。它被用于在沒(méi)有定義變量的情況下分配內(nèi)存,然后由一個(gè)指針指向這段內(nèi)存。雖然這個(gè)概念好像很讓人糊涂,其實(shí)它很簡(jiǎn)單。以下的代碼解說(shuō)了如何為一個(gè)整數(shù)分配內(nèi)存空間:
int *pNumber;
pNumber = new int;
第一行代碼聲明了一個(gè)指針pNumber,第二行代碼分配了一個(gè)整數(shù)的空間,并使pNumber指向這一段新分配的內(nèi)存。下面是另外一個(gè)例子,這一次使用了一個(gè)double:
double *pDouble;
pDouble = new double;
這些規(guī)則是相同的T,所以你應(yīng)該可以很容易地掌握。
動(dòng)態(tài)分配和本地變量的不同點(diǎn)是:你分配的內(nèi)存在函數(shù)返回和語(yǔ)句塊結(jié)束的時(shí)候不會(huì)被釋放,所以,如果你用動(dòng)態(tài)分配來(lái)重新編寫(xiě)上面的代碼,那么它就會(huì)正常工作了:
#include <stdio.h>
int *pPointer;
void SomeFunction()
{
// 使pPointer指向一個(gè)new的整數(shù)
pPointer = new int;
*pPointer = 25;
}
void main()
{
SomeFunction(); // 讓pPointer指向某些東西
printf("Value of *pPointer: %d\n", *pPointer);
}
請(qǐng)通讀并編譯以上的示例代碼,并確信你已經(jīng)弄懂了它為何如此工作。當(dāng)調(diào)用SomeFunction的時(shí)候,它分配了一段內(nèi)存,并使pPointer指向
這段內(nèi)存。這一次當(dāng)函數(shù)返回的時(shí)候,這段new的內(nèi)存就會(huì)完好保留,所以pPointer仍然指向某些有用的內(nèi)容。這就是動(dòng)態(tài)分配了!請(qǐng)確信你已經(jīng)搞懂了
這一點(diǎn),然后繼續(xù)閱讀關(guān)于這段代碼中的一個(gè)嚴(yán)重錯(cuò)誤。
來(lái)得明白,去得明白
還有一個(gè)復(fù)雜的因素,并且是十分嚴(yán)重的——雖然它很好補(bǔ)救。問(wèn)題是你分配的內(nèi)存在離開(kāi)的時(shí)候雖然仍然完好,但是這段內(nèi)存永遠(yuǎn)也不會(huì)自動(dòng)銷(xiāo)毀。這
就是說(shuō),如果你不通知電腦結(jié)束使用的話,這段內(nèi)存就會(huì)一直存在下去,這樣做的結(jié)果就是內(nèi)存的浪費(fèi)。最終,系統(tǒng)就會(huì)因?yàn)閮?nèi)存耗盡而崩潰。所以,這是相當(dāng)重要
的一個(gè)問(wèn)題。當(dāng)你使用完內(nèi)存之后,釋放它的代碼非常簡(jiǎn)單:
delete pPointer;
這一切就這么簡(jiǎn)單。不管怎樣,在你傳遞一個(gè)有效的指針——亦即一個(gè)指向一段你已經(jīng)分配好的內(nèi)存指針,而不是那些老舊的垃圾內(nèi)存——的時(shí)候,你都需要無(wú)比細(xì)心。嘗試delete一段已經(jīng)釋放的內(nèi)存是十分危險(xiǎn)的,這可能會(huì)導(dǎo)致你的程序崩潰。
好了,下面又是那個(gè)例子,這一次它就不會(huì)浪費(fèi)內(nèi)存了:
#include <stdio.h>
int *pPointer;
void SomeFunction()
{
// 使pPointer指向一個(gè)new的整數(shù)
pPointer = new int;
*pPointer = 25;
}
void main()
{
SomeFunction(); // 讓pPointer指向某些東西
printf("Value of *pPointer: %d\n", *pPointer);
delete pPointer;
}
唯一的一行不同也就是最本質(zhì)的一點(diǎn)。如果你不將內(nèi)存delete掉,你的程序就會(huì)得到一個(gè)“內(nèi)存泄漏”。如果出現(xiàn)了內(nèi)存泄漏,那么除非你關(guān)閉應(yīng)用程序,否則你將無(wú)法重新使用這段泄漏的內(nèi)存。
向函數(shù)傳遞指針
向函數(shù)傳遞指針的技術(shù)非常有用,但是它很容易掌握(譯注:這里存在必然的轉(zhuǎn)折關(guān)系嗎?呃,我看不出來(lái),但是既然作者這么寫(xiě)了,我又無(wú)法找出一個(gè)合適的關(guān)聯(lián)詞,只好按字面翻譯了)。如果我們要編寫(xiě)一段程序,在其中要把一個(gè)數(shù)增加5,我們可能會(huì)像這么寫(xiě):
#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是傳遞到這個(gè)函數(shù)中的nMyNumber的一份拷貝,而不是nMyNumber本身。因此,
“Number = Number +
5”這一行則是向這份拷貝加上了5,而main()中的原始變量并沒(méi)有任何變化。你可以運(yùn)行這個(gè)程序試著證明這一點(diǎn)。
對(duì)于這個(gè)程序,我們可以
向函數(shù)傳遞這個(gè)數(shù)字內(nèi)存地址的指針。這樣,我們就需要修改這個(gè)函數(shù),使之能接收一個(gè)指向整數(shù)的指針。于是,我們可以添加一個(gè)星號(hào),即把“void
AddFive(int Number)”改為“void AddFive(int*
Number)”。下面是這個(gè)修改過(guò)了的程序,注意到我們已經(jīng)將nMyNumber的地址(而不是它本身)傳遞過(guò)去了嗎?此處改動(dòng)是添加了一個(gè)
“&”符號(hào),它讀作(你應(yīng)該回憶起來(lái)了)“the address of(……的地址)”。
#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);
}
你可以試著自己編寫(xiě)一個(gè)程序來(lái)證明這一點(diǎn)。注意到AddFive函數(shù)中Number之前的“*”的重要性了嗎?這就是告知編譯器我們要在指針Number指向的數(shù)字上加5,而不是向指針本身加5。
最后要注意的一點(diǎn)是,你亦可以在函數(shù)中返回指針,像下面這個(gè)樣子:
int * MyFunction();
在這個(gè)例子中,MyFunction返回了一個(gè)指向整數(shù)的指針。
指向類(lèi)的指針
關(guān)于指針,我還有還有兩點(diǎn)需要提醒你。其中之一是指向結(jié)構(gòu)或類(lèi)的指針。你可以像這樣定義一個(gè)類(lèi):
class MyClass
{
public:
int m_Number;
char m_Character;
};
然后,你可以定義一個(gè)MyClass的變量:
MyClass thing;
你應(yīng)該已經(jīng)知道這些了,如果還沒(méi)有的話,你需要閱讀一下這方面的資料。你可以這樣定義一個(gè)指向MyClass的指針:
MyClass *thing;
就像你期望的一樣。然后,你可以為這個(gè)指針?lè)峙湟恍﹥?nèi)存:
thing = new MyClass;
這就是問(wèn)題之所在了——你將如何使用這個(gè)指針?呃,通常你會(huì)這么寫(xiě):“thing.m_Number”,但是對(duì)于這個(gè)例子不行,因?yàn)閠hing并非一個(gè)
MyClass,而是一個(gè)指向MyClass的指針,所以它本身并不包含一個(gè)名為“m_Number”的變量;它指向的結(jié)構(gòu)才包含這個(gè)m_Number。
因此,我們必須使用一種不同的轉(zhuǎn)換方式。這就是將“.”(點(diǎn))替換為一個(gè)“->”(橫線和一個(gè)大于號(hào))。請(qǐng)看下面這個(gè)例子:
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;
}
指向數(shù)組的指針
你也可以使指針指向數(shù)組,如下:
int *pArray;
pArray = new int[6];
這將創(chuàng)建一個(gè)指針pArray,它會(huì)指向一個(gè)6個(gè)元素的數(shù)組。另一種不使用動(dòng)態(tài)分配的方法如下:
int *pArray;
int MyArray[6];
pArray = &MyArray[0];
請(qǐng)注意,你可以只寫(xiě)MyArray來(lái)代替&MyArray[0]。當(dāng)然,這種方法只適用于數(shù)組,是C/C++語(yǔ)言的實(shí)現(xiàn)使然(譯注:你也可以把
函數(shù)名賦值給一個(gè)相應(yīng)的函數(shù)指針)。通常出現(xiàn)的錯(cuò)誤是寫(xiě)成了“pArray =
&MyArray;”,這是不正確的。如果你這么寫(xiě)了,你會(huì)獲得一個(gè)指向數(shù)組指針的指針(可能有些繞嘴吧?),這當(dāng)然不是你想要的。
使用指向數(shù)組的指針
如果你有一個(gè)指向數(shù)組的指針,你將如何使用它?呃,假如說(shuō),你有一個(gè)指向整數(shù)數(shù)組的指針吧。這個(gè)指針最初將會(huì)指向數(shù)組的第一個(gè)值,看下面這個(gè)例子:
#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);
}
要想使指針移到數(shù)組的下一個(gè)值,我們可以使用pArray++。我們也可以——當(dāng)然你們有些人可能也猜到了——使用pArray +
2,這將使這個(gè)數(shù)組指針移動(dòng)兩個(gè)元素。要注意的一點(diǎn)是,你必須清楚數(shù)組的上界是多少(在本例中是3),因?yàn)樵谀闶褂弥羔樀臅r(shí)候,編譯器不能檢查出來(lái)你是否
已經(jīng)移出了數(shù)組的末尾。所以,你可能很容易地使系統(tǒng)崩潰。下面仍然是這個(gè)例子,顯示了我們所設(shè)置的三個(gè)值:
#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就是pArray當(dāng)前位置的前兩個(gè)元素。不過(guò),請(qǐng)確定你是在操作指針,而不是操作它指向的值。這種使用指針的操作在循環(huán)的時(shí)候非常有用,例如for或while循環(huán)。
請(qǐng)注意,如果你有了一個(gè)指針(例如int* pNumberSet),你也可以把它看作一個(gè)數(shù)組。比如pNumberSet[0]相當(dāng)于*pNumberSet,pNumberSet[1]相當(dāng)于*(pNumberSet + 1)。
關(guān)于數(shù)組,我還有最后一句警告。如果你用new為一個(gè)數(shù)組分配空間的話,就像下面這個(gè)樣子:
int *pArray;
pArray = new int[6];
那么必須這樣釋放它:
delete[] pArray;
請(qǐng)注意delete之后的[]。這告知編譯器它正在刪除一個(gè)整個(gè)的數(shù)組,而不是單獨(dú)的一個(gè)項(xiàng)目。你必須在使用數(shù)組的時(shí)候使用這種方法,否則可能會(huì)獲得一個(gè)內(nèi)存泄漏。
最后的話
最后要注意的是:你不能delete掉那些沒(méi)有用new分配的內(nèi)存,像下面這個(gè)樣子:
void main()
{
int number;
int *pNumber = number;
delete pNumber; // 錯(cuò)誤:*pNumber不是用new分配的
}
常見(jiàn)問(wèn)題及FAQ
Q:為什么在使用new和delete的時(shí)候會(huì)得到“symbol undefined”錯(cuò)誤?
A:這很可能是由于你的源文件被編譯器解釋成了一個(gè)C文件,因?yàn)閚ew和delete操作符是C++的新特性。通常的改正方法是使用.cpp作為你的源文件擴(kuò)展名。
Q:new和malloc的區(qū)別是什么?
A:new是C++特有的關(guān)鍵詞,并且是標(biāo)準(zhǔn)的分配內(nèi)存方法(除了Windows程序的內(nèi)
存分配方法之外)。你絕不能在一個(gè)C
C++程序中使用malloc,除非絕對(duì)必要。由于malloc并不是為C++面向?qū)ο蟮奶厣O(shè)計(jì)的,所以使用它為類(lèi)對(duì)象分配內(nèi)存就不會(huì)調(diào)用類(lèi)的構(gòu)造函
數(shù),這樣就會(huì)出現(xiàn)問(wèn)題。由于這些原因,本文并不對(duì)它們進(jìn)行討論,并且只要有可能,我亦會(huì)避免使用它們。
Q:我能一并使用free和delete嗎?
A:你應(yīng)該使用和分配內(nèi)存相配套的方法來(lái)釋放內(nèi)存。例如,使用free來(lái)釋放由malloc分配的內(nèi)存,用delete來(lái)釋放由new分配的內(nèi)存。
引用
從某種角度上來(lái)說(shuō),引用已經(jīng)超過(guò)了本文的范圍。但是,既然很多讀者問(wèn)過(guò)我這方面的問(wèn)題,那么我在此對(duì)其進(jìn)行一個(gè)簡(jiǎn)要的討論。引用和指針十分相
似,在很多情況下用哪一個(gè)都可以。如果你能夠回憶起來(lái)上文的內(nèi)容——我提到的“&”讀作“the address
of(……的地址)”,在聲明的時(shí)候例外。在聲明的這種情況下,它應(yīng)該讀作“a reference to(……的引用)”,如下:
int& Number = myOtherNumber;
Number = 25;
引用就像是myOtherNumber的指針一樣,只不過(guò)它是自動(dòng)解析地址的,所以它的行為就像是指針指向的實(shí)際值一樣。與其等價(jià)的指針代碼如下:
int* pNumber = &myOtherNumber;
*pNumber = 25;
指針和引用的另一個(gè)不同就是你不能更換引用的內(nèi)容,也就是說(shuō)你在聲明之后就不能更換引用指向的內(nèi)容了。例如,下面的代碼會(huì)輸出20:
int myFirstNumber = 25;
int mySecondNumber = 20;
int &myReference = myFirstNumber;
myReference = mySecondNumber;
printf("%d", myFristNumber);
當(dāng)在類(lèi)中的時(shí)候,引用的值必須由構(gòu)造函數(shù)設(shè)置,像下面這種方法一樣:
CMyClass::CMyClass(int &variable) : m_MyReferenceInCMyClass(variable)
{
// 這里是構(gòu)造代碼
}
總結(jié)
這一主題最初是十分難以掌握的,所以你最好讀上它個(gè)至少兩遍——因?yàn)榇蠖鄶?shù)人不能立即弄懂。下面我再為你列出本文的重點(diǎn):
1、指針是一種指向內(nèi)存中某個(gè)位置的變量,你可以通過(guò)在變量名前添加星號(hào)(*)來(lái)定義一個(gè)指針(也就是int *number)。
2、你可以通過(guò)在變量名前添加“&”來(lái)獲得它的內(nèi)存地址(也就是pNumber = &my_number)。
3、除了在聲明中以外(例如int *number),星號(hào)應(yīng)該讀作“the memory location pointed to by(由……指向的內(nèi)存位置)”。
4、除了在聲明中以外(例如int &number),“&”應(yīng)該讀作“the address of(……的地址)”。
5、你可以使用“new”關(guān)鍵字來(lái)分配內(nèi)存。
6、指針必須和它所指向的變量類(lèi)型相配套,所以int *number不應(yīng)該指向一個(gè)MyClass。
7、你可以向函數(shù)傳遞指針。
8、你必須使用“delete”關(guān)鍵字來(lái)釋放你分配的內(nèi)存。
9、你可以使用&array[0]來(lái)獲得一個(gè)數(shù)組的指針。
10、你必須使用delete[]來(lái)釋放動(dòng)態(tài)分配的數(shù)組,而不是簡(jiǎn)單的delete。
這并非一個(gè)完全的指針指南,其中有一點(diǎn)我能夠涉及到的其它細(xì)節(jié),例如指針的指針;還有一些我一點(diǎn)也未涉及到的東西,例如函數(shù)指針——我認(rèn)為作為初學(xué)者的文章,這個(gè)有些復(fù)雜了;還有一些很少使用的東西,在此我亦沒(méi)有提到,省得讓這些不實(shí)用的細(xì)節(jié)使大家感到混亂。
就這樣了!你可以試著運(yùn)行本文中的程序,并自己編寫(xiě)一些示例來(lái)弄懂關(guān)于指針的問(wèn)題吧。