您好,登錄后才能下訂單哦!
這篇文章主要介紹“C++引用如何使用”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“C++引用如何使用”文章能幫助大家解決問題。
引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會為引用變量開辟內存空間,它和它引用的變量共用同一塊內存空間。
類型& 引用變量名(對象名) = 引用實體;
如下:
void TestRef() { int a = 10; int& ra = a;//<====定義引用類型 printf("%p\n", &a); printf("%p\n", &ra); }
注意:引用類型必須和引用實體是同種類型的
1. 引用在定義時必須初始化
2. 一個變量可以有多個引用
3. 引用一旦引用一個實體,再不能引用其他實體
如下:
void TestRef() { int a = 10; int a2 = 20; //a的多個引用 int& b = a; int& c = a; int& d = b; int& ra;//該條語句編譯時會出錯,未初始化 int &ra = a2;//報錯,引用了其他實體 printf("%p %p %p %p\n", &a, &b, &c, &d); }
void TestConstRef() { const int a = 10; //int& ra = a; // 該語句編譯時會出錯,a為常量 const int& ra = a; // int& b = 10; // 該語句編譯時會出錯,b為常量 const int& b = 10; double d = 12.34; //int& rd = d; // 該語句編譯時會出錯,類型不同 const int& rd = d; //int& c = 100; // 該語句編譯時會出錯,常量是只讀的 const int& c = 100; }
注意:
引用取別名原則:對原引用變量,讀寫權限只能縮小,不能放大
const int a = 10;
int& ra = a;
編譯不通過,因為放大了權限,原引用本來是只讀,但是引用以后卻變成了可讀可寫
int& b = 10;
const int& b = 10;
編譯可以通過,因為縮小了權限,原引用本來是可讀可寫,引用后變成了只讀
double d = 12.34;
int& rd = d;
編譯不通過,這里比較特殊,看起來是因為類型不同而報錯,其實不然,報錯是因為權限放大了,為什么?
int類型要引用double類型,double類型轉化到int類型屬于隱式類型轉換會舍棄小數位,隱式類型轉換會產生臨時變量,double類型到int類型會創建一個臨時變量存儲double變成了int類型的值,這里需要注意,這個臨時變量具有常性是只讀的,rd其實是引用了這個臨時變量,因為臨時變量是只讀的,引用了臨時變量的rd也應該是只讀的,所以這就是為什么const int& rd = d 可以編譯通過。
int& c = 100;
編譯通過,因為常量本來就是只讀的,不加const代表引用后變成了可讀可寫,權限放大。
void Swap(int& left, int& right) { int temp = left; left = right; right = temp; }
輸出型參數
減少拷貝,提高效率
int& Count() { static int n = 0; n++; // ... return n; }
減少拷貝
(傳值返回需要拷貝數據,傳引用返回直接返回變量的別名)
首先,我們要知道當函數返回一個值時,會生成一個臨時變量,而函數的返回類型就是這個臨時變量的類型
int Add(int a, int b) { return a + b; } int Count() { static int n = 0; n++; return n; } int main() { int temp = Add(2, 3); int tmp = Count(); return 0; }
以上代碼將a+b(n)的值賦值給臨時變量,臨時變量再賦值給temp(tmp),為什么要設置這個臨時變量?
其實很簡單,在這個代碼里是會有問題的,出了函數作用域a+b的值就已經被銷毀了,需要一個臨時變量去儲存這個返回值,再去訪問那塊空間是非法的,而被static修飾的n由于它的生命周期變長了,即使出了函數也不會被銷毀
那么問題來了,以下代碼是正確的嗎?
int& Add(int a, int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2); return 0; }
很明顯是有問題的!這里將c的引用返回 ,而一旦出了函數c就被銷毀了,這塊空間也被操作系統收回,再將c的引用賦值給ret就變成了非法訪問了,就變成了由引用造成的野指針
由上面的問題可以衍生出以下代碼:
這里的ret是什么?
int& Add(int a, int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2); Add(3, 4); cout << "Add(1, 2) is :"<< ret <<endl; return 0; }
很明顯是7,ret是c的引用,由于出了函數以后這塊空間的使用權還給了操作系統,由于第二次函數調用仍然是在第一次函數調用的空間進行棧幀的建立,因為ret的地址(ret的地址就是之前那塊臨時變量的地址)還是之前那個地址,所以由于第二次返回c時建立的臨時變量已經變成了7,所以ret也變成了7
但是一定會是7嗎?其實不然,我們知道這塊空間的使用權還給了操作系統,這塊空間也有可能會被其他程序使用了,導致數值變成了不確定性,因為這里是直接馬上又調用了這個函數,所以會是7,所以,其實正確答案應該是隨機值才對
看下面這個代碼就是典型的例子:
int& Add(int a, int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2); Add(3, 4); cout << "Add(1, 2) is :"<< ret <<endl; cout << "Add(1, 2) is :"<< ret <<endl; return 0; }
這里的輸出語句其實也是調用了函數,由上面可知第一個是7,那么第二個呢?隨機值!因為進行了第一次輸出后其實也是進行了函數調用,函數調用會建立棧幀,在上一個輸出建立的棧幀處重新建立了棧幀,函數調用前需要先傳參,由于上一個輸出語句銷毀完棧幀以后ret地址處的值被覆蓋成隨機值,在第二次輸出語句中此時就會把這個隨機值作為參數傳過去給函數,導致輸出了隨機值,所以傳引用返回不是所有情況都可以使用的,像一開始加上了static關鍵字之類的才可以返回,因為n的生命周期變長了,出了函數作用域沒有被銷毀,取值都是去靜態區取數據。
結論:如果函數返回時,出了函數作用域,如果返回對象還未還給系統,則可以使用引用返回,如果已經還給系統了,則必須使用傳值返回。
以值作為參數或者返回值類型,在傳參和返回期間,函數不會直接傳遞實參或者將變量本身直接返回,而是傳遞實參或者返回變量的一份臨時的拷貝,因此用值作為參數或者返回值類型,效率是非常低下的,尤其是當參數或者返回值類型非常大時,效率就更低。
#include <time.h> struct A { int a[10000]; }; void TestFunc1(A a) {} void TestFunc2(A& a) {} void TestFunc3(A* a) {} 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(); // 以指針作為參數 size_t begin3 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc3(&a); size_t end3 = clock(); // 分別計算兩個函數運行結束后的時間 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl; cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl; cout << "TestFunc2(A&)-time:" << end3 - begin3 << endl; }
#include <time.h> struct A{ int a[10000]; }; A a; // 值返回 A TestFunc1() { return a;} // 引用返回 A& TestFunc2(){ return a;} void TestReturnByRefOrValue() { // 以值作為函數的返回值類型 size_t begin1 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc1(); size_t end1 = clock(); // 以引用作為函數的返回值類型 size_t begin2 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc2(); size_t end2 = clock(); // 計算兩個函數運算完成之后的時間 cout << "TestFunc1 time:" << end1 - begin1 << endl; cout << "TestFunc2 time:" << end2 - begin2 << endl; }
通過上述代碼的比較,發現傳值和指針在作為傳參以及返回值類型上效率相差很大。
在語法概念上引用就是一個別名,沒有獨立空間,和其引用實體共用同一塊空間。
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; }
我們來看下引用和指針的匯編代碼對比:
引用和指針的不同點:
引用在定義時必須初始化,指針沒有要求(建議初始化)
引用在初始化時引用一個實體后,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型實體
沒有NULL引用,但有NULL指針
在sizeof中含義不同:引用結果為引用類型的大小,但指針始終是地址空間所占字節個數(32位平臺下占 4個字節)
引用自加即引用的實體增加1,指針自加即指針向后偏移一個類型的大小
有多級指針,但是沒有多級引用
訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理
引用比指針使用起來相對更安全
引用和指針的相同點:
雖然從語法角度來看引用是別名沒有額外開空間,但是底層角度來看他們是一樣的。
什么是底層角度呢?就是通過編譯器處理的結果來看,以下是指針和引用經編譯器處理后的結果
我們會發現匯編指令是一致的,這就說明了從底層角度看這兩個實現方式是一樣的
關于“C++引用如何使用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。