網站首頁 編程語言 正文
C++函數返回值與拷貝
先來談談對C++中函數返回return的理解,自己本來在學Java,但是平時學校的項目是用的C++,所以在平時搬磚時經常會有一些問題,今天就來談談前段時間注意到的一個很小的知識點,話不多說,先上列子。
首先我們創建一個簡單的Man類,實現它的無參構造函數、有參構造函數和析構函數:
class Man
{
public:
Man() {
cout << "構造" << endl;
data = new int(0); }
Man(const Man& m)
{
cout << "拷貝構造" << endl;
this->data = m.data;
}
~Man()
{
cout << "析構" << endl;
delete data;
}
int* data;
};
聲明一個get函數獲取一個Man的對象
Man get(Man& m)
{
cout << "----" << endl;
return m;
}
在main函數中執行下列代碼
void main()
{
Man m, n;
//cout << "before m=" << &m << "n=" << &n << endl;
*m.data = 5;
printf("m.data is %d\n", *m.data);
n = get(m);
printf("m.data is %d\n", *m.data);
printf("n.data is %d\n", *n.data);
system("pause");
}
你可以試著想一想三個printf的輸出結果分別是多少
執行結果如下圖所示:
在輸出結果里我們可以清楚的看到,Man m, n; 創建了m,n兩個對象,調用了構造函數,對m對象中的data賦值,然后我們調用get(Man& man) 函數,注意這里函數參數是引用類型,因此傳入的對象是m對象本身,這里我們要區別get(Man man) 兩種函數參數類型的區別,我稍后再提。get(Man& man) 函數調用完畢后,返回對象m。
按照我們過去的分析會認為對象n等于get函數返回的m對象 (n=m) (注意這里等號=被重載過),m對象中的int* data 成員值直接賦值給了n對象中的data成員,輸出時照理說m和n的data值都應該等于5的,但是:
為什么這里輸出結果卻表明這個data指針指向的空間被銷毀了?
為什么get函數執行里會多出了拷貝構造和析構這兩個過程呢?
如果我們返回值為Man&會有什么區別變化呢?
這里我們做一個對比,填加一個getR函數,返回值為Man& 引用類型:
Man& getR(Man& m)
{?
? ? cout << "----" << endl;
?? ?return m;
}
接下來我們調用getR這個函數看一看輸出結果:
void main()
{
?? ??? ?Man m, n;
?? ??? ?*m.data = 5;
?? ??? ?printf("m.data is %d\n", *m.data);
?? ??? ?n = getR(m);
?? ??? ?
?? ??? ?printf("m.data is %d\n", *m.data);
?? ??? ?printf("n.data is %d\n", *n.data);
?? ??? ?
?? ? ? ?system("pause");
?? ? ? ?}
執行結果如下圖所示:?
執行結果如下圖所示:
可以看到,當我們返回的是m對象的的引用時,getR 函數執行時沒有調用拷貝構造和析構函數
這里我解釋一下返回值不是引用的情況時整個函數執行的過程
(個人拙劣的理解)
我們再回到get這個函數:
Man get(Man& m)
{
cout << "----" << endl;
return m;
}
首先函數參數傳入m這個對象的引用我們毋庸置疑,關鍵就在return這里。
我們捋一捋函數從開始到結束這個過程,隨著Main函數調用get函數,get函數入棧,同時get方法對應的棧幀(儲存函數局部變量、返回地址等信息)也入棧,這里的局部變量也就是m對象的引用。
當我們return這個m對象時,會在內存中創建一個臨時的Man temp對象,同時這個temp對象調用其拷貝構造函數,也就是Man temp(m) 。
完成temp對象的創建后,get函數出棧,對應的棧區內容被銷毀,這時系統會調用m對象的析構函數,注意這里有一個陷阱?。。?!
由于m對象是在main方法下的棧區創建的,因此get方法出棧后,系統調用m析構函數并沒有真正把m對象在棧區銷毀(因為它根本就不是在get方法的棧區上),調用析構函數僅僅是將data指針所指向的內存空間被銷毀了(delete data;),這也解釋了為什么m.data的值為-572662307。
當main方法執行完畢后,m對象才會調用析構函數真正被銷毀,當然,這也會帶來另一個問題,data指向的內存區被執行了兩次delete,運行結束后你也就會發現還會有一個**“析構”**和一個內存問題報錯。
回到我們返回的值上:
n = get(m);
這里實際上可以理解成
Man temp(m);
n=temp;
當然由于get函數的退出調用析構函數時,data指針指向的內存區域數據已經被銷毀,自然n和m得到的值是一個錯誤值了。
總結
對于函數返回值類型為非引用類型(當然引用類型也可以理解為Man& temp=m),都是會在內存中創建一個臨時變量,將返回值拷貝到臨時變量中,而返回值是作為函數調用棧區中的局部變量,隨著函數的返回,棧區的銷毀,而被銷毀。
原文鏈接:https://blog.csdn.net/qq_39913402/article/details/105939391
相關推薦
- 2022-08-15 Spring框架??贾R點總結
- 2022-07-11 UVM中uvm_config_db非直線的設置與獲取
- 2022-06-07 Python批量解壓&壓縮文件夾的示例代碼_python
- 2022-03-14 python導出requirements.txt的幾種方法總結_python
- 2022-05-31 Android實現文件資源管理器雛形_Android
- 2022-03-14 ffmpeg開發讀取目錄列表
- 2022-07-27 C++詳細講解圖論的基礎與圖的儲存_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同步修改后的遠程分支