C/C++之指標(pointer),參考(reference) 觀念整理與常見問題 ...
文章推薦指數: 80 %
轉貼至http://sandwichc-life.blogspot.com/2007/10/cc-pointer-reference.html#two C/C++之指標(pointer),參考(r.
ProgramsKnowledge
跳到主文
ProgramsKnowledge
部落格全站分類:不設分類
相簿
部落格
留言
名片
Aug28Sun201123:53
C/C++之指標(pointer),參考(reference)觀念整理與常見問題(轉貼)
轉貼至http://sandwichc-life.blogspot.com/2007/10/cc-pointer-reference.html#two
C/C++之指標(pointer),參考(reference)觀念整理與常見問題
這篇文章是由我舊的blog轉貼過來的
文中某些小細節稍作修改
--------------------------------
前言
這是以前替人代班教課時寫的一些東西
重新整理後放上來,一方面當作自己的備忘錄(自己最看得懂的還是自己寫的東西)
另一方面如果有人有這方面的問題,希望此文能對你們也有一點點幫助。
--------------------------------
很多程式員說:學C/C++而不會使用指標,相當於沒學過C/C++。
本文針對C/C++中,指標與參考的常見問題或錯誤,做了一番整理,但求能達到拋磚引玉之效。
如有疏漏或錯誤之處,尚請不吝告知指教。
目錄
何謂指標(pointer)?何謂參考(reference)?
call
byvalue?callbyaddress(或callby
pointer)?callbyreference?--swap(int*a,int*b)v.s.swap(int
&a,int&b)
pointer
topointer,referencetopointer(int**v.s.int*&)
function
pointer
void
**(*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的觀念,請參考Reference
inJAVA--淺談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*&)
當我們用call
bypointer(或address)來傳遞參數時,被呼叫的函式複製一份pointer的值過去。
但是,當我們想在函式內改變pointer的值(而非pointer所指向之變數的值),而且改變的效果要能在函式外看得到時,callbypointer就不足夠用了。
此時應該用的是"callby
pointertopointer"或"callbyreferenceto
pointer"。
我們先看下面的例子:
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(int
argc,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即:發生某事件時,自動執行某些動作。
在event
driven的環境中,便時常使用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; //ptrisa
pointerpointstodouble,whichisaconstant
double*constptr; //ptrisa
constantpointerpointstodouble
doubleconst*ptr; //ptrisapointer
pointstoconstantdouble
constdouble*constptr; //ptrisa
constantpointerpointstodouble,whichisaconstant
結果完全相同:-)
5.2複雜宣告的讀法void**(*d)(int&,char**(*)(char*,
char**)).......
其實閱讀C/C++中複雜的宣告有點像是讀英文的長句子,看多了,自然知道句子是怎麼構造出來的。
但對於句子還不熟的人,難免得藉助文法來拆解一個句子。
關於C語言複雜宣告的解析文法,最令我印象深刻的,莫過於印度工程師Vikram的"Theright-leftrule"。
他是這麼說的:
「從最內層的括號讀起,變數名稱,然後往右,遇到括號就往左。
當括號內的東西都解讀完畢了,就跳出括號繼續未完成的部份,重覆上面的步驟直到解讀完畢。
」
舉個例子:void**(*d)(int&,char*)依下面方式解讀:
1.最內層括號的讀起,變數名稱:d
2.往右直到碰到):(空白)
3.往左直到碰到(:是一個函數指標
4.跳出括號,往右,碰到(int&,char*):此函式接受兩個參數:第一個參數是referencetointeger,第二個參數是characterpointer。
5.往左遇上void**:此函式回傳的型態為pointer
topointertovoid。
==>d是一個函式指標,指向的函式接受int&和char*兩個參數並回傳void**的型態。
如何,是不是好懂很多了呢?
標題中的void**(*d)(int&,char**(*)(char*,char
**))其實和上面的例子幾乎一樣,只是函式的第二個參數又是一個函式指標,接受char*和char**兩個參數並回傳char**的型態。
行文至此,把指標和參考的常見問題與混淆大致地提了一些。
希望能讓使用C/C++的人在面對或使用指標、參考、或取址時,不再有疙瘩在心中。
文中若有不足或錯誤之處,也請高手不吝指教囉。
:-)
全站熱搜
創作者介紹
BigBear
ProgramsKnowledge
BigBear發表在痞客邦留言(0)人氣()
全站分類:不設分類個人分類:Others此分類上一篇:虛擬IP辨別方式
此分類下一篇:batchcommand
上一篇:ButtonStyle
下一篇:BorderColorEvent
歷史上的今天
2011:ButtonStyle
2011:取得errororexception的位置(行數)
2011:MessageBoxandDialogResult
2011:設定物件字型大小粗體等
2011:DataGridViewpart2
▲top
留言列表
禁止留言
文章分類
DATABASES(4)
ORACLE(34)ORACLE錯誤處理(2)MYSQL(1)SQLSERVER(0)
.NET(5)
ASP.NET(C#)(1)C#(WinForm)(66)IIS(5)VB.NET(2)VB6.0(4)
JAVA(4)
Swing(0)JSP&Servlet(0)FundamentalsofJava(1)Tomcat(0)
FLEX(1)Others(16)
最新文章
熱門文章
文章精選
文章精選
2012七月(13)
2012四月(4)
2012三月(14)
2011十二月(7)
2011十一月(6)
2011十月(3)
2011九月(9)
2011八月(6)
2011六月(15)
2011五月(1)
2011三月(15)
2011二月(39)
2010十二月(1)
所有文章列表
文章搜尋
誰來我家
站方公告
[公告]2022年度農曆春節期間服務公告[公告]MIB廣告分潤計劃、PIXwallet錢包帳戶條款異動通知[公告]2021年度農曆春節期間服務公告
活動快報
達特蕾蒂易肽纖
小心!糖糖危機來襲!Dr.Lady的享甜0負擔魔法【易肽...
看更多活動好康
我的好友
最新留言
動態訂閱
新聞交換(RSS)
參觀人氣
本日人氣:
累積人氣:
QRCode
POWEREDBY
(登入)
回到頁首
回到主文
免費註冊
客服中心
痞客邦首頁
©2003-2022PIXNET
關閉視窗
延伸文章資訊
- 1C++ (原始指標)
的指標 void 只會指向原始記憶體位置。 有時候需要使用 void* 指標,例如在C++ 程式碼與C 函式之間傳遞時。 當具類型的指標 ...
- 2[C++, CPP 教學教程教材Tutorial] 指標(Pointer)&指涉器 ...
介紹C++的指標(Pointer), 指涉器(Reference), 取址運算子(Address-of ... 指標 :用來儲存記憶體位址的變數 ⇒ 所以初始化時只能指派位址給指標,而不是值; ...
- 3指標與位址 - OpenHome.cc
int *n; float *s; char *c;. 雖然宣告指標時,C++ 習慣將 * 前置在變數名稱前,不過 n 的型態是 ...
- 4C語言: 超好懂的指標,初學者請進~ - 寫點科普Kopuchat
等等,那C 語言中的指標是長什麼樣子? 讓我們來看看這段程式碼: void main(){ int a = 15; int b = 2; int c = 39; ...
- 5指標與位址 - OpenHome.cc
int *n; float *s; char *c;. 雖然宣告指標時,C 習慣將 * 前置在變數名稱前,不過 n 的型態是 ...