C/C++之指標(pointer),參考(reference) 觀念整理與常見問題 ...

文章推薦指數: 80 %
投票人數:10人

From: http://sandwichc.blogspot.com/2007/02/cc-pointer-reference.html很多程式員說:學C/C++而不會使用指標,相當於沒學過C/C++。

本文針對C/C++中,指標與參考的 ... 皮皮Mynotebook日誌相簿影音好友名片 關於我加入好友我的相簿我的影音 chuangyf0917's新文章C/C++之指標(pointer),參考(reference)觀念整理與常見問題CEdit&CRichEdit使用技巧CListCtrl使用技巧 全部展開|全部收合 chuangyf0917's新回應沒有新回應! 皮皮 關鍵字 平均分數:0顆星投票人數:0人我要評分: 200709281031C/C++之指標(pointer),參考(reference)觀念整理與常見問題?C/C++From:http://sandwichc.blogspot.com/2007/02/cc-pointer-reference.html很多程式員說:學C/C++而不會使用指標,相當於沒學過C/C++。

本文針對C/C++中,指標與參考的常見問題或錯誤,做了一番整理,但求能達到拋磚引玉之效。

如有疏漏或錯誤之處,尚請不吝告知指教。

目錄何謂指標(pointer)?何謂參考(reference)?callbyvalue?callbyaddress(或callbypointer)?callbyreference?--swap(int*a,int*b)v.s.swap(int&a,int&b)pointertopointer,referencetopointer(int**v.s.int*&)functionpointervoid**(*d)(int&,char**(*)(char*,char**))....如何看懂複雜的宣告…1.何謂指標(pointer)?何謂參考(reference)?我們先談指標(pointer)。

指標,其實也只是一個變數,只是這個變數的意義是:指向某個儲存位址。

很玄嗎?一點也不。

下面這張圖就可以輕易的看出指標為何物。

圖中,a,b,c,d,p1,p2都是一般的變數,儲存在記憶體(memory)中。

其中,p1變數所記載的值是變數a的記憶體(memory)位址,而p2則記載著b的記憶體位址,像這樣的狀況,我們就稱p1是一個指向a的指標,相同的,p2是一個指向b的指標。

在C/C++中,我們用下面的式子來表示這個關係:int*p1=&a;int*p2=&b;其中的&,稱為addressof(取址)。

即,p1=addressofa,p2=addressofb。

另一個符號*,代表的意義是指標。

int*p1要由後往前閱讀來瞭解它的意義:p1isapointerpointstoaninteger。

因此,int*p1=&a;這整行,我們可以看成:p1isapointerpointstointegervariablea,即:p1是一個指標,指向整數變數a。

且讓我們暫時打住指標的討論,轉頭看看參考(reference)。

參考,可以想像成是一個變數或物件的別名(alias)。

通常,當函式(function)的參數(parameter)在函式中會被修改,而且要把這個修改結果讓呼叫函式的部份繼續使用,我們會用參考來當參數傳入函式中。

讓我們看看下面的例子:voidswap(int&a,int&b){inttmp=a;a=b;b=tmp;}當其他程式呼叫此交換程式時,只要直接寫swap(x,y)就能交換x與y的值。

在這裡,a和b為x與y的別名,即:a就是x,b就是y,如同美國國父就是華盛頓一樣。

a和b不是x和y的複製品,任何做用在a與b上的動作都會反應在x與y上面,反之亦然。

指標和參考之所以難懂,有很大一部份的原因是符號上的陌生所致。

加上&既能用於取址又能用於參考,容易造成初學者的混淆。

下面我們提供幾個建議來幫助各位看懂這些符號。

把int*p視為int*p。

把int和*連在一起看,當作是一種型態叫做"指向整數之指標",要比int*p自然得多。

同樣的方式也可以套在char*p或void*p等。

但要注意的是下面的狀況:int*p,q;不要把這行誤解成p,q都是指向int之指標,事實上,q只是一個int變數。

上面這行相當於int*p,q;或int*p;intq;如果p,q都要宣告成指向int之指標,應寫成:int*p,*q或者干脆分兩行寫:int*p;int*q;若&前面有資料型態(ex:int&),則為參考,&前面有等號(ex:int*p=&a),則為取址。

由於&同時具有多種意義,因此容易造成混淆。

這裡列出的這個方法,可以幫助弄清楚每個&的意義。

2.callbyvalue?callbyaddress(或callbypointer)?callbyreference?--swap(int*a,int*b)v.s.swap(int&a,int&b)JAVA中的reference與C++的reference意義上並不相同,卻使用同一個字,這也是reference容易造成混淆的原因。

在此,我們暫不考慮JAVA中reference的觀念(關於java中reference的觀念,請參考ReferenceinJAVA--淺談java的指標),純粹把主題放在C/C++上。

