<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
參照不是新定義一個變數,而是給已存在變數取了一個別名,編譯器不會為參照變數開闢記憶體空間,它和它參照的變數共用同一塊記憶體空間。
型別& 參照變數名(物件名) = 參照實體;
如下:
void TestRef() { int a = 10; int& ra = a;//<====定義參照型別 printf("%pn", &a); printf("%pn", &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 %pn", &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; }
我們來看下參照和指標的組合程式碼對比:
參照和指標的不同點:
參照和指標的相同點:
雖然從語法角度來看參照是別名沒有額外開空間,但是底層角度來看他們是一樣的。
什麼是底層角度呢?就是通過編譯器處理的結果來看,以下是指標和參照經編譯器處理後的結果
我們會發現組合指令是一致的,這就說明了從底層角度看這兩個實現方式是一樣的
到此這篇關於C++深入探究參照的使用的文章就介紹到這了,更多相關C++參照內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45