文章目錄
- 一、引用概念
- 二、引用特性
- 1、引用在定義時必須初始化
- 2、一個變量可以有多個引用
- 3、引用一旦引用一個實體,再不能引用其他實體
- 三、常引用
- 四、使用場景
- 1、做參數
- 1、輸出型參數
- 2、大對象傳參
- 2、做返回值
- 1、傳值返回
- 2、傳引用返回
- 五、傳值、傳引用效率比較
- 六、引用和指針的區別

一、引用概念
引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會為引用變量開辟內存空間,它和它引用的變量共用同一塊內存空間
就比如英雄聯盟里面的游戲角色,就拿腕豪這個英雄來舉例吧
有些人叫他勁夫,有些人叫他腕豪,有些人叫他瑟提這三個名字說的都是他,只是不同人對他的叫法不同
再用代碼舉個例:
int a = 0;
int b = a;
這里沒有用引用,而是創建兩個單獨的變量,再把a的值賦給a,這在內存里創建了兩個單獨的內存空間且存儲的值相同,但是地址不同
int a = 0;
int& b = a;
這里先是創建a變量,開辟一個內存空間存儲值 0 ,再利用引用給a變量再去個外號b,不會再另外開辟一個名為b的空間,a和b的地址是相同的
我們可以通過打印a與b的地址來進行驗證:
cout << &a << endl;
cout << &b << endl;
結果:
地址相同說明a和b代表的是同一個內存空間,那如果同時a++ b++會怎么樣?
a++;
b++;
調試結果如下:
a++ 之后 b 也跟著變,再到 b++, a也跟著變了,所以 ++ 這個動作不管是對a還是b都是同時進行的,不會出現進行了 a++ ,b 不變,運行了 b++ ,a 不變的情況
二、引用特性
1、引用在定義時必須初始化
意思是不能這樣寫
2、一個變量可以有多個引用
理論上可以給一個變量去無限個別名,甚至可以給這個變量的別名取別名
對a++,在調試一下:
3、引用一旦引用一個實體,再不能引用其他實體
引用很深情,它始終如一
外面的蝴蝶再多,只能讓它的外表有所改變,但內心永遠不變
三、常引用
int main()
{//權限平移int a = 0;int& b = a;//權限放大 - 這是不允許的/*const int c = 0;int& d = c;*///權限縮小int c = 0;const int& d = c;return 0;
}
權限平移
:就是a和b前面定義都不加const它們兩個的權限都是相同的,都是可讀可寫的
權限放大
:是指c已經被const限定了只讀不可寫,但是它的別名d卻沒被限定為只讀不可寫,這種寫法再c++語法里面是不支持的
權限縮小
是指把一個變量的別名限定為只讀不可寫,自身不被限定為只讀不可寫
int類型的a能賦值給double類型的b是因為中途空間會創建一個臨時變量賦值給b,而臨時變量具有常性,會發生一個隱性的類型轉換
double類型的a的別名bb為什么會出現問題?還是因為臨時變量具有常性
,相當于是被const修飾
的而bb沒有被const修飾,臨時變量傳過去就相當于是權限放大
,所以會有問題
a的別名bbb前面加上const修飾,之后就與臨時變量相當于是權限平移,所以這種寫法就可行
四、使用場景
1、做參數
1、輸出型參數
以Swap交換函數做例子,以前是用指針倆接受變量的地址,再解引用各自的地址進行交換,現在可以利用引用來實現這個交換的功能,省去了解引用和傳變量地址的過程,讓整體更加簡潔高效
void Swap(int& r1, int& r2)
{int tmp = r1;r1 = r2;r2 = tmp;
}int main()
{int a = 1;int b = 2;Swap(a, b);return 0;
}
2、大對象傳參
作用:提高效率
a是一個大小為40000字節的數組,包含于結構體A,Func1直接是把a整個傳遞過去,Func2是引用的a,本質是不用在傳遞的。下面的TestRefAndValue()里面分別記錄Func1和Func2傳參花費的時間
#include <time.h>
struct A{ int a[10000]; };
void TestFunc1(A aa){}
void TestFunc2(A& aa){}
void TestRefAndValue()
{A a;// 以值作為函數參數size_t begin1 = clock();for (size_t i = 0; i < 10000; ++i)TestFunc1(a);size_t end1 = clock();// 以引用作為函數參數size_t begin2 = clock();for (size_t i = 0; i < 10000; ++i)TestFunc2(a);size_t end2 = clock();
// 分別計算兩個函數運行結束后的時間cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
結果如下:
雖然看起來差距不大,但是如果多次累積的話,差距就會擴大很多
2、做返回值
1、傳值返回
int Count()
{static int n = 0;n++;// ...return n;
}int main()
{int ret = Count();return 0;
}
Count的返回值就是n嗎?答案是否定的
應該是n的一份拷貝tmp傳遞給main
因為在Count里面給n定義時加了static,所以n是放到內存中的靜態去的,返回n時,是到靜態區里面去找到n進行拷貝,再傳拷貝值給ret
這里是傳值返回,就算我們寫代碼時不加static定義n,在返回n之前,編譯器也自動會進行對n進行拷貝,只是不會在靜態區里面找,就直接在棧里面找就行
2、傳引用返回
int& Count()
{int n = 0;n++;return n;
}int main()
{int ret = Count();cout << ret << endl;cout << ret << endl;return 0;
}
int& 是引用返回的語法,含義是返回返回對象的別名
這里ret的結果是未定義的,如果返回結束時,系統會清理Count的棧置成隨機值,那么這里的熱ret就是隨機值
結論:上面程序使用引用返回本質上是不對的,結果是沒有保障的
int& Count()
{int n = 0;n++;return n;
}int main()
{int& ret = Count();cout << ret << endl;cout << ret << endl;return 0;
}
結論:出了函數作用域,返回對象就銷毀了,那么一定不能用引用返回,一定要用傳值返回
int& Count()
{static int n = 0;n++;return n;
}int main()
{int& ret = Count();cout << ret << endl;cout << ret << endl;return 0;
}
這樣傳引用返回就可以了
結論:這里是先把Count里面的n放到靜態區,那么他就不會隨著棧幀的銷毀變成隨機值
五、傳值、傳引用效率比較
以值作為參數或者返回值類型,在傳參和返回期間,函數不會直接傳遞實參或者將變量本身直接返回,而是傳遞實參或者返回變量的一份臨時的拷貝,因此用值作為參數或者返回值類型,效率是非常低下的,尤其是當參數或者返回值類型非常大時,效率就更低
六、引用和指針的區別
在語法概念上引用就是一個別名,沒有獨立空間,和其引用實體共用同一塊空間
int main()
{int a = 10;int& ra = a;cout << "&a = " << &a << endl;cout << "&ra = " << &ra << endl;return 0;
}
地址相同
在底層實現上實際是有空間的,因為引用是按照指針方式來實現的
int main()
{int a = 10;int& ra = a;ra = 20;int* pa = &a;*pa = 20;return 0;
}
打開反匯編窗口就可以看見它們的底層邏輯其實是一樣的
引用和指針的不同點:
1. 引用概念上定義一個變量的別名,指針存儲一個變量地址。
2. 引用在定義時必須初始化,指針沒有要求
3. 引用在初始化時引用一個實體后,就不能再引用其他實體,而指針可以在任何時候指向任何
一個同類型實體
4. 沒有NULL引用,但有NULL指針
5. 在sizeof中含義不同:引用結果為引用類型的大小,但指針始終是地址空間所占字節個數(32
位平臺下占4個字節)
6. 引用自加即引用的實體增加1,指針自加即指針向后偏移一個類型的大小
#我的編程語言學習筆記#