呼叫副函式時,callbyvalue,address,或reference是三種不同的參數傳遞方式。

其意義如下:callbyvalue假設函式A呼叫函式B(p,q),則B中的p和q是「複製」自函式A所傳入的參數,B中對p,q所做的任何運算都不會影響到A中的p和q,因為B執行完後,並不會把複製的p,q存回到A中。

這種參數傳遞方式,我們稱之為callbyvalue。

以swap這個常見的函式為例,若swap寫成下面的樣子:voidswap(inta,intb){inttmp=a;a=b;b=tmp;}則呼叫swap(x,y)後,x和y的值並不會有變化。

callbyaddress(或callbypointer)利用指標來做參數傳遞,這種方法骨子裡仍是callbyvalue,只不過callbyvalue的value,其資料型態為指標罷了。

我們同樣看看用callbyaddress來寫swap交換兩個integer的例子。

voidswap(int*a,int*b){inttmp=*a;*a=*b;*b=tmp;}呼叫swap時,要寫成swap(&x,&y)。

呼叫swap時,x的指標(x的儲存位置)與y的指標(y的儲存位置)會被複製一份到swap中,然後把該位置內所記載的值做更換。

swap結束後,&x(addressofx)和&y(addressofy)依然沒變,只是addressofx所記錄之變數值與addressofy所記錄之變數值交換了。

因為&x和&y其實是利用callbyvalue在傳,因此,callbyaddress其實骨子裡就是callbyvalue。

callbyreference這是C++才加進來的東西,C本身並沒有callbyreference。

callbyreference基本上是把參數做個別名(alias),以下面的swap為例:swap(int&a,int&b){inttmp=a;a=b;b=tmp;}未來使用時,只要呼叫swap(x,y),就可以讓x和y的值交換。

在這個例子中,a就是x,b就是y。

這個觀念在上一節已經提過,在此不再贅述。

3.pointertopointer,referencetopointer(int**v.s.int*&)當我們用callbypointer(或address)來傳遞參數時,被呼叫的函式複製一份pointer的值過去。

但是,當我們想在函式內改變pointer的值(而非pointer所指向之變數的值),而且改變的效果要能在函式外看得到時,callbypointer就不足夠用了。

此時應該用的是"callbypointertopointer"或"callbyreferencetopointer"。

我們先看下面的例子:intg_int=0;voidchangePtr(int*pInt){pInt=&g_int;}voidmain(){intlocalInt=1;int*localPInt=&localInt;changePtr(localPInt);printf("%d\n",*localPInt);}在這個例子中,印出來的數字仍然會是localInt的1,因為changPtr中的pInt是由localPInt「複製」過去的,對pInt做改變並不會反應到localPInt身上。

我們先用pointertopointer對localPInt做改變,請看下例。

intg_int=0;voidchangePtr(int**pInt){*pInt=&g_int;}voidmain(){intlocalInt=1;int*localPInt=&localInt;changePtr(&localPInt);printf("%d\n",*localPInt);}本例中,印出來的數字會是g_int的0。

changePtr函式中的pInt是由&localPInt複製所得,因此對pInt做改變並不會影響main中的&localPInt(資料型態:pointertopointertointeger)。

但在changePtr函式中我們改變的對象是pInt所指向的內容,因此這項改變在main中會顯示出來。

同樣的功能,我們也可改用referencetopointer來完成。

但同樣切記,reference是C++才有的功能,因此referencetopointer也只能在支援C++的環境中使用。

intg_int=0;voidchangePtr(int*&refPInt){refPInt=&g_int;}voidmain(){intlocalInt=1;int*localPInt=&localInt;changePtr(localPInt);printf("%d\n",*localPInt);}這段程式印出來的數字會是0。

因為在changePtr中,我們宣告的參數型態為int*&,即:referencetopointertointeger。

因此,main中的localPInt與changePtr函式中的refPInt其實是「同一件東西」。

另一種常見的混淆是pointerarray(指標陣列)與pointertopointers,因為兩種都可以寫成**的型式。

如,int**可能是pointertopointertointeger,也可能是integerpointerarray。

但pointerarray的觀念相對來講要簡單且直觀許多,這裡我們就暫不花篇幅敘述。

常見的例子:main(intargc,char**argv)其實應該是main(intargc,char*argv[])。

4.functionpointer變數的指標指向變數的位址,同樣的,functionpointer(函式指標)也是指向函式的位址的指標。

函式指標的加入,讓C/C++的符號更複雜,也使更多人望之而卻步。

在說明函式指標的用途前,我們先直接由語法來看看函式指標該怎麼宣告、怎麼理解。

