網站首頁 編程語言 正文
返回值是類名和返回值是引用的區別
返回非引用類型
函數的返回值用于初始化在調用函數時創建的臨時對象(temporary object),如果返回類型不是引用,在調用函數的地方會將函數返回值復制給臨時對象。
在求解表達式的時候,如果需要一個地方存儲其運算結果,編譯器會創建一個沒命名的對象,這就是臨時對象。C++程序員通常用temporary這個術語來代替temporary object。
用函數返回值初始化臨時對象與用實參初始化形參的方法是一樣的。
當函數返回非引用類型時,其返回值既可以是局部對象,也可以是求解表達式的結果。
返回引用類型
當函數返回引用類型時,沒有復制返回值,相反,返回的是對象本身。
千萬不要返回局部對象的引用!千萬不要返回指向局部對象的指針!
當函數執行完畢時,將釋放分配給局部對象的存儲空間。此時對局部對象的引用就會指向不確定的內存!返回指向局部對象的指針也是一樣的,當函數結束時,局部對象被釋放,返回的指針就變成了不再存在的對象的懸垂指針。
返回引用時,要求在函數的參數中,包含有以引用方式或指針方式存在的,需要被返回的參數。
如果返回對象,最后多執行一次拷貝構造函數,如果返回引用,直接返回現存對象
#include <iostream>
using namespace std;
class Timer
{
public:
? ? Timer();
? ? Timer(int, int, int);
? ? friend Timer ?&operator+(Timer&, Timer&);
? ? friend Timer operator-(Timer&, Timer&);
? ? friend ostream& operator<<(ostream &out, Timer &t);
? ? friend istream& operator>>(istream &in, Timer &t);
private:
? ? int hour, minute, second;
};
Timer::Timer()
{
? ? hour = 0;
? ? minute = 0;
? ? second = 0;
}
Timer::Timer(int hour, int minute, int second)
{
? ? this->hour = hour;
? ? this->minute = minute;
? ? this->second = second;
}
Timer & operator+(Timer& a, Timer &b)
{
? ? a.second = a.second + b.second;
? ? a.minute = a.minute + b.minute;
? ? a.hour = a.hour + b.hour;
? ? if (a.second >= 60)
? ? {
? ? ? ? a.second = a.second - 60;
? ? ? ? a.minute++;
? ? }
? ? if (a.minute >= 60)
? ? {
? ? ? ? a.minute = a.minute - 60;
? ? ? ? a.hour++;
? ? }
? ? return a;
}
Timer operator-(Timer &a, Timer &b)
{
? ? Timer c;
? ? c.hour = a.hour - b.hour;
? ? c.minute = a.minute - b.minute;
? ? c.second = a.second - b.second;
? ? if (c.second < 0)
? ? {
? ? ? ? c.second += 60;
? ? ? ? c.minute--;
? ? }
? ? if (c.minute < 0)
? ? {
? ? ? ? c.minute += 60;
? ? ? ? c.hour--;
? ? }
? ? return c;
}
ostream& operator<<(ostream &out, Timer &t)
{
? ? out << t.hour << ":" << t.minute << ":" << t.second << endl;
? ? return out;
}
istream& operator>>(istream&in, Timer &t)
{
? ? cout << "Input hours and minutes." << endl;
? ? in >> t.hour >> t.minute >> t.second;
? ? return in;
}
int main()
{
? ? Timer t1, t2, t3, t4;
? ? cin >> t1 >> t2;
? ? cout << "t1=" << t1;
? ? cout << "t2=" << t2;
? ? t3 = t1 + t2;
? ? cout << "t3=t1+t2=" << t3;
? ? t4 = t1 - t2;
? ? cout << "t4=t1-t2=" << t4;
? ? return 0;
}
剛開始我將函數聲明為:
friend Timer&operator+(Timer&,Timer&);
friend Timer&operator-(Timer&,Timer&);
對于<<,>>的重載聲明的和長代碼一樣。但是這樣做之后我發現出現異常。
在Time &operator+(Timer&t1,Timer&t2)里我聲明了一個Timer的局部變量,而當函數調用結束后,該指針變成了懸掛指針。
所以,一定謹記不要在返回值為引用的函數中返回局部變量。
C++函數返回值和返回引用問題
C++函數的返回過程基本可以分為兩個階段,返回階段和綁定階段,根據兩個階段中需要返回的值的類型不同(返回值和引用),和要綁定的值的類型(綁定值和引用)會產生不同的情況。
最基本的規則是先返回,再綁定,返回和綁定的時候,都有可能發生移動或者拷貝構造函數的調用來創建臨時對象,并且只會發生一次。更具體的,當返回值的時候,函數返回之前,會調用一次拷貝構造函數或移動構造函數,函數在綁定到值的時候,會發生一次拷貝構造函數或移動構造函數,如果返回時已經調用了構造函數,則綁定時不會再調用,而是直接綁定。返回引用或者綁定到引用,在各自階段,不會產生構造函數的調用。
下面舉例闡述各種可能的情況(例子中很多情況我們是不該這樣寫的,本文只討論如果這樣做了會怎樣)。
關于右值引用和移動構造函數的解釋,后面會寫篇東西做個總結。這里僅需要一條規則,那就是在需要調用移動構造函數的情況,如果類沒有定義移動構造函數,則調用它的拷貝構造函數,后面對未定義移動構造函數的情況,不再贅述。
為敘述方便,定義綁定值和返回值:
綁定值就是函數賦給的變量,返回值就是函數的返回值,最終的調用形式類似:
返回值 fun()
{
?? ?.....
?? ?return 返回值;
}
綁定值 = func();
定義一個類:
class myClass
{
public:
? ? //構造函數
?? ?myClass()
?? ?{
?? ??? ?cout << "construct" << endl;
?? ?}
? ? //拷貝構造函數
?? ?myClass(const myClass& rhs)
?? ?{
?? ??? ?cout << "copy construct" << endl;
?? ?}
? ? //移動構造函數
?? ?myClass(myClass&& rhs)
?? ?{
?? ??? ?cout << "move consturct" << endl;
?? ?}
? ? //析構函數
?? ?~myClass()
?? ?{
?? ??? ?cout << "desctruct" << endl;
?? ?}
};
1.綁定值類型為值類型
返回值類型為值類型,返回的是局部變量:
在函數返回階段,調用類的移動構造函數創建返回值,并綁定到綁定值上。移動構造函數的調用發生在函數返回階段。
例子:
myClass fun()
{
?? ?myClass mc;
?? ?return mc;
}
?
int main()
{
?? ?myClass result = fun();
}
輸出:
construct
move consturct
desctruct
desctruct
2.綁定值類型為值類型
返回值類型為值類型,返回的是局部變量的引用:
首先,這不是一個好的做法,企圖返回一個局部變量的引用結果通常并不會令人滿意。
函數調用結束后,函數幀被回收(實際上只是設置了棧頂的地址),函數棧中的臨時變量不再有意義。
但是這么做通常也不會造成什么副作用,因為在函數返回階段,會調用拷貝構造函數(注意,即使定義了移動構造函數也不會調用),將生成的新的臨時對象,綁定到綁定值上。
例子:
myClass fun()
{
?? ?myClass mc;
?? ?myClass& mcRef = mc;
? ? ? ? //如果是想返回這個局部變量的引用,并且在函數外部對其進行修改,那必然要失望了。
?? ?return mcRef;
}
?
int main()
{
?? ?myClass result = fun();
}
輸出:
construct
copy construct
desctruct
desctruct
3.綁定值類型為值類型
返回值類型為值類型,返回的是全局變量的引用:
和2中的情形類似,但是由于全局變量的生命周期超過當前函數,所以即使函數返回,變量仍然存活。在函數返回階段,仍然會調用拷貝構造函數,將產生得臨時對象綁定到綁定值上,卻依然不能返回全局對象。
例子:
通常,返回作為參數而傳遞進來的引用的情況更加常見,此時的參數對于函數來說,和全局變量類似,即函數返回,變量的生命周期也不會結束。
myClass fun(myClass& param)
{
? ? ? ? //最終只能得到param的拷貝
?? ?return param;
}
?
int main()
{
?? ?myClass mc;
?? ?myClass result = fun(mc);
}
輸出:
construct
copy construct
desctruct
desctruct
4.綁定值類型為值類型
返回值類型為引用類型,返回的是局部變量或局部變量的引用:
和情況2情形類似的是,希望以引用的方式返回局部變量或者局部變量的引用,通常也不能取得令人滿意的結果;但與情況2的情形不同的是,情況2基本不會產生什么副作用,情況2中在函數返回階段對對象進行拷貝,此時對象完整;但是在本情況中,就會產生嚴重的副作用。
由于返回值類型為引用類型,在函數返回階段,并不會調用拷貝構造函數,而在綁定階段,綁定值是值類型,才會產生拷貝構造函數的調用,而此時函數已經返回,拷貝函數拷貝的是函數中的臨時變量,此時已經析構了,再進行拷貝,只會得到一個被析構對象的拷貝,通常,這會產生嚴重的錯誤。
簡單的說,這種情況下,會先析構臨時對象,在拷貝這個臨時對象。
例子:
myClass& fun()
{
?? ?myClass mc;
?? ?//或者myClass mcRef = mc; return mcRef
?? ?return mc;
}
?
int main()
{
?? ?myClass result = fun();
}
輸出:
construct
desctruct
copy construct
desctruct
5.綁定值類型為值類型
返回值類型為引用類型,返回的是全局變量的引用:
基本的情況和2是類似的,即并不能真的的到全局變量的引用,但是也不會產生什么副作用(除非調用者就是希望或者這個全局變量,并進行修改)。由于全局變量在函數結束之后并不會被析構,所以對它調用拷貝構造函數是安全的。和情況4類似,拷貝函數的調用也不是發生在函數返回階段,而是發生在綁定階段。
例子:
同樣使用參數為引用類型的函數舉例:
myClass& fun(myClass& param)
{
?? ?return param;
}
?
int main()
{
?? ?myClass mc;
?? ?myClass result = fun(mc);
}
輸出:
construct
copy construct
desctruct
desctruct
上述5中情況是將返回值綁定到值類型的情形,所有情形中,均會出現拷貝或移動構造函數的調用,最終綁定之后,都不可能得到原來的對象,企圖通過這種方式來修改原對象,必然會失敗。
如果綁定值類型為引用類型,返回值類型為值類型,返回的是局部變量,局部變量的引用或者全局變量會怎么樣呢?
這種情況編譯器會報錯,由于函數返回的是值類型,必然會產生移動構造函數或者拷貝構造函數,產生一個臨時對象,而臨時對象是一個右值,我們常說的引用,其實是左值引用,而一個右值對象是不能夠綁定到一個左值引用的,所以編譯器會報錯。
我們可以將函數的返回值版綁定到右值引用變量(使用myClass&&),我們下面討論返回右值引用的情況。但是,有時候返回一個右值引用并且使用它,可能并不能產生想要的結果(如本文最后例子)。
6.綁定值類型為右值引用
返回值類型為值類型,返回的是局部變量或局部變量的引用或全局變量的引用:
函數返回階段,由于是返回值類型,所以會調用移動構造函數或者拷貝構造函數,創建一個不具名的臨時變量(其實它就是一個右值),可以把它綁定到一個右值引用上。
但是對它的修改是沒有什么意義的,本來函數創建的這個不具名臨時變量,會在函數返回之后被析構,由于我們增加了一個指向它的引用,所以這個臨時變量的生命周期被延長了,直到指向它的引用離開作用域,它才會析構。
例子:
myClass fun()
{
?? ?myClass mc;
?? ?return mc;
}
int main()
{
?? ?myClass&& result = func();
}
輸出:
construct
move consturct
desctruct
desctruct?
myClass fun()
{
?? ?myClass mc;
?? ?myClass& mcRef = mc;
?? ?return mcRef;
}
int main()
{
?? ?myClass&& result = fun();
}
輸出:
construct
copy construct
desctruct
desctruct
7.綁定值類型為為引用類型
返回值類型為引用類型,返回局部變量或局部變量的引用:
由于函數返回階段和綁定階段都是引用類型,所以不會產生任何拷貝或移動構造函數的調用,最終綁定值類就是函數的局部變量,但是由于函數返回后,局部變量已經被析構,在保存這個局部變量的引用沒有意義,往往會引起一些詭異的錯誤。
例子:
myClass& fun()
{
?? ?myClass mc;
?? ?//或者myClass& mcRef = mc; return mcRef;
?? ?return mc;
}
?
int main()
{
? ? ? ? //在函數返回之后,我們甚至還能使用這個result,但是僅僅是編譯器將原來result的位置繼續解釋成result變量,
? ? ? ? //很有可能很快這個位置會被新的棧幀覆蓋,繼續使用result,可能會出現“詭異”的問題
?? ?myClass& result = fun();
}
輸出:
construct
desctruct
8.綁定值類型為引用類型
返回值類型為引用類型,返回全局變量的引用:
通常,當我們這是我們的真實意圖,函數在返回階段和綁定階段均不存在構造函數的調用,最終綁定值就是需要返回的全局變量,對最終綁定值的任何修改,均會反映到全局變量上。
例子:
依然使用參數為引用類型的函數舉例。
myClass& fun(myClass& mc)
{
?? ?return mc;
}
int main()
{
?? ?myClass mc;
?? ?myClass& result = fun(mc);
}
輸出:
construct
desctruct
9.一個思考
右值是不能綁定到左值引用的,但是當把右值付給左值會怎樣呢?
如下
int main()
{
?? ?myClass mc;
?? ?myClass result = move(mc);
}
答案是會調用移動構造函數創建一個新的對象,并且綁定到綁定值。
輸出:
construct
move consturct
desctruct
desctruct
另一個非常類似的例子:
int main()
{
?? ?myClass mc;
?? ?myClass&& mcRightRef = move(mc);
?? ?myClass result = mcRightRef;
}
和第一個非常類似,但是result的生成是調用拷貝構造函數而非移動構造函數完成的。
輸出:
construct
copy construct
desctruct
desctruct
這就涉及到了移動構造函數調用的時機,更多關于右值,右值引用和移動構造函數詳細的研究,待續。
原文鏈接:https://blog.csdn.net/siyue0211/article/details/76906981
相關推薦
- 2022-08-26 Python?Decorator裝飾器的創建方法及常用場景分析_python
- 2022-12-06 C#基礎教程之類class與結構struct的區別_C#教程
- 2022-05-19 Python?Timer和TimerFPS計時工具類_python
- 2022-07-09 基于fluttertoast實現封裝彈框提示工具類_Android
- 2022-07-22 spring-boot設置跨域訪問方式
- 2022-12-23 C++多線程之帶返回值的線程處理函數解讀_C 語言
- 2023-12-22 獲取微信小程序版本號,uni
- 2023-04-24 一文帶你深入了解C++中音頻PCM數據_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支