C/C++?引用作為函數(shù)的返回值方式
語法:類型 &函數(shù)名(形參列表){ 函數(shù)體 }
特別注意:
1.引用作為函數(shù)的返回值時(shí),必須在定義函數(shù)時(shí)在函數(shù)名前將&
2.用引用作函數(shù)的返回值的最大的好處是在內(nèi)存中不產(chǎn)生返回值的副本
//代碼來源:RUNOOB #include<iostream> using namespace std; float temp; float fn1(float r){ temp=r*r*3.14; return temp; } float &fn2(float r){ //&說明返回的是temp的引用,換句話說就是返回temp本身 temp=r*r*3.14; return temp; } int main(){ float a=fn1(5.0); //case 1:返回值 //float &b=fn1(5.0); //case 2:用函數(shù)的返回值作為引用的初始化值 [Error] invalid initialization of non-const reference of type 'float&' from an rvalue of type 'float' //(有些編譯器可以成功編譯該語句,但會(huì)給出一個(gè)warning) float c=fn2(5.0);//case 3:返回引用 float &d=fn2(5.0);//case 4:用函數(shù)返回的引用作為新引用的初始化值 cout<<a<<endl;//78.5 //cout<<b<<endl;//78.5 cout<<c<<endl;//78.5 cout<<d<<endl;//78.5 return 0; }
case1:用返回值方式調(diào)用函數(shù)
如下圖:
返回全局變量temp的值時(shí),C++會(huì)在內(nèi)存中創(chuàng)建臨時(shí)變量并將temp的值拷貝給該臨時(shí)變量。當(dāng)返回到主函數(shù)main后,賦值語句a=fn1(5.0)會(huì)把臨時(shí)變量的值再拷貝給變量a
case2:用函數(shù)的返回值初始化引用的方式調(diào)用函數(shù)
如下圖:
這種情況下,函數(shù)fn1()是以值方式返回到,返回時(shí),首先拷貝temp的值給臨時(shí)變量。
返回到主函數(shù)后,用臨時(shí)變量來初始化引用變量b,使得b成為該臨時(shí)變量到的別名。
由于臨時(shí)變量的作用域短暫(在C++標(biāo)準(zhǔn)中,臨時(shí)變量或?qū)ο蟮纳芷谠谝粋€(gè)完整的語句表達(dá)式結(jié)束后便宣告結(jié)束,也就是在語句float &b=fn1(5.0);之后),所以b面臨無效的危險(xiǎn),很有可能以后的值是個(gè)無法確定的值。
如果真的希望用函數(shù)的返回值來初始化一個(gè)引用,應(yīng)當(dāng)先創(chuàng)建一個(gè)變量,將函數(shù)的返回值賦給這個(gè)變量,然后再用該變量來初始化引用:
int x=fn1(5.0); int &b=x;
case3:用返回引用的方式調(diào)用函數(shù)
如下圖:
這種情況下,函數(shù)fn2()的返回值不產(chǎn)生副本,而是直接將變量temp返回給主函數(shù),即主函數(shù)的賦值語句中的左值是直接從變量temp中拷貝而來(也就是說c只是變量temp的一個(gè)拷貝而非別名) ,這樣就避免了臨時(shí)變量的產(chǎn)生。尤其當(dāng)變量temp是一個(gè)用戶自定義的類的對(duì)象時(shí),這樣還避免了調(diào)用類中的拷貝構(gòu)造函數(shù)在內(nèi)存中創(chuàng)建臨時(shí)對(duì)象的過程,提高了程序的時(shí)間和空間的使用效率。
case4:用函數(shù)返回的引用作為新引用的初始化值的方式來調(diào)用函數(shù)
如下圖:
這種情況下,函數(shù)fn2()的返回值不產(chǎn)生副本,而是直接將變量temp返回給主函數(shù)。
1.在主函數(shù)中,一個(gè)引用聲明d用該返回值初始化,也就是說此時(shí)d成為變量temp的別名。
2.由于temp是全局變量,所以在d的有效期內(nèi)temp始終保持有效,故這種做法是安全的。
3.不能返回局部變量的引用。如上面的例子,如果temp是局部變量,那么它會(huì)在函數(shù)返回后被銷毀,此時(shí)對(duì)temp的引用就會(huì)成為“無所指”的引用,程序會(huì)進(jìn)入未知狀態(tài)。
4.不能返回函數(shù)內(nèi)部通過new分配的內(nèi)存的引用。雖然不存在局部變量的被動(dòng)銷毀問題,但如果被返回的函數(shù)的引用只是作為一個(gè)臨時(shí)變量出現(xiàn),而沒有將其賦值給一個(gè)實(shí)際的變量,那么就可能造成這個(gè)引用所指向的空間(有new分配)無法釋放的情況(由于沒有具體的變量名,故無法用delete手動(dòng)釋放該內(nèi)存),從而造成內(nèi)存泄漏。因此應(yīng)當(dāng)避免這種情況的發(fā)生
5當(dāng)返回類成員的引用時(shí),最好是const引用。這樣可以避免在無意的情況下破壞該類的成員。
6.可以用函數(shù)返回的引用作為賦值表達(dá)式中的左值
#include<iostream> using namespace std; int value[10]; int error=-1; int &func(int n){ if(n>=0&&n<=9) return value[n];//返回的引用所綁定的變量一定是全局變量,不能是函數(shù)中定義的局部變量 else return error; } int main(){ func(0)=10; func(4)=12; cout<<value[0]<<endl; cout<<value[4]<<endl; return 0; }
用引用實(shí)現(xiàn)多態(tài)
在C++中,引用是除了指針外另一個(gè)可以產(chǎn)生多態(tài)效果的手段。
也就是說一個(gè)基類的引用可以用來綁定其派生類的實(shí)例
class Father;//基類(父類) class Son:public Father{.....}//Son是Father的派生類 Son son;//son是類Son的一個(gè)實(shí)例 Father &ptr=son;//用派生類的對(duì)象初始化基類對(duì)象的使用
特別注意:
ptr只能用來訪問派生類對(duì)象中從基類繼承下來的成員。如果基類(類Father)中定義的有虛函數(shù),那么就可以通過在派生類(類Son)中重寫這個(gè)虛函數(shù)來實(shí)現(xiàn)類的多態(tài)。
函數(shù)中返回引用和返回值的區(qū)別
主要討論下面兩個(gè)函數(shù)的區(qū)別
int& at() { ? ? return m_data_; }
int at() { ? ? return m_data_; }
上面兩個(gè)函數(shù),第一個(gè)返回值是int的引用int&,第二個(gè)返回值是int,二者的區(qū)別是什么呢?
我們先用一個(gè)語句 const int& a = mymay.at(); 來分別調(diào)用一次上面兩個(gè)函數(shù),然后看匯編語言的結(jié)果。
反匯編結(jié)果:
#int& at() #{ # return m_data_; #} 00BB6830 push ebp 00BB6831 mov ebp,esp 00BB6833 sub esp,0CCh 00BB6839 push ebx 00BB683A push esi 00BB683B push edi 00BB683C push ecx 00BB683D lea edi,[ebp-0CCh] 00BB6843 mov ecx,33h 00BB6848 mov eax,0CCCCCCCCh 00BB684D rep stos dword ptr es:[edi] 00BB684F pop ecx 00BB6850 mov dword ptr [this],ecx m_data_++; 00BB6853 mov eax,dword ptr [this] 00BB6856 mov ecx,dword ptr [eax] 00BB6858 add ecx,1 00BB685B mov edx,dword ptr [this] 00BB685E mov dword ptr [edx],ecx return m_data_; #取地址this中的值5879712(m_data_的地址)到寄存器eax中,此時(shí)寄存器eax存的是m_data_的地址 00BB6860 mov eax,dword ptr [this] } 00BB6863 pop edi 00BB6864 pop esi 00BB6865 pop ebx 00BB6866 mov esp,ebp 00BB6868 pop ebp 00BB6869 ret const int& a = mymay.at(); 00176AA2 lea ecx,[mymay] 00176AA5 call MyMat::at (0171546h) #此時(shí)寄存器eax中的值為m_data_的地址5879712,直接將地址5879712存入地址a中。 00176AAA mov dword ptr [a],eax cout << a << endl;
#int at() #{ # return m_data_; #} 012B6830 push ebp 012B6831 mov ebp,esp 012B6833 sub esp,0CCh 012B6839 push ebx 012B683A push esi 012B683B push edi 012B683C push ecx 012B683D lea edi,[ebp-0CCh] 012B6843 mov ecx,33h 012B6848 mov eax,0CCCCCCCCh 012B684D rep stos dword ptr es:[edi] 012B684F pop ecx 012B6850 mov dword ptr [this],ecx return m_data_; #和上面一樣,也是先取出m_data_的地址 012B6853 mov eax,dword ptr [this] #和上面不一樣,不是直接將m_data_的地址放入寄存器eax中,而是取地址5879712中的值(m_data_=3)放入寄存器eax中,此時(shí)寄存器eax存的是m_data_的值(3) 012B6856 mov eax,dword ptr [eax] } 012B6858 pop edi 012B6859 pop esi 012B685A pop ebx 012B685B mov esp,ebp 012B685D pop ebp 012B685E ret const int& a = mymay.at(); 008E6AA2 lea ecx,[mymay] 008E6AA5 call MyMat::at (08E154Bh) #此時(shí)eax的值為3,將3存入地址ebp-24h中, 008E6AAA mov dword ptr [ebp-24h],eax #將eax的值變成ebp-24h 008E6AAD lea eax,[ebp-24h] #將地址ebp-24h寫到地址為a中,此時(shí)a代表的地址是ebp-24h 008E6AB0 mov dword ptr [a],eax cout << a << endl;
所以結(jié)論就是:
1、返回值為引用型(int& )的時(shí)候,返回的是地址,因?yàn)檫@里用的是 int& a=mymay.at(); ,所以a和m_data_指的是同一塊地址(由寄存器eax傳回的5879712)。
2、返回值不是引用型(int)的時(shí)候,返回的是一個(gè)數(shù)值。這個(gè)時(shí)候就很有意思了,編譯器是先將這個(gè)數(shù)值放入一個(gè)內(nèi)存中(上面例子中,該內(nèi)存地址為ebp-24h),再把這個(gè)地址付給a,此時(shí)的a代表的地址是ebp-24h,和m_data_代表的地址不一樣(m_data_代表的地址是5879712)。
3、綜上兩點(diǎn)可以看出,當(dāng)返回的值不是引用型時(shí),編譯器會(huì)專門給返回值分配出一塊內(nèi)存的(例子中為ebp-24h)。
說明一下函數(shù)返回時(shí)
如果不是返回一個(gè)變量的引用,則一定會(huì)生成一個(gè)臨時(shí)變量。
看下面的函數(shù),返回的是t而不是&t,所以一定會(huì)有臨時(shí)變量產(chǎn)生。
?T function1(){ ? ? ?T t(0); ? ? ?return t; ?} ?T x=function1();
這里的過程是:
1.創(chuàng)建命名對(duì)象t
2.拷貝構(gòu)造一個(gè)無名的臨時(shí)對(duì)象,并返回這個(gè)臨時(shí)對(duì)象
3.由臨時(shí)對(duì)象拷貝構(gòu)造對(duì)象x
4.T x=function1();這句語句結(jié)束時(shí),析構(gòu)臨時(shí)對(duì)象
這里一共生成了3個(gè)對(duì)象,一個(gè)命名對(duì)象t,一個(gè)臨時(shí)對(duì)象作為返回值,一個(gè)命名對(duì)象x。
下面的函數(shù)稍微復(fù)雜一定,它沒有先定義一個(gè)中間變量t,看起來似乎是直接返回了一個(gè)臨時(shí)變量。但實(shí)際上,如果不經(jīng)過c++的優(yōu)化,那么它并沒有提高效率,因?yàn)樗€是創(chuàng)建了3個(gè)對(duì)象。
?T function2(){ ? ? ? return T(0); ?} ?T x=function2();
這里的過程是:
1.創(chuàng)建一個(gè)無名對(duì)象
2.由無名對(duì)象拷貝構(gòu)造一個(gè)無名的臨時(shí)對(duì)象
3.析構(gòu)無名對(duì)象,返回臨時(shí)對(duì)象
4.由臨時(shí)對(duì)象拷貝構(gòu)造對(duì)象x
5.T x=function2()語句結(jié)束時(shí),析構(gòu)臨時(shí)對(duì)象。
這里一共生成了3個(gè)對(duì)象,其中有2個(gè)對(duì)象都是馬上被析構(gòu)掉的,不能被后面的代碼使用。既然是這樣,那么就會(huì)有優(yōu)化的余地,可以嘗試著不要前面的兩個(gè)臨時(shí)變量。c++確實(shí)會(huì)做這樣的優(yōu)化,優(yōu)化后的c++會(huì)避免匿名對(duì)象和臨時(shí)對(duì)象這兩個(gè)對(duì)象的生成,而直接生成x,這樣就減少了兩次對(duì)象生成-回收的消耗,提高了程序性能。
其實(shí)function1()這段代碼也是會(huì)經(jīng)過優(yōu)化的,但因?yàn)榕R時(shí)對(duì)象t是一個(gè)命名對(duì)象,所以一定會(huì)被創(chuàng)建。存儲(chǔ)返回值的臨時(shí)對(duì)象是多余的,會(huì)被優(yōu)化掉而不生成。
但是,程序員不應(yīng)該依賴這種優(yōu)化,因?yàn)閏++不保證這種優(yōu)化一定會(huì)做。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C語言使用廣度優(yōu)先搜索算法解決迷宮問題(隊(duì)列)
這篇文章主要介紹了C語言使用廣度優(yōu)先搜索算法解決迷宮問題,結(jié)合迷宮問題分析了C語言隊(duì)列廣度優(yōu)先搜索算法的相關(guān)使用技巧,需要的朋友可以參考下2017-09-09C語言中操作utmp文件的相關(guān)函數(shù)用法
這篇文章主要介紹了C語言中操作utmp文件的相關(guān)函數(shù)用法,包括getutent()函數(shù)和setutent()函數(shù)以及endutent()函數(shù),需要的朋友可以參考下2015-08-08C++編程異常處理中try和throw以及catch語句的用法
這篇文章主要介紹了C++編程異常處理中try和throw以及catch語句的用法,包括對(duì)Catch塊的計(jì)算方式的介紹,需要的朋友可以參考下2016-01-01C語言入門篇--四大常量(字面,const修飾,宏,枚舉)及標(biāo)識(shí)符
本篇文章是c語言基礎(chǔ)篇,主要講述一下常量,常量即不可被直接修改的量(const修飾的常變量可間接修改,后續(xù)文章會(huì)繼續(xù)說明)請(qǐng)大家持續(xù)關(guān)注腳本之家2021-08-08