C++ 類和對象 拷貝構造函數

一 拷貝構造函數的概念:

拷貝構造函數是一種特殊的構造函數,用于創建一個對象是另一個對象的副本。當需要用一個已存在的對象來初始化一個新對象時,或者將對象傳遞給函數或從函數返回對象時,會調用拷貝構造函數。

二?拷貝構造函數的特點:

1:拷貝構造函數是構造函數的一個重載形式。

2:拷貝構造函數的參數只有一個且必須是類類型對象的引用,使用傳值方式編譯器直接報錯, 因為會引發無窮遞歸調用。

3:若未顯式定義,編譯器會生成默認的拷貝構造函數。 默認的拷貝構造函數對象按內存存儲按 字節序完成拷貝,這種拷貝叫做淺拷貝,或者值拷貝。

注意:在編譯器生成的默認拷貝構造函數中,內置類型是按照字節方式直接拷貝的,而自定 義類型是調用其拷貝構造函數完成拷貝的。

4:編譯器生成的默認拷貝構造函數已經可以完成字節序的值拷貝了,還需要自己顯式實現嗎? 當然像日期類這樣的類是沒必要的。

2.1 代碼示例:

class Time 
{
public:// 普通構造函數Time(int hour = 0, int minute = 0, int second = 0) {_hour = hour;_minute = minute;_second = second;}// 拷貝構造函數,使用引用傳遞Time(const Time& other) {_hour = other._hour;_minute = other._minute;_second = other._second;}void Print() const {std::cout << _hour << ":" << _minute << ":" << _second << std::endl;}private:int _hour;int _minute;int _second;
};int main()
{Time t1(10, 20, 30);   // 使用普通構造函數//構造函數的重載Time t2 = t1;          // 使用拷貝構造函數//Time t2(t1);        // 拷貝構造的另一種寫法t1.Print();t2.Print();return 0;
}

輸出:

2.2 為什么要使用引用呢?

我們在 increment 函數中改變x的值并沒有間接性改變a,這是因為傳過去的只是編譯器創建實參的一個副本,而修改副本怎么可能可以改變a呢?

#include <iostream>void increment(int x) 
{x = x + 1;  // 修改的是副本,不影響實參
}int main() 
{int a = 5;increment(a);  // 傳遞a的副本std::cout << a << std::endl;  // 輸出5,原始值a未被修改return 0;
}

知道傳值傳參的本質之后,再來想一想為什么要用引用?咱們先來說說如果沒用用引用的后果會是怎么樣,當把自定義類型傳出去后且不用引用或者指針來接收,它會

調用 Time(const Time other),其中 othert1 的按值傳遞副本。

為了按值傳遞,編譯器需要創建 other 的副本。

創建 other 的副本時,再次調用 Time(const Time other)

這個新調用的 Time(const Time other) 又需要創建自己的 other 副本,再次調用 Time(const Time other)

如此反復,導致無限遞歸調用,最終導致棧溢出。

圖:

C++規定,自定義類型的拷貝,都會調用拷貝構造

那為什么要引用呢?

首先我們來回顧一下引用 :

1:引用是現有變量的另一個名字。

2:它們不創建新對象,只是指向已有對象。

3:引用只是指向現有對象,不創建新副本

因為引用就是它本身,所以何來創建新副本這一說法,創建新副本是怕改變副本從而導致改變實參值

2.3 總結:

1:按值傳遞會遞歸:每次傳遞對象會復制對象,導致無限遞歸。

2:引用傳遞避免遞歸:引用只是指向對象本身,不會復制對象

三 默認拷貝構造:

當你沒有顯式定義拷貝構造函數時,編譯器會為你自動生成一個默認的拷貝構造函數。這個默認拷貝構造函數會逐個拷貝對象的所有成員變量。

3.1 內置類型與自定義類型的拷貝:

內置類型:如 int, char, float 等,拷貝時直接按照字節方式進行復制,也就是直接復制其值。

自定義類型:如類和結構體,拷貝時會調用該類型的拷貝構造函數。

3.2 代碼示例:

內置類型:

#include <iostream>class MyClass 
{
public:int x;  // 內置類型成員
};int main() 
{MyClass obj1;obj1.x = 10;MyClass obj2 = obj1;  // 使用編譯器生成的默認拷貝構造函數std::cout << "obj1.x: " << obj1.x << std::endl; std::cout << "obj2.x: " << obj2.x << std::endl;return 0;
}

輸出:

對于一個類里面只有內置類型成員那編譯器生成的默認拷貝構造會自動復制其值。

自定義類型:

