1. 什麼是引用
引用其實就是給變量起了一個別名,還有一種普遍的說法是:引用是一種更安全的指針
首先要注意的第一點是,引用是必須初始化的,指針可以不初始化
int a = 10;
int *p = &a;
int& b = a;
*p = 20;
cout<< a << ' ' << *p << ' ' << b <<endl; //20 20 20
b = 30;
cout<< a << ' ' << *p << ' ' << b <<endl; //30 30 30
上面的代碼也就說明了a、*p、b這三個變量屬於同一塊內存
當我們查看int *p = &a;
和int& b = a;
這兩個分別定義一個指針和引用的語句的彙編代碼時,可以發現,定義一個指針和定義一個引用在彙編中是完全相同的(在彙編上都是)
而查看*p = 20;
和b = 30;
這兩個給指針/引用指向的地址中保存的元素賦值的操作的語句的彙編代碼可以發現,指針解引用賦值和引用賦值在彙編上也沒有區別(在彙編上都是先從一個指針內存中把它所指向的內存也就是對應元素的地址取出來,再把新值保存到該地址中)
也就可以看出,在彙編層面上,指針和引用是沒有區別的,它們都是通過指針的方式來進行的,也就是說引用操作其實底層就是一個指針,只不過更加安全了,當我們定義一個引用變量指令上就是定義一個指針,給引用變量賦值會自動做一個指針的解引用操作,例如上面的代碼中給b賦值30時其實並不是給b本身賦值,而是給b所代表的指針先解引用後再賦值。
瞭解到引用其實是使用指針實現的,也就很容易能想明白,對一個引用進行賦值時,賦值操作符右邊應該是一個可以取地址的東西,比如int &c = 20;
這個代碼就是錯誤的,因爲20不可以被取地址
2. 左值引用和右值引用
左值:有內存、有名字、值可以修改,可以放在賦值操作符左邊也可以放在賦值操作符右邊
右值:沒內存、沒名字、值不能修改,只能放在賦值操作符右邊
C++11提供了右值引用
右值引用看起來就像是二級引用(但其實不存在二級引用):int &&c = 20;
右值引用的底層實現是先在棧上生成一個臨時量儲存對應的右值,再把臨時量的地址用指針保存
還有一種方式能夠創建出右值的引用:const int &d = 20;
,這種方法的底層實現也是先創建臨時量,再保存臨時量的地址,彙編代碼與第一種完全相同
但是兩種方法的區別在於,第二種中的d被const修飾了,不可以再作左值(d = 30;
會報錯),而第一種中的c是可以作左值的(c = 30;
不會報錯)
這裏需要注意一點,一個右值引用變量,本身是一個左值
int &&c = 20;
int &e = c; //這裏編譯正確說明變量c要用左值引用來引用,也就說明它是一個左值
int &&f = c; //這句會報錯,不能使用右值引用來引用一個左值
3. 引用的示例
當我們想定義一個引用變量來引用一個數組,該怎麼定義呢?
先看如何用指針變量指向普通整形變量
int a = 10;
int* b = &a;
把指針的定義改寫成引用的定義的方式是,去掉指向的變量名前的取地址符,並且把’*‘換成’&’,int * b = &a;
更改之後就成了int& b = a;
,這個規律能幫助記憶一些難寫的引用使用語法
例如當我們用指針變量指向一整個數組時(也就是定義一個數組指針)使用下面的方式
int array[5] = {};
int (*p)[5] = &array;
使用上面的規律就可以獲得用引用變量引用數組的寫法,去掉array前的’&’,將’*‘換成’&’,就得到int (&q)[5] = array;
但是在這裏有一點需要注意,看下面的代碼:
int array[5] = {};
int (*p)[5] = &array;
int (&q)[5] = array;
cout<< sizeof(array) <<endl; // 20
cout<< sizeof(p) <<endl; // 4
cout<< sizeof(q) <<endl; // 20
cout<< sizeof(*p) <<endl; // 20
可以發現sizeof(指針)和sizeof(引用)得到的結果是不一樣的,我們可以這樣來理解,我們說引用其實是變量的別名,也就是說sizeof(q)其實就相當於sizeof(array),所以結果是整個數組的大小,而指針自己本身是有地址、有大小的,sizeof§得到的是指針自己所佔內存的大小
我們也可以發現sizeof(*p)的結果是數組的大小,這也從側面證明了當使用引用變量時在底層對它底層的指針自動作了解引用操作
4. 引用和指針的區別
- 定義一個引用變量和定義一個指針變量在彙編指令上是沒有任何區別的,它們在彙編中都是通過指針實現的;通過引用變量修改所引用的內存的值,和通過指針解引用修改指針指向的內存的值,其底層指令也是一模一樣的 (從底層實現上)
- 引用是必須初始化的,指針可以不初始化 (從使用上)
- 引用只有一級引用,沒有多級引用,指針可以有多級指針 (從使用上)