最近在用VC6做一個項目,有的弟兄反映不太理解指標和引用的區別。從目前現況來看,現在的開發人員用慣了Java,C#等語言,距離作業系統底層越來越遠了,以至於大部分人對C/C++中的一些東西早已淡忘。
說到指標,凡是學過c語言的人都不陌生,這是是c語言的精華所在。指標操作不僅高效,而且非常接近系統底層,容易掌控。所以凡是使用c/c++編碼的技術人員,個人建議是首先花精力把指標搞清楚。當然,事情總是具有兩面性,有利必有弊,指標操作最為詬病的就是它的不安全性。除非你對它理解很透徹,否則難免會出現訪問越界、記憶體泄露以及不可預知的結果。
“引用”是C++中引入的概念,我們經常會看到“引用”應用在函數調用過程中。在C++語言中,調用一個函數時,參數傳遞可以採用3種方式:傳值、傳址和傳引用。
比如下面一段VC代碼:
//主程式碼
CString strUserId = "001";
CString strUserName = "無名";
BOOL bFind = FindUserName(strUserId, strUserName);
if( bFind )
{
MessageBox("User Name:" + strUserName);
}
else
{
MessageBox("No found!");
}
//尋找使用者函數
BOOL FindUserName(CString& strUserIdParam, CString& strUserNameParam)
{
if( strUserIdParam == "001" )
{
strUserNameParam = "王澤賓";
return TRUE;
}
return FALSE;
}
FindUserName函數中使用了兩個引用型參數。主程式碼最終的運行結果是:彈出一個對話方塊,內容是“User Name:王澤賓”。函數中的“CString &”,表示參數使用“傳引用方式”。FindUserName中的strUserNameParam與主程式碼中的strUserName是同一個對象,所以修改他們中的任何一個,都會反映到另外一個。strUserNameParam只不過是strUserName的別名而已,這非常類似於windows系統中檔案的捷徑,或者linux系統中檔案的符號連結。
如果把尋找使用者函數修改為:
//尋找使用者函數
BOOL FindUserName(CString strUserIdParam, CString strUserNameParam)
{
if( strUserIdParam == "001" )
{
strUserNameParam = "王澤賓";
return TRUE;
}
return FALSE;
}
FindUserName函數中使用了傳值參數。主程式碼最終的運行結果是:彈出一個對話方塊,內容是“User Name:無名”。函數中的“CString”,表示參數使用“傳值方式”。
FindUserName中的strUserNameParam與主程式碼中的strUserName是完全不同的兩個對象,所以修改他們中的任何一個,都不會反映到另外一個上。strUserNameParam的內容複寫了一份strUserName的內容,所以strUserNameParam又稱為strUserName的副本,這個複製操作是在函數調用過程中參數壓棧時執行的。
如果把尋找使用者函數修改為:
//尋找使用者函數
BOOL FindUserName(CString *pstrUserIdParam, CString *pstrUserNameParam)
{
if( *pstrUserIdParam == "001" )
{
*pstrUserNameParam = "王澤賓";
return TRUE;
}
return FALSE;
}
那麼主程式碼中調用函數的地方需要略作修改:
BOOL bFind = FindUserName(&strUserId, &strUserName);
FindUserName函數中使用了傳址參數。主程式碼最終的運行結果是:彈出一個對話方塊,內容是“User Name:王澤賓”。函數中的“CString *”,表示參數使用“傳址方式”。FindUserName中的pstrUserNameParam與主程式碼中的pstrUserName是兩個完全沒有關係的指標變數,只是這兩個指標變數中存放的內容完全相同,都是指向同一塊記憶體空間的地址,所以修改他們中的任何一個指標變數所指向的記憶體空間,效果都是完全一樣的。pstrUserNameParam的內容複寫了一份pstrUserName的內容,pstrUserNameParam是pstrUserName的副本,這個複製操作是在函數調用過程中參數壓棧時執行的。
通過以上例子,我們可以看到引用在作為函數參數傳遞時,具有指標的效果,但是卻可以象傳值的一樣使用,方式比指標簡單。當作為在參數時,通過指標和引用,都可以在函數體內操作直接改變他們所指向的地址空間裡面儲存的內容,無須編譯器為他們建立一個臨時變數複製並儲存他們的值的一個副本。
指標和引用在C/C++使用方法上,略有不同:
1、指標的值可以為 NULL ,而引用不能為 NULL,你如果VC中直接定義變數CString& strVar1;或者CString& strVar1 = NULL;這是行不通的,編譯器無法通過文法掃描。編譯器要求定義引用變數時,必須指定初值。
2、一旦引用被初始化,就不能改變引用的關係(指標則可以隨時改變所指的對象)。
以下樣本程式中,k 被初始化為i 的引用。語句k = j 並不能將k 修改成為j 的引用,只是把k 的值改變成為6。由於k 是i 的引用,所以i 的值也變成了6。
int i = 5;
int j = 6;
int &k = i;
k = j; // k 和i 的值都變成了6;
所以,我們得出結論:引用實際上只是程式設計語言中文法層面的概念,經編譯後實際執行的代碼中與指標沒有任何區別。引入這個概念後帶來好處有兩條:
1、代碼簡潔,使用方便。可以對比一下上面的例子。
2、部分消除指標使用的不安全。由於從文法層面就要求必須它初始化,所以不會出現null 指標操作這樣的危險情況。
Java,C#語言的一個優點就是取消了指標的概念,從而從文法層面就消除了C/C++語言的不安全性,這對於構建健壯的大型應用非常有益。它們對於函數調用的參數傳遞採用兩種方式:對於簡單類型的參數實行傳值方式,對於物件類型的參數實行傳引用方式。
以Java為例:
class ObjUser{
String strName = "init value";
public String toString(){
return strName;
}
}
public class Test{
ObjUser User = new ObjUser();
int Age = 60;
public void changeObj(ObjUser user){
user.strName = "changed value";
}
public void changeInt(int age){
age = 80;
}
public static void main(String[] args)
{
Test objUser = new ObjUser();
System.out.println("==================Print User=================");
System.out.println("Before call changeObj() method: " + objUser.User);
oRef.changeObj(objUser.User);
System.out.println("After call changeObj() method: " + objUser.User);
System.out.println("==================Print Age=================");
System.out.println("Before call changeAge() method: " + Age);
oRef.changePri(objUser.Age);
System.out.println("After call changeAge() method: " + Age);
}
}
運行結果:
==================Print User=================
Before call changeObj() method: init value
After call changeObj() method: changed value
==================Print Age=================
Before call changeAge() method: 60
After call changeAge() method: 60
在Java中如果要實現對象傳值方式,可以先藉助於clone()方法先來複製對象,然後再調用函數。
C#的情形基本類似,php中也保留了傳值和傳引用兩種方式,跟C++的文法類似。
====================================================================================
終於用上了自己悶頭苦幹完成的郵件用戶端了,這兩個月可累死我了。