#include <iostream>class Time 
{
public:// 默認構造函數Time() {  _hour = 0;_minute = 0;_second = 0;}// 拷貝構造函數Time(const Time& other) {_hour = other._hour;_minute = other._minute;_second = other._second;std::cout << "Time::Time(const Time& other)" << std::endl;}private:int _hour;int _minute;int _second;
};class MyClass 
{
public:int x;  // 內置類型成員Time t; // 自定義類型成員
};int main() 
{MyClass obj1;obj1.x = 10;MyClass obj2 = obj1;  // 使用編譯器生成的默認拷貝構造函數std::cout << "obj1.x: " << obj1.x << std::endl;std::cout << "obj2.x: " << obj2.x << std::endl; return 0;
}

當執行MyClass obj2 = obj1; 因obj1類里面有自定義類型 t 所以編譯器生成的默認拷貝構造會自動調用Time(const Time& other) 來完成

3.3 總結:

內置類型:編譯器默認拷貝構造函數會直接復制其值。

自定義類型:編譯器默認拷貝構造函數會調用該類型的拷貝構造函數來復制其內容。

四 內存分區:

要理解好深拷貝與淺拷貝那就得先了解內存是怎么樣分區的。

計算機程序運行時,內存通常被分為四個主要區域:棧區、堆區、全局靜態區和只讀區(常量區和代碼區)。

4.1 棧區:

局部變量:函數內部定義的變量。

形參(函數參數):函數定義時的參數。

返回地址:函數調用后的返回地址。

特點:

棧區中訪問速度快且棧的內存連續分配

因存儲的都是 局部/形參/返回地址 所以棧區空間小,存儲的生命周期短

在我們局部變量所在的函數執行完成時,它會自動釋放內存

4.2 堆區:

動態分配的數據:通過 newmalloc 等動態分配函數分配的內存。

特點:

因存儲的都是new 或者malloc開辟的空間所以堆區空間大,所以訪問速度慢

堆中的內存分配和釋放是通過指針進行的,可能不是連續的。

堆區的內存需要程序員手動管理,必須手動釋放動態分配的內存,否則會導致內存泄漏。

4.3 全區/靜態區:

全局變量:在所有函數外部定義的變量。

靜態變量:使用 static 關鍵字定義的變量。

特點:

全局變量和靜態變量在程序的整個運行期間一直存在,直到程序結束。

全局變量可以在程序的所有函數中訪問,靜態變量在聲明的作用域內共享

4.4 只讀常量區:

常量:程序中定義的常量。

代碼:程序的指令代碼。

特點:

常量區的數據在程序運行期間不能被修改,保證了數據的安全性和穩定性。

代碼區存儲程序的指令代碼,在程序運行時被載入內存以執行。

五 淺拷貝:

首先我們來回顧C語言里面的基本類型指針類型

5.1 基本類型:

基本類型是C語言內置的數據類型,它們用于存儲最基本的數值數據。常見的基本類型包括:int float char……

5.2 指針類型:

指針類型是存儲內存地址的數據類型。指針用于指向其他變量或對象在內存中的位置。

5.3 基本類型代碼示例:

#include <iostream>class BasicType 
{
public:int value;// 構造函數BasicType(int v) {value = v;}// 拷貝構造函數BasicType(const BasicType& other) {value = other.value;}
};int main() 
{BasicType obj1(10);BasicType obj2 = obj1;  // 淺拷貝,復制基本類型的值std::cout << "改變前: " << std::endl;std::cout << "obj1.value: " << obj1.value << std::endl;std::cout << "obj2.value: " << obj2.value << std::endl;obj2.value = 20;  // 修改obj2的值std::cout << "改變后: " << std::endl;std::cout << "obj1.value: " << obj1.value << std::endl;std::cout << "obj2.value: " << obj2.value << std::endl;return 0;
}

輸出:

值會被復制但修改新對象的值不會影響原對象。

5.3 指針類型代碼示例:

