引用(reference)是其中C++語言複合類型之一。
C++11中新增了一種引用:所謂的“右值引用(rvalue reference)”,之後再詳細介紹。這種引用主要用于内置類。嚴格來說,我們使用術語“引用(reference)”,指的是“左值引用(lvalue reference)”
引用(reference)實際上就是給對象起了個外号,操作一個變量的引用也就相當于操作變量本身,這一點跟指針很類似,隻是相比指針更直覺。
一、基本操作
1.引用必須要被初始化
定義引用時,程式把引用和它的初始值綁定(binding)在一起,而不是将初始值拷貝給引用。一旦初始化完成,引用将和它的初始值對象一直綁定在一起(換言之,無法令引用重新綁定到另一個對象)。
int x = 10, y = 9;
int &xx = x;
int &refVal2; //報錯:引用必須被初始化
int &refVal3 = 10; //報錯,引用類型的初始值必須是個對象
xx = y; //無法令引用重新綁定到另一個對象
xx++;
std::cout << "x Address:" << &x << std::endl; //x Address:0x72fe2c
std::cout << "y Address:" << &y << std::endl; //y Address:0x72fe28
std::cout << "xx Address:" << &xx << std::endl; //xx Address:0x72fe2c
std::cout << "x:" << x << std::endl; //x:11
std::cout << "xx:" << xx << std::endl; //xx:11
2.引用本身并不是對象。
不能建立引用的引用和指向引用的指針,但可以建立指針的引用。
int x = 10;
int *p = &x;
int *&refp = p;
(*refp)++;
cout << *p << endl; //11
cout << *refp << endl; //11
cout << x << endl; //11
3.建立數組的引用。
很多資料都強調,不能建立數組的引用。因為數組是一個由若幹個元素所組成的集合,是以無法建立一個數組的别名。其實是無法直接像引用基本類型那樣,聲明數組的引用,稍加處理就可以建立數組的引用了。
int (&xx)[3]
的解讀:首先,xx是一個引用;其次,xx是一個存放3個元素的數組引用;最後,xx是一個存放3個整數類型的數組引用。
int x[] = {1,2,3};
int (&xx)[sizeof(x)/sizeof(x[0])] = x; //A reference to current array x
xx[1]++;
std::cout << "xx[1]:" << xx[1] << std::endl; //xx[1]:3
std::cout << "x Address:" << &x << std::endl; //x Address:0x72fe20
std::cout << "xx Address:" << &xx << std::endl; //x Address:0x72fe20
下面介紹一種标準點的寫法(或者更容易了解的寫法)。在此之前,先來了解類型别名(type alias),它是某種類型的同義詞,便于将複雜的類型名字變得簡單明了,易于了解和使用。
傳統的方法是使用關鍵字
typedef
,例如
typedef int Jack; // 幫int取名叫Jack
Jack age = 10; // 等同于int age = 10;
現在,展示容易了解的寫法,定義
arrayref
代表的類型是存放
[sizeof(x)/sizeof(x[0])]
個整數類型的數組類型。
int x[] = {1,2,3};
typedef int arrayref[sizeof(x)/sizeof(x[0])];
arrayref &xx = x; //A reference to current array x
xx[1]++;
std::cout << "xx[1]:" << xx[1] << std::endl; //xx[1]:3
std::cout << "x Address:" << &x << std::endl; //x Address:0x72fe20
std::cout << "xx Address:" << &xx << std::endl; //x Address:0x72fe20
二、函數
A.作為函數參數
基本用法:可以直接對實參進行操作,比如對兩個數進行交換。
void swap(int &a,int &b){
int temp;
temp = a;
a = b;
b = temp;
}
傳引用參數的好處:
1.安全,引用對象(指向的實參)不會在函數内部改變。指針參數和引用參數都在棧中開辟了記憶體空間存放的是由主調函數放進來的實參變量的位址。但是,被調函數内部指針存放的内容可以被改變,即可能改變指向的實參,是以并不安全,而引用則不同,它引用的對象的位址一旦賦予,則不能改變。
void compare(int *p, int &ref){
cout << "In compare function:" << endl;
cout << "p Address:" << p << endl;
cout << "ref Address:" << &ref << endl;
int y = 11;
p = &y;
ref = y;
cout << "p Address:" << p << endl;
cout << "ref Address:" << &ref << endl;
}
int main() {
int x = 10;
int *p = &x;
int &xx = x;
cout << "p Address:" << p << endl;
cout << "xx Address:" << &xx << endl;
compare(p,xx);
return 0;
}
輸出結果為:
p Address:0x79fe3c
xx Address:0x79fe3c
In compare function:
p Address:0x79fe3c
ref Address:0x79fe3c
p Address:0x79fdfc
ref Address:0x79fe3c
- 拷貝大的類型對象或者容器對象比較低效,這時推薦使用引用形參。比如
類。string
bool compare(string &s1, string &s2){
return s1.size() == s2.size();
}
3.有的類型(包括IO類型在内)根本不支援拷貝操作。這時,函數隻能通過引用形參通路該類型的對象。
B.作為函數傳回值
當函數傳回引用類型的時候,沒有複制傳回值,而是傳回對象的引用(即對象本身)。實際上, 就是傳回一個變量的記憶體位址,既然是記憶體位址的話,那麼肯定可以讀寫該位址所對應的記憶體區域的值,即就是“左值”,可以出現在指派語句的左邊。
typedef double arr[5];
double& setValues(arr &ref,int i )
{
return ref[i]; // 傳回第 i 個元素的引用
}
int main() {
using namespace std;
double vals[5] = {10.1, 12.6, 33.1, 24.1, 50.0};
arr &refarr = vals;
cout << "Before:" << endl;
for ( int i = 0; i < 5; i++ ) cout << vals[i] << " ";
cout << endl;
setValues(refarr,1) = 20.23; // 改變第 2 個元素
cout << "After:" << endl;
for ( int i = 0; i < 5; i++ ) cout << vals[i] << " ";
cout << endl;
return 0;
}
一個函數隻能傳回一個值,然而有時函數需要同時傳回多個值,引用形參為我們一次傳回多個結果提供了有效的途徑。舉個例子,我們定義一個名為find_char的函數,它傳回在string對象中某個指定字元第一次出現的位置。同時,我們也希望函數能傳回該字元出現的總次數。
該如何實作呢?一種方法是定義一個新的資料類型,讓它包含兩個值。另一種簡單的方法,我們可以給函數傳入一個額外的引用實參,令其儲存字元出現的次數:
string::size_type find_char(const string &s, char c, string::size_type &occurs){
auto ret = s.size();
occurs = 0;
for(decltype(ret) i = 0; i != s.size(); ++i){
if(s[i] == c){
if(ret == s.size()) ret = i;
++occurs;
}
}
return ret; //出現次數通過occurs隐式地傳回
}
三、const的引用
A.初始化和對const的引用
可以把引用綁定到
const
對象上,我們稱之為對常量的引用(reference to const)。與普通引用不同的是,對常量的引用不能被用作修改它所綁定的對象。
int i = 42;
const int &r1 = i; //允許将const int&綁定到一個普通int對象上
const int &r2 = 42; //正确:r1是一個常量引用
const int &r3 = r1 * 2; //正确:r3是一個常量引用
int &r4 = r1 * 2; //錯誤:r4是一個非常量引用
上面,同樣的初始化對于非const引用是不合法的,将導緻編譯錯誤。原因有些微妙,需要适當做些解釋。
引用在内部存放的是一個對象的位址,它是該對象的别名。對于不可尋址的值,如文字常量,以及不同類型的對象,編譯器為了實作引用,必須生成一個臨時對象,将該對象的值置入臨時對象中,引用實際上指向該對象(對該引用的操作就是對該臨時對象的操作),但使用者不能通路它。
double dval = 3.14;
const int &ri = dval;
此處
ri
引用了一個
int
型的數。對
ri
的操作應該是整數運算,但
dval
卻是一個雙精度浮點數。是以為了確定
ri
綁定一個整數,編譯器把上述代碼程式設計如下形式:
const int temp = dval;
const int &ri = temp;
再放一些例子
double dval = 3.14159;
//下3行僅對const引用才是合法的
const int &ir = 1024;
const int &ir2 = dval;
const double &dr = dval + 1.0;
上面的代碼,轉化成
double dval = 3.14159;
//不可尋址,文字常量
int tmp1 = 1024;
const int &ir = tmp1;
//不同類型
int tmp2 = dval;//double -> int
const int &ir2 = tmp2;
//另一種情況,不可尋址
double tmp3 = dval + 1.0;
const double &dr = tmp3;
B.函數和const的引用
1.函數形參
如果函數無須改變引用形參的值,最好将其聲明為常量引用。
bool isShorter(const string &s1,const string &s2){
return s1.size() < s2.size();
}
-
函數傳回值
由于傳回值直接指向了一個生命期尚未結束的變量,是以,對于函數傳回值(或者稱為函數結果)本身的任何操作,都在實際上,是對那個變量的操作,這就是引入const類型的傳回的意義。當使用了const關鍵字後,即意味着函數的傳回值不能立即得到修改!
如下代碼,将無法編譯通過,這就是因為傳回值立即進行了++操作(相當于對變量z進行了++操作),而這對于該函數而言,是不允許的。如果去掉const,再行編譯,則可以獲得通過,并且列印形成
z:7
的結果。
const int& abc(int a, int b, int c, int& result){
result = a + b + c;
return result;
}
int main() {
int a = 1; int b = 2; int c=3;
int z;
abc(a, b, c, z)++; //wrong: returning a const reference
cout << "z: " << z << endl;
return 0;
}
參考文獻
1.《C++ Primer(第5版)》 Stanley B.Lippman, Josee Lajoie, Barbara E.Moo