引用(reference)是一個現有對象的別名。用對象來初始化引用之後,那麼對象的名字或引用的名字都可以用於指向(refer to)該對象:
int a = 12;
int &ra = a;
--ra; // a == 11;
a = 10; // ra = 10;
int *ip = &ra;
人們常常會將引用和指針相混淆,原因大概在於C++編譯器通常采用指針的方式實現引用,但引用其實不是指針,其行為和指針並不相同。
在引用和指針之間存在三大區別:其一,不存在空引用(NULL Reference);其二,所有引用都要初始化;其三,一個引用永遠指向用來對它初始化的那個對象。比如說,在先前的例子中,引用ra在整個生命期內都指向a。絕大多數對引用的誤用都滋生於對這三大區別的理解。
一些編譯器可以捕捉到那些明顯的創建空引用的嘗試:
Employee &anEmployee = *static_cast<Employee*>(0);
然而,編譯咕嘟可能無法偵測到不那麼明顯的創建空引用的嘗試,從而導致在運行期發生未定義的行為:
Employee *getAnEmployee();
//...
Employee &anEmployee = *getAnEmployee(); // 可能是糟糕的代碼
if (&anEmployee == 0)
如果getAnEmployee返回的是一個空指針,那麼其後代碼的行為就是未定義的。在這個例子中,最好使用一個指針來存放getAnEmployee返回的結果。
Employee *employee = getAnEmployee();
if (employee)//...
引用必須初始化的要求,意味著當一個引用初始化它所指向的那個對象必須存在。這一點很重要,因此我想再重復一遍:一個引用就是在該引用被初始化之前已經存在的一個對象的別名。一旦一個引用被初始化去指向一個特定的對象,那麼該引用以後就不可以再指向別的對象;在一個引用的整個生命期內,該引用被綁定到用於初始化它的那個對象上。實際上,一個引用完成其初始化後,就只是初始化它的那個對象的別名了。這個“別名”屬性使得引用常常成為形參的優秀選擇。在以下swap函數模板中,形參a和b乃是傳遞給調用的實參的別名:
template<typename T>
void swap(T &a, T &b)
{
T temp(a);
a = b;
b = temp;
}
//...
int x = 1, y = 2;
swap(x, y);
在以上對swap的整個調用期間,a是x的別名,b是y的別名。提醒一下,引用所指向的對象可以沒有名字,因此引用可用於為沒有名字的對象賦予一個方便的名字:
int grades[MAX];
swap(grade[i], grade[j]);
在swap中的形參a和b分別被用實參grade[i] grade[j]初始化以後,這兩個沒有名字的數組元素就可以通過別名a, b進行操縱了。為了簡化和優化,還可以采用更直接的方式來使用這個屬性。
考慮如下函數,它用於設置二維數組中一個特定元素:
inline void set_2d(float *a, int m, int i, int j)
{
a[i*m+j] = a[i*m+j] * a[i*m+i] + a[i*m+j];//哎呀
}
可以將注釋有“哎呀”的那行代碼替代為更簡單的版本,該版本利用引用,而且還帶來額外的好處,那就是正確!
inline void set_2d(float *a, int m, int i, int j)
{
float &r = a[i*m+j];
r = r * r + r;
}
一個指向非常量的引用是不可以用字面值或臨時值進行初始化的:
double
PAN style="COLOR: #0000cc">&d = 12.3; //錯誤
swap(std::string("Hello"), std::string("World")); //錯誤 臨時值
然而,一個指向常量的引用就可以:
const double &cd = 12.3; // OK
template<typename T>
T add(const T &a, const T &b)
{
return a+b;
}
//...
const std::string &greeting = add(std::string("Hello"), std::string("World")); // OK
當一個指向常量的引用采用一個字面值來初始化時,該引用實際上被設置為指向“采用該字面值初始化”的一個臨時位置。因此,cd並非真的指向字面值12.3,而是指向一個采用12.3初始化的、類型為double的臨時變量。greeting引用則指向對add的調用所返回的無名臨時string值。一般說來,這類臨時對象在創建它們的表達式的末尾被銷毀(確切地說,就是離開作用域並且析構函數被調用)。然而,當這類臨時對象用於初始化一個指向常量的引用時,在引用指向它們期間,這些臨時對象會一直存在。