#include <iostream>class SimplePointer 
{
public:int* ptr;  // 成員變量 ptr// 構造函數SimplePointer(int value)
{ptr = (int*)malloc(sizeof(int));  // 動態分配內存并初始化if (ptr != nullptr) {*ptr = value;}
}SimplePointer(const SimplePointer& other) {this->ptr = other.ptr;  // 淺拷貝,復制內存地址}void print() const {std::cout << "Value: " << *ptr << std::endl;}
};int main() 
{SimplePointer obj1(10);  // 創建第一個對象,并將值初始化為10SimplePointer obj2(obj1);  // 使用拷貝構造函數(淺拷貝)// 打印初始值std::cout << "Initial values:" << std::endl;obj1.print();obj2.print();// 修改obj2的值*obj2.ptr = 20;// 打印修改后的值std::cout << "After change:" << std::endl;obj1.print();obj2.print(); return 0;
}

輸出:

復制內存地址,共享同一塊內存,修改會互相影響

六 深拷貝:

#include <iostream>
#include <cstdlib>
#include <cstring>class SimpleClass 
{
public:int* ptr;// 默認構造函數SimpleClass(int value) {ptr = (int*)malloc(sizeof(int));  // 動態分配內存并初始化if (ptr != nullptr) {*ptr = value;}}// 深拷貝構造函數SimpleClass(const SimpleClass& other) {ptr = (int*)malloc(sizeof(int));  // 分配新內存if (ptr != nullptr) {*ptr = *(other.ptr);  // 復制內容}}// 析構函數~SimpleClass() {if (ptr != nullptr) {free(ptr);  // 釋放內存}}void Print() const {if (ptr != nullptr) {std::cout << "Value: " << *ptr << std::endl;}}
};int main() 
{SimpleClass obj1(10);  // 創建對象,ptr 指向的值為 10SimpleClass obj2 = obj1;  // 使用深拷貝構造函數obj1.Print();obj2.Print();// 修改 obj2 的值if (obj2.ptr != nullptr) {*(obj2.ptr) = 20;}obj1.Print();obj2.Print();return 0;
}

輸出:

深拷貝不僅復制對象的指針成員,還為指針指向的內容分配新的內存,并復制原對象的數據。這樣,兩個對象擁有獨立的內存,修改一個不會影響另一個。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/43147.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/43147.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/43147.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

打卡第6天----哈希表

每天進步一點點,滴水石穿,日積月累,不斷提升。 數組和鏈表章節告一段落。開啟哈希表相關的。 哈希表能解決什么問題呢,一般哈希表都是用來快速判斷一個元素是否出現集合里 一、有效的字母異位詞 leetcode題目編號:242 題目描述: 給定兩個字符串 s 和 t ,編寫一個函數…

安裝和配置 MSYS2

MSYS2&#xff08;Minimal SYStem 2&#xff09;是一個為Windows平臺提供的輕量級Linux類系統&#xff0c;它集成了大量的GNU工具鏈、工具和庫&#xff0c;為開發者提供了一個類似于Linux的shell環境和完整的開發環境。以下是關于MSYS2的詳細介紹&#xff1a; 一、定義與特點 …

深入理解Java中的泛型編程

深入理解Java中的泛型編程 大家好&#xff0c;我是微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01; 1. 泛型的基礎概念 在Java中&#xff0c;泛型編程是一種強大的編程范式&#xff0c;它允許我們編寫可以操作各種類型的代…

vagrant遠程連接不上問題

如果使用的是vbox虛擬機的host_only的默認ip為56.1開頭如果使用這個1作為地址&#xff0c;只能使用轉發的形式連接&#xff0c;宿主機使用ssh -p 22222 name127.0.0.1 連接&#xff0c;如果配置的主機位大于1則可以使用虛擬機的ip地址連接。 1使用網絡地址轉換&#xff08;ant…

Linux忘記密碼重置root密碼、重置普通用戶密碼

重啟看到選項按e reboot 或 init 62、移動到Linux開頭的行在末尾添加 rw init/bin/bash3、按下Ctrlx引導啟動 mount -o remount,rw /輸入命令回車更改密碼,輸入新密碼&#xff0c;別用小鍵盤&#xff0c;容易出錯 passwd輸入兩次校驗&#xff0c;出現updated successfully就…

OceanBase 配置項系統變量實現及應用詳解(1):配置項的定義及使用方法

《OceanBase 配置項&系統變量實現及應用詳解》專題導讀 在使用OceanBase的過程中&#xff0c;看到大家經常會遇到“參數”、“配置項”、“系統變量”等概念&#xff0c;卻不太清楚它們是不是同一個東西&#xff0c;以及應該如何使用。一些對數據庫開發感興趣的朋友&#…

開源大模型對比

隨著chatgpt誕生&#xff0c;開源大模型的也獲得了突飛猛進的進展&#xff0c;值得關注的是國內本地私有大模型已經在很多真實的場景中落地&#xff0c;比如智能客服。美國的技術研發能力遙遙領先&#xff0c;但是不得不說落地應用這塊是我們的強項。企業使用大模型一般需要考慮…

【面向就業的Linux基礎】從入門到熟練,探索Linux的秘密(九)-git(1)

Git是一個版本管理控制系統&#xff08;縮寫VCS&#xff09;&#xff0c;它可以在任何時間點&#xff0c;將文檔的狀態作為更新記錄保存起來&#xff0c;也可以在任何時間點&#xff0c;將更新記錄恢復回來。 文章目錄 前言 一、git是什么 二、git基本概念 三、git基本命令 總結…

電商項目中分與元金額單位互轉實戰

在Java開發中&#xff0c;可能遇到金額單位的轉換&#xff0c;比如本系統用分作為金額的基本單位&#xff0c;對方系統用元作為金額的基本單位&#xff0c;這就需要進行單位轉換&#xff0c;記錄下來&#xff0c;方便備查。 一、分轉元 分轉元&#xff0c;分到元相差兩位&…

PHP源碼:新聞門戶系統(附管理后臺+前臺)

一. 前言 今天小編給大家帶來了一款可學習&#xff0c;可商用的&#xff0c;新聞門戶系統 源碼&#xff0c;支持二開&#xff0c;無加密。項目可以擴展為個人博客&#xff0c;和一些社交論壇網址。主要功能&#xff1a;支持文章管理&#xff0c;評論管理&#xff0c;分類管理等…

Kotlin linkedMapOf filterKeys

Kotlin linkedMapOf filterKeys fun main(args: Array<String>) {val lhm linkedMapOf<String, Any>(Pair("name", "phil"), //因為key相同都為 name&#xff0c;被后面的覆蓋。Pair("year", 2024),Pair("name", "f…

大語言模型的應用探索AI Agent初探!

前言 大語言模型的應用之一是與大語言模型進行聊天也就是一個ChatBot&#xff0c;這個應用已經很廣泛了。 接下來的一個應用就是AI Agent。 AI Agent是人工智能代理&#xff08;Artificial Intelligence Agent&#xff09;的概念&#xff0c;它是一種能夠感知環境、進行決策…

消防認證-防火窗

一、消防認證 消防認證是指消防產品符合國家相關技術要求和標準&#xff0c;且通過了國家認證認可監督管理委員會審批&#xff0c;獲得消防認證資質的認證機構頒發的證書&#xff0c;消防產品具有完好的防火功能&#xff0c;是住房和城鄉建設領域驗收的重要指標。 二、認證依據…

GEE代碼實例教程詳解:NDVI時間序列趨勢分析

簡介 在本篇博客中&#xff0c;我們將使用Google Earth Engine (GEE) 對MODIS NDVI數據進行時間序列趨勢分析。通過分析2001年至2021年的NDVI數據&#xff0c;我們可以了解植被覆蓋度隨時間的變化趨勢。 背景知識 MODIS數據集 MODIS&#xff08;Moderate Resolution Imagin…

Websocket在Java中的實踐——整合Rabbitmq和STOMP

大綱 Rabbitmq開啟STOMP支持 服務端依賴參數參數映射類配置類邏輯處理類 測試測試頁面Controller測試案例 在《Websocket在Java中的實踐——STOMP通信的最小Demo》一文中&#xff0c;我們使用enableSimpleBroker啟用一個內置的內存級消息代理。本文我們將使用Rabbitmq作為消息代…

【Unity2D 2022:Particle System】添加拾取粒子特效

一、創建粒子特效游戲物體 二、修改粒子系統屬性 1. 基礎屬性 &#xff08;1&#xff09;修改發射粒子持續時間&#xff08;Duration&#xff09;為3s &#xff08;2&#xff09;取消勾選循環&#xff08;Looping&#xff09; &#xff08;2&#xff09;修改粒子存在時間&…

SQL性能優化策略

發現問題 通過業務監控發現慢SQL或接口響應延遲。利用性能分析工具定位問題。 定位SQL語句 使用監控工具確定影響性能的SQL語句和表。 SQL查詢變慢原因 索引失效&#xff1a;查詢未使用索引或索引效率低。多表連接&#xff1a;JOIN操作導致性能下降。查詢字段過多&#xf…

Monitor結構解讀之EntryQ和WaitSet的區別

EntryQ&#xff08;或_EntryList&#xff09;和WaitSet&#xff08;或_WaitSet&#xff09;在Java的monitor機制中扮演著不同的角色&#xff0c;它們之間的主要區別體現在以下幾個方面&#xff1a; 1. 等待原因和機制 EntryQ&#xff08;或_EntryList&#xff09;&#xff1a…

面試常考題---128陷阱(詳細)

1.問題引入 分別引入了int和Integer變量&#xff0c;并進行比較 int b 128; int b1 128;Integer d 127; Integer d1 127;Integer e 128; Integer e1 128;System.out.println(bb1); System.out.println(dd1); System.out.println(ee1); System.out.println(e.equals(e1)…