最近在用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++使用方法上,略有不一樣:
一、指針的值能夠爲 NULL ,而引用不能爲 NULL,你若是VC中直接定義變量CString& strVar1;或者CString& strVar1 = NULL;這是行不通的,編譯器沒法經過語法掃描。編譯器要求定義引用變量時,必須指定初值。
二、一旦引用被初始化,就不能改變引用的關係(指針則能夠隨時改變所指的對象)。
如下示例程序中,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;
因此,咱們得出結論:引用實際上只是編程語言中語法層面的概念,經編譯後實際執行的代碼中與指針沒有任何區別。引入這個概念後帶來好處有兩條:
一、代碼簡潔,使用方便。能夠對比一下上面的例子。
二、部分消除指針使用的不安全。因爲從語法層面就要求必須它初始化,因此不會出現空指針操做這樣的危險狀況。
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++的語法相似。