假設有個函式長成下面的樣子:voidfunc1(intint1,charchar1);我們想宣告一個能指向func1的指標,則寫成下面這樣:void(*funcPtr1)(int,char);這樣的寫法應理解成:funcPtr1是一個函數指標,它指向的函數接受int與char兩個參數並回傳void。

如果今天有另一個函式長成voidfunc2(intint2,charchar2);則funcPtr1也能指向func2。

指標指向的方法,寫成下面這樣:funcPtr1=&func1;取址符號省略亦可,效果相同:funcPtr1=func1;若欲在宣告時就直接給予初值,則寫成下面這樣:void(*funcPtr1)(int,char)=&func1;//&亦可省略stdlib.h中提供的qsort函式是函式指標最常見的應用之一。

此函式之prototype長得如下:voidqsort(void*base,size_tn,size_tsize,int(*cmp)(constvoid*,constvoid*));其中的int(*cmp)(constvoid*,constvoid*)就使用到函式指標。

函式指標常見的使用時機是multithread時。

函數指標負責把函數傳進建立執行緒的API中。

另外,callbackfunction也是常使用函式指標的地方。

所謂callbackfunction即:發生某事件時,自動執行某些動作。

在eventdriven的環境中,便時常使用callbackfunction來實現此機制。

事實上,函式指標還能讓C語言實作polymorphism。

但礙於篇幅,在此不再詳述。

5.void**(*d)(int&,char**(*)(char*,char**))....如何看懂複雜的宣告…在這裡,我們介紹兩種方式來看懂複雜的宣告。

第一種要判斷的是:常數與指標混合使用時,到底const修飾的是指標還是指標所指的變數?第二種是面對如標題所示這種複雜的宣告時,我們要怎麼讀懂它。

5.1常數與指標的讀法constdouble*ptr;double*constptr;doubleconst*ptr;constdouble*constptr;以上幾個宣告,到底const修飾的對象是指標,還是指標所指向的變數呢?其實,關鍵在於:*與const的前後關係!當*在const之前,則是常數指標,反之則為常數變數。

因此,constdouble*ptr;//ptr指向常數變數double*constptr;//ptr是常數指標doubleconst*ptr;//ptr指向常數變數constdouble*constptr;//指向常數變數的常數指標事實上,在TheC++ProgrammingLanguage中有提到一個簡單的要訣:由右向左讀!!讓我們用這個要訣再來試一次。

constdouble*ptr;//ptrisapointerpointstodouble,whichisaconstantdouble*constptr;//ptrisaconstantpointerpointstodoubledoubleconst*ptr;//ptrisapointerpointstoconstantdoubleconstdouble*constptr;//ptrisaconstantpointerpointstodouble,whichisaconstant結果完全相同:-)5.2複雜宣告的讀法void**(*d)(int&,char**(*)(char*,char**)).......其實閱讀C/C++中複雜的宣告有點像是讀英文的長句子,看多了,自然知道句子是怎麼構造出來的。

但對於句子還不熟的人,難免得藉助文法來拆解一個句子。

關於C語言複雜宣告的解析文法,最令我印象深刻的,莫過於印度工程師Vikram的"Theright-leftrule"。

他是這麼說的:「從最內層的括號讀起,變數名稱,然後往右,遇到括號就往左。

當括號內的東西都解讀完畢了,就跳出括號繼續未完成的部份,重覆上面的步驟直到解讀完畢。

」舉個例子:void**(*d)(int&,char*)依下面方式解讀:1.最內層括號的讀起,變數名稱:d2.往右直到碰到):(空白)3.往左直到碰到(:是一個函數指標4.跳出括號,往右,碰到(int&,char*):此函式接受兩個參數:第一個參數是referencetointeger,第二個參數是characterpointer。

5.往左遇上void**:此函式回傳的型態為pointertopointertovoid。

==>d是一個函式指標,指向的函式接受int&和char*兩個參數並回傳void**的型態。

如何,是不是好懂很多了呢?標題中的void**(*d)(int&,char**(*)(char*,char**))其實和上面的例子幾乎一樣,只是函式的第二個參數又是一個函式指標,接受char*和char**兩個參數並回傳char**的型態。

行文至此,把指標和參考的常見問題與混淆大致地提了一些。

希望能讓使用C/C++的人在面對或使用指標、參考、或取址時,不再有疙瘩在心中。

文中若有不足或錯誤之處,也請高手不吝指教囉。

:-) PostedbyHung-HsuanChen(Sean)at9:21AM Labels:C/C++,programminglanguage 皮皮/Xuite日誌/回應(1)/引用(0)/好文轉寄CEdit&CRi...|日誌首頁|沒有下一則上一篇CEdit&CRichEdit使用技巧回應



請為這篇文章評分?