眾所周知,引用作為函數參數可以避免參數對象的額外拷貝,對於非內置類型,一般而言可以獲得更高的效率,同時比指針更安全,語義也更清晰。但是除此之外引用有什麼特別的作用呢?在同一個作用域的引用,就像這樣:
void f()
{
int i = 0;
int &ri = i; //這裡。
//...
}
事實上,在f的內部,需要操作i的地方,完全可以直接使用i,而不必要使用ri間接操作,使用i在語義上更明確。而混合使用i和ri反倒容易引起邏輯的混亂。
似乎這是一個雞肋,但是其實不是。
這裡引用乾坤一笑文章中的例子:
例一、用C宏,書寫代碼更簡潔
這段代碼寫網絡程序的朋友都很眼熟,是Net/3中mbuf的實現。
struct mbuf
{
struct m_hdr mhdr;
union {
struct
{
struct pkthdr MH_pkthdr; /* M_PKTHDR set */
union
{
struct m_ext MH_ext; /* M_EXT set */
char MH_databuf[MHLEN];
} MH_dat;
} MH;
char M_databuf[MLEN]; /* !M_PKTHER, !M_EXT*/
} M_dat;
};
上面的代碼,假如我想訪問最裡層的MH_databuf,那麼我必須寫M_dat.MH.MH_dat.MH_databuf; 這是不是很長,很難寫呀?這樣的代碼閱讀起來也不明了。其實,對於MH_pkthdr、MH_ext、MH_databuf來說,雖然不是在一個結構層次上,但是如果我們站在mbuf之外來看,它們都是mbuf的屬性,完全可以壓扁到一個平面上去看。所以,源碼中有這麼一組宏:
#define m_next m_hdr.mh_next
#define m_len m_hdr.mh_len
#define m_data m_hdr.mh_data
... ...
#define m_pkthdr M_dat.MH.MH_pkthdr
#define m_pktdat M_dat.MH.MH_dat.MH_databuf
... ...
這樣寫起代碼來,是不是很精練呢!
這裡用宏很巧妙的解決了訪問深層數據的問題,但是宏的固有缺點也被引入了代碼中,同時,如果其他地方無意中引用了這個宏定義的頭文件,而且恰好使用了名為m_pktdat的數據成員,那這個宏帶來的後果可就不是我們想要的了。
事實上用引用也可以達到類似的效果,不過必須是在使用的時候。由於引用不是標准C的組成部分,所以這只是一個C++技巧。
//假如代碼是這樣的:
mbuf m; //這裡的mbuf就是前面的struct mbuf。
//如果要使用MH_ext成員,可以這樣:
m_ext &MH_ext = m.M_dat.MH.MH_dat.MH_ext;
//然後你的代碼中就可以直接使用MH_ext作為m.M_dat.MH.MH_dat.MH_ext的替代品了。
也許看起來不是很自然,不過這無疑是一種很直接的方法。你還可以通過一個const引用來在邏輯上避免無意的寫操作。
實際的“面向對象”的C++代碼中,不推薦直接數據成員的訪問,取而代之的是使用Get()和Set()方法存取數據,有些人只使用Get,通過返回一個成員的引用來達到讀寫數據成員的雙重目的,這時候,你可以在外部定義一個引用接受函數的返回,從而避免每次都要寫(XXX.Get()).Get()這種拖沓的語句來訪問一個深層的成員。
引用的另一個作用,就是“別名”。別名是引用的另一種翻譯,很明確的表達了引用的另一個作用。僅僅是為了代碼的可讀性:
//下面的代碼
int i = 0,j = 0;
//...
for( i = 0; i < 10; i++)
for( j = 0; j < 10; j++ )
a[i][j] = 0;
//你能明白這段代碼的含義嘛?有點困難,i和j的含義是不明確的,無法一眼看透。
//假如改成這樣:
const int width = 10;
const int height = 10;
//...
int i = 0,j = 0;
//...
int &line = i;
int &row = j;
for(line = 0;line < height;line++)
for(row = 0;row < width;row++)
a[line][row] = 0;
//是不是好了一點?
這並不是一個典型的例子,因為i和j的定義是任意的,某些情況你必須使用別人給定的名稱很郁悶的變量,而他們又必須用來表達截然不同的含義,這時候一個引用往往可以讓你清爽很多。
再看下面這個例子:
class CA
{
int m_i;
public:
int &i;
int const &c_i;
CA():i(m_i),c_i(m_i){};
};
這是一個簡單的類,與所謂的“面向對象”的方法不同,這裡使用引用實現內部數據的公用接口。這個手法用來對應 乾坤一笑 的另一段話:
這就是偶說的PME模型的問題了,delphi、java、c#之類的語言都提供一種叫做屬性的語法,大概是這個樣子的:
class A
{
property int x
{
get {return x;}
set {x = value;}
}
};
A a;
這樣, 就可以這麼訪問了 a.x = 8; int b = a.x;
這比用 a.setx(8); b=a.getx();直觀多了。
你可以用 CA a; a.i訪問CA的私有數據成員,達到像屬性方法那樣的效果。但是這個方法在VC6下的表現卻不盡如人意,因為它存儲了一個指針用來取代語法上的引用,這導致類體積不必要的擴張,是我們所不希望看到的。也許在實現上確實存在難度,不過還是希望有更好的編譯器能實現真正意義的引用接口。
這個例子其實是上面深層數據成員訪問的一個引申。
引用,作為C++的一個特殊手法,也許還有很多不為人知的作用等待我們去發掘呢~
注:文中的代碼並未經過嚴格測試,有興趣的讀者可以自己測試代碼的有效性。