網(wǎng)站首頁 編程語言 正文
引入
對于普通類型的對象來說,他們之間的復(fù)制很簡單:
int a = 10;
int b = a;
但是對于類對象來說,其中會(huì)存在許多的成員變量。
#include <iostream>
using namespace std;
class CExample {
private:
int a;
public:
//構(gòu)造函數(shù)
CExample(int b)
{ a = b;}
//一般函數(shù)
void Show ()
{
cout<<a<<endl;
}
};
int main()
{
CExample A(100);
CExample B = A; //注意這里的對象初始化要調(diào)用拷貝構(gòu)造函數(shù),而非賦值
B.Show ();
return 0;
}
從以上代碼可以看出系統(tǒng)為對象 B 分配了內(nèi)存并完成了與對象 A 的復(fù)制過程。就類對象而言,相同類型的類對象是通過拷貝構(gòu)造函數(shù)來完成整個(gè)復(fù)制過程的。
下面這個(gè)則是拷貝構(gòu)造函數(shù)的工作過程
#include <iostream>
using namespace std;
class CExample {
private:
int a;
public:
//構(gòu)造函數(shù)
CExample(int b)
{ a = b;}
//拷貝構(gòu)造函數(shù)
CExample(const CExample& C)
{
a = C.a;
}
//一般函數(shù)
void Show ()
{
cout<<a<<endl;
}
};
int main()
{
CExample A(100);
CExample B = A; // CExample B(A); 也是一樣的
B.Show ();
return 0;
}
在這里CExample(const CExample& C) 就是我們自定義的拷貝構(gòu)造函數(shù)。
一.什么是拷貝構(gòu)造函數(shù)
同一個(gè)類的對象在內(nèi)存中有完全相同的結(jié)構(gòu),如果作為一個(gè)整體進(jìn)行復(fù)制或稱拷貝是完全可行的。這個(gè)拷貝過程只需要拷貝數(shù)據(jù)成員,而函數(shù)成員是共用的(只有一份拷貝)。在建立對象時(shí)可用同一類的另一個(gè)對象來初始化該對象的存儲(chǔ)空間,這時(shí)所用的構(gòu)造函數(shù)稱為拷貝構(gòu)造函數(shù)。
拷貝構(gòu)造函數(shù)本質(zhì)上來說也是構(gòu)造函數(shù)。
二.什么情況下使用拷貝構(gòu)造函數(shù)
一般來說有以下三種情況:
- 用舊對象去初始化新對象
- 值傳遞—參數(shù)是類類型的值類型,從實(shí)參傳遞給形參的過程,是用實(shí)參去構(gòu)造形參
- 函數(shù)返回值是值類型–用局部對象去構(gòu)造臨時(shí)對象調(diào)用拷貝構(gòu)造
class A
{
public:
A(int i = 0):m_i(i)
{
cout<<"A(int) "<<m_i<<endl;
}
A(const A &a):m_i(a.m_i)
{
cout<<"A(A) "<<m_i<<endl;
}
~A()
{
cout<<"~A "<<m_i<<endl;
}
private:
int m_i;
};
void fn(A t) //2.將c傳遞給t的過程,是值傳遞,此時(shí)臨時(shí)對象形參t是新對象,用c去構(gòu)造t,調(diào)用拷貝構(gòu)造
{
cout<<"fn end"<<endl;
//在退出fn函數(shù)時(shí),將臨時(shí)對象t釋放,調(diào)用析構(gòu)函數(shù)
}
A test()
{
A d(40); //調(diào)用普通構(gòu)造A(int)構(gòu)造對象d
return d;
/*
3.在返回的時(shí)候,將局部對象的值給了臨時(shí)對象(值傳遞,調(diào)用拷貝構(gòu)造,用舊局部對象去構(gòu)造新的臨時(shí)對象)
然后局部對象d就釋放了,臨時(shí)對象將值帶回到主調(diào)函數(shù)中后,臨時(shí)對象的值才釋放
*/
}
A fnn()
{
A s(50); //A(int) 50
return s; //
/*1.s->臨時(shí)對象 拷貝構(gòu)造
2.析構(gòu)函數(shù)局部對象s
*/
}
void main()
{
A a(20); //A(int) 20
A b = a; //1.用a初始化b A(A)
cout<<"fn"<<endl;
A c(30); //A(int) 30
fn(c);
cout<<"test"<<endl;
c = test(); //臨時(shí)對象將值賦給c(調(diào)用賦值函數(shù))之后,調(diào)用析構(gòu),析構(gòu)臨時(shí)對象
cout<<"fnn"<<endl;
A t = fnn(); //臨時(shí)對象初始化新對象t,是否會(huì)調(diào)用拷貝構(gòu)造?--調(diào)用了,不過編譯器做過優(yōu)化
cout<<"main end"<<endl;
}
三.使用拷貝構(gòu)造函數(shù)需要注意什么
拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載形式。
拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須使用引用傳參,使用傳值方式會(huì)引發(fā)無窮遞歸調(diào)用。
若未顯示定義,系統(tǒng)生成默認(rèn)的拷貝構(gòu)造函數(shù)。默認(rèn)的拷貝構(gòu)造函數(shù)對象按內(nèi)存存儲(chǔ)按字節(jié)序完成拷貝,稱為:位拷貝。
四.深拷貝淺拷貝
4.1 淺拷貝
所謂淺拷貝,指的是在對象復(fù)制時(shí),只對對象中的數(shù)據(jù)成員進(jìn)行簡單的賦值,默認(rèn)拷貝構(gòu)造函數(shù)執(zhí)行的也是淺拷貝。大多情況下“淺拷貝”已經(jīng)能很好地工作了,但是一旦對象存在了動(dòng)態(tài)成員,指針,那么淺拷貝就會(huì)出現(xiàn)一些問題。
對于下面函數(shù)來說有指針作為數(shù)據(jù)成員,則用s1對象去構(gòu)造s2對象的時(shí)候,調(diào)用默認(rèn)拷貝構(gòu)造,用s1中的數(shù)據(jù)成員指針m_str去初始化s2對象中的數(shù)據(jù)成員m_str,即是s2.m_str = s1.m_str,那么導(dǎo)致兩個(gè)對象中的指針指向同一塊內(nèi)存單元,指向的都是構(gòu)造s1對象時(shí)開辟的內(nèi)存單元,所以在主函數(shù)退出時(shí)候要析構(gòu)s2和s1時(shí),將同一段空間釋放兩次出現(xiàn)內(nèi)存錯(cuò)誤。
class Str
{
public:
Str(const char *str = "")
{
m_str = new char[strlen(str)+1];
strcpy(m_str,str);
}
~Str()
{
delete[]m_str;
}
void Print()
{
cout<<m_str<<endl;
}
private:
char *m_str;
};
void main()
{
Str s1("pangpang");
Str s2(s1);
cout<<sizeof(Str)<<endl;
s1.Print();
s2.Print();
}
上述程序的內(nèi)存布局:
對于指針作為數(shù)據(jù)成員的類,用s1對象去構(gòu)造s2對象的時(shí)候,調(diào)用默認(rèn)拷貝構(gòu)造函數(shù)時(shí),二者指向同一內(nèi)存單元,即二者的初始地址相同這里均為0X0002000,當(dāng)我們構(gòu)造完以后將要進(jìn)行析構(gòu)時(shí),這里將會(huì)出現(xiàn)錯(cuò)誤:因?yàn)槲鰳?gòu)函數(shù)要釋放空間,而這里我們的空間對應(yīng)的是一塊空間,當(dāng)我們析構(gòu)完s2后:這一塊空間的內(nèi)容已經(jīng)被delete,而我們還需要析構(gòu)s1,即:一個(gè)內(nèi)存空間析構(gòu)了兩次,出現(xiàn)內(nèi)存錯(cuò)誤。
為了解決上述問題 我們就需要給s2中的m_str也開辟和s1中的m_str一樣大小的空間,所以我們就需要 深拷貝 。
4.2 深拷貝
在“深拷貝”的情況下,對于對象中動(dòng)態(tài)成員,就不能僅僅簡單地賦值了,而應(yīng)該重新動(dòng)態(tài)分配空間,如上面的例子就應(yīng)該按照如下的方式進(jìn)行處理:
class Str
{
public:
Str(const char *str = "")
{
m_str = new char[strlen(str)+1];
strcpy(m_str,str);
}
~Str()
{
cout<<"~Str"<<endl;
delete[]m_str;
}
Str(const Str& s)
{
cout<<"Str(Str)"<<endl;
m_str = new char[strlen(s.m_str)+1]; //開辟同樣大小的空間
strcpy(m_str,s.m_str); //將內(nèi)容拷貝成一樣的
}
void Print()
{
cout<<m_str<<endl;
}
private:
char *m_str;
};
void main()
{
Str s1("pangpang");
Str s2(s1); //拷貝構(gòu)造
s1.Print();
s2.Print();
}
現(xiàn)在的程序內(nèi)存布局為:
各自指向一段內(nèi)存空間,但它們指向的空間具有相同的內(nèi)容,這就是所謂的“深拷貝”。
原文鏈接:https://blog.csdn.net/weixin_56935264/article/details/126941151
相關(guān)推薦
- 2022-11-02 使用ggsignif優(yōu)雅添加顯著性標(biāo)記詳解_R語言
- 2023-07-16 spring boot多模塊打包 運(yùn)行
- 2022-09-29 解決react組件渲染兩次的問題_React
- 2022-08-04 pandas添加新列的5種常見方法_python
- 2022-11-30 Linux實(shí)現(xiàn)徹底清理空文件夾的方法詳解_linux shell
- 2021-12-03 Android識(shí)別NFC芯片制造商的方法_Android
- 2022-11-06 React?Hooks--useEffect代替常用生命周期函數(shù)方式_React
- 2022-10-30 移動(dòng)web開發(fā)技能之touch事件詳解_IOS
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支