網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
#include <iostream>
#include <string>
using namespace std;
void func(string str){
cout<<str<<endl;
}
int main(){
string s1 = "http:www.biancheng.net";
string s2(s1);
string s3 = s1;
string s4 = s1 + " " + s2;
func(s1);
cout<<s1<<endl<<s2<<endl<<s3<<endl<<s4<<endl;
return 0;
}
運(yùn)行結(jié)果:
http:www.biancheng.net
http:www.biancheng.net
http:www.biancheng.net
http:www.biancheng.net
http:www.biancheng.net http:www.biancheng.net
s1、s2、s3、s4 以及 func() 的形參 str,都是使用拷貝的方式來(lái)初始化的。
對(duì)于 s1、s2、s3、s4,都是將其它對(duì)象的數(shù)據(jù)拷貝給當(dāng)前對(duì)象,以完成當(dāng)前對(duì)象的初始化。
對(duì)于 func() 的形參 str,其實(shí)在定義時(shí)就為它分配了內(nèi)存,但是此時(shí)并沒(méi)有初始化,只有等到調(diào)用 func() 時(shí),才會(huì)將其它對(duì)象的數(shù)據(jù)拷貝給 str 以完成初始化。
當(dāng)以拷貝的方式初始化一個(gè)對(duì)象時(shí),會(huì)調(diào)用一個(gè)特殊的構(gòu)造函數(shù),就是拷貝構(gòu)造函數(shù)(Copy Constructor)。
#include <iostream>
#include <string>
using namespace std;
class Student{
public:
Student(string name = "", int age = 0, float score = 0.0f); //普通構(gòu)造函數(shù)
Student(const Student &stu); //拷貝構(gòu)造函數(shù)(聲明)
public:
void display();
private:
string m_name;
int m_age;
float m_score;
};
Student::Student(string name, int age, float score): m_name(name), m_age(age), m_score(score){ }
//拷貝構(gòu)造函數(shù)(定義)
Student::Student(const Student &stu){
this->m_name = stu.m_name;
this->m_age = stu.m_age;
this->m_score = stu.m_score;
cout<<"Copy constructor was called."<<endl;
}
void Student::display(){
cout<<m_name<<"的年齡是"<<m_age<<",成績(jī)是"<<m_score<<endl;
}
int main(){
const Student stu1("小明", 16, 90.5);
Student stu2 = stu1; //調(diào)用拷貝構(gòu)造函數(shù)
Student stu3(stu1); //調(diào)用拷貝構(gòu)造函數(shù)
stu1.display();
stu2.display();
stu3.display();
return 0;
}
運(yùn)行結(jié)果:
Copy constructor was called.
Copy constructor was called.
小明的年齡是16,成績(jī)是90.5
小明的年齡是16,成績(jī)是90.5
小明的年齡是16,成績(jī)是90.5
第 8 行是拷貝構(gòu)造函數(shù)的聲明,第 20 行是拷貝構(gòu)造函數(shù)的定義。拷貝構(gòu)造函數(shù)只有一個(gè)參數(shù),它的類型是當(dāng)前類的引用,而且一般都是 const 引用。
1) 為什么必須是當(dāng)前類的引用呢?
如果拷貝構(gòu)造函數(shù)的參數(shù)不是當(dāng)前類的引用,而是當(dāng)前類的對(duì)象,那么在調(diào)用拷貝構(gòu)造函數(shù)時(shí),會(huì)將另外一個(gè)對(duì)象直接傳遞給形參,這本身就是一次拷貝,會(huì)再次調(diào)用拷貝構(gòu)造函數(shù),然后又將一個(gè)對(duì)象直接傳遞給了形參,將繼續(xù)調(diào)用拷貝構(gòu)造函數(shù)……這個(gè)過(guò)程會(huì)一直持續(xù)下去,沒(méi)有盡頭,陷入死循環(huán)。
只有當(dāng)參數(shù)是當(dāng)前類的引用時(shí),才不會(huì)導(dǎo)致再次調(diào)用拷貝構(gòu)造函數(shù),這不僅是邏輯上的要求,也是 C++ 語(yǔ)法的要求。
2) 為什么是 const 引用呢?
拷貝構(gòu)造函數(shù)的目的是用其它對(duì)象的數(shù)據(jù)來(lái)初始化當(dāng)前對(duì)象,并沒(méi)有期望更改其它對(duì)象的數(shù)據(jù),添加 const 限制后,這個(gè)含義更加明確了。
另外一個(gè)原因是,添加 const 限制后,可以將 const 對(duì)象和非 const 對(duì)象傳遞給形參了,因?yàn)榉?const 類型可以轉(zhuǎn)換為 const 類型。如果沒(méi)有 const 限制,就不能將 const 對(duì)象傳遞給形參,因?yàn)?const 類型不能轉(zhuǎn)換為非 const 類型,這就意味著,不能使用 const 對(duì)象來(lái)初始化當(dāng)前對(duì)象了。
當(dāng)然,你也可以再添加一個(gè)參數(shù)為非const 引用的拷貝構(gòu)造函數(shù),這樣就不會(huì)出錯(cuò)了。換句話說(shuō),一個(gè)類可以同時(shí)存在兩個(gè)拷貝構(gòu)造函數(shù),一個(gè)函數(shù)的參數(shù)為 const 引用,另一個(gè)函數(shù)的參數(shù)為非 const 引用。
class Base{
public:
Base(): m_a(0), m_b(0){ }
Base(int a, int b): m_a(a), m_b(b){ }
private:
int m_a;
int m_b;
};
int main(){
int a = 10;
int b = a; //拷貝
Base obj1(10, 20);
Base obj2 = obj1; //拷貝
return 0;
}
b 和 obj2 都是以拷貝的方式初始化的,具體來(lái)說(shuō),就是將 a 和 obj1 所在內(nèi)存中的數(shù)據(jù)按照二進(jìn)制位(Bit)復(fù)制到 b 和 obj2 所在的內(nèi)存,這種默認(rèn)的拷貝行為就是淺拷貝,這和調(diào)用 memcpy() 函數(shù)的效果非常類似。
對(duì)于簡(jiǎn)單的類,默認(rèn)的拷貝構(gòu)造函數(shù)一般就夠用了,我們也沒(méi)有必要再顯式地定義一個(gè)功能類似的拷貝構(gòu)造函數(shù)。但是當(dāng)類持有其它資源時(shí),例如動(dòng)態(tài)分配的內(nèi)存、指向其他數(shù)據(jù)的指針等,默認(rèn)的拷貝構(gòu)造函數(shù)就不能拷貝這些資源了,我們必須顯式地定義拷貝構(gòu)造函數(shù),以完整地拷貝對(duì)象的所有數(shù)據(jù)。
下面我們通過(guò)一個(gè)具體的例子來(lái)說(shuō)明顯式定義拷貝構(gòu)造函數(shù)的必要性。
#include <iostream>
#include <cstdlib>
using namespace std;
//變長(zhǎng)數(shù)組類
class Array{
public:
Array(int len);
Array(const Array &arr); //拷貝構(gòu)造函數(shù)
~Array();
public:
int operator[](int i) const { return m_p[i]; } //獲取元素(讀取)
int &operator[](int i){ return m_p[i]; } //獲取元素(寫(xiě)入)
int length() const { return m_len; }
private:
int m_len;
int *m_p;
};
Array::Array(int len): m_len(len){
m_p = (int*)calloc( len, sizeof(int) );
}
Array::Array(const Array &arr){ //拷貝構(gòu)造函數(shù)
this->m_len = arr.m_len;
this->m_p = (int*)calloc( this->m_len, sizeof(int) );
memcpy( this->m_p, arr.m_p, m_len * sizeof(int) );
}
Array::~Array(){ free(m_p); }
//打印數(shù)組元素
void printArray(const Array &arr){
int len = arr.length();
for(int i=0; i<len; i++){
if(i == len-1){
cout<<arr[i]<<endl;
}else{
cout<<arr[i]<<", ";
}
}
}
int main(){
Array arr1(10);
for(int i=0; i<10; i++){
arr1[i] = i;
}
Array arr2 = arr1;
arr2[5] = 100;
arr2[3] = 29;
printArray(arr1);
printArray(arr2);
return 0;
}
運(yùn)行結(jié)果:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
0, 1, 2, 29, 4, 100, 6, 7, 8, 9
本例中我們顯式地定義了拷貝構(gòu)造函數(shù),它除了會(huì)將原有對(duì)象的所有成員變量拷貝給新對(duì)象,還會(huì)為新對(duì)象再分配一塊內(nèi)存,并將原有對(duì)象所持有的內(nèi)存也拷貝過(guò)來(lái)。這樣做的結(jié)果是,原有對(duì)象和新對(duì)象所持有的動(dòng)態(tài)內(nèi)存是相互獨(dú)立的,更改一個(gè)對(duì)象的數(shù)據(jù)不會(huì)影響另外一個(gè)對(duì)象,本例中我們更改了 arr2 的數(shù)據(jù),就沒(méi)有影響 arr1。
這種將對(duì)象所持有的其它資源一并拷貝的行為叫做深拷貝,我們必須顯式地定義拷貝構(gòu)造函數(shù)才能達(dá)到深拷貝的目的。深拷貝的例子比比皆是,除了上面的變長(zhǎng)數(shù)組類,使用的動(dòng)態(tài)數(shù)組類也需要深拷貝;此外,標(biāo)準(zhǔn)模板庫(kù)(STL)中的 string、vector、stack、set、map 等也都必須使用深拷貝。
讀者如果希望親眼目睹不使用深拷貝的后果,可以將上例中的拷貝構(gòu)造函數(shù)刪除,那么運(yùn)行結(jié)果將變?yōu)椋?, 1, 2, 29, 4, 100, 6, 7, 8, 9
0, 1, 2, 29, 4, 100, 6, 7, 8, 9
可以發(fā)現(xiàn),更改 arr2 的數(shù)據(jù)也影響到了 arr1。這是因?yàn)椋趧?chuàng)建 arr2 對(duì)象時(shí),默認(rèn)拷貝構(gòu)造函數(shù)將 arr1.m_p 直接賦值給了 arr2.m_p,導(dǎo)致 arr2.m_p 和 arr1.m_p 指向了同一塊內(nèi)存,所以會(huì)相互影響。
另外需要注意的是,printArray() 函數(shù)的形參為引用類型,這樣做能夠避免在傳參時(shí)調(diào)用拷貝構(gòu)造函數(shù);又因?yàn)?printArray() 函數(shù)不會(huì)修改任何數(shù)組元素,所以我們添加了 const 限制,以使得語(yǔ)義更加明確。
- 到底是淺拷貝還是深拷貝
如果一個(gè)類擁有指針類型的成員變量,那么絕大部分情況下就需要深拷貝,因?yàn)橹挥羞@樣,才能將指針指向的內(nèi)容再?gòu)?fù)制出一份來(lái),讓原有對(duì)象和新生對(duì)象相互獨(dú)立,彼此之間不受影響。如果類的成員變量沒(méi)有指針,一般淺拷貝足以。
另外一種需要深拷貝的情況就是在創(chuàng)建對(duì)象時(shí)進(jìn)行一些預(yù)處理工作,比如統(tǒng)計(jì)創(chuàng)建過(guò)的對(duì)象的數(shù)目、記錄對(duì)象創(chuàng)建的時(shí)間等,請(qǐng)看下面的例子:
#include <iostream>
#include <ctime>
#include <windows.h> //在Linux和Mac下要換成 unistd.h 頭文件
using namespace std;
class Base{
public:
Base(int a = 0, int b = 0);
Base(const Base &obj); //拷貝構(gòu)造函數(shù)
public:
int getCount() const { return m_count; }
time_t getTime() const { return m_time; }
private:
int m_a;
int m_b;
time_t m_time; //對(duì)象創(chuàng)建時(shí)間
static int m_count; //創(chuàng)建過(guò)的對(duì)象的數(shù)目
};
int Base::m_count = 0;
Base::Base(int a, int b): m_a(a), m_b(b){
m_count++;
m_time = time((time_t*)NULL);
}
Base::Base(const Base &obj){ //拷貝構(gòu)造函數(shù)
this->m_a = obj.m_a;
this->m_b = obj.m_b;
this->m_count++;
this->m_time = time((time_t*)NULL);
}
int main(){
Base obj1(10, 20);
cout<<"obj1: count = "<<obj1.getCount()<<", time = "<<obj1.getTime()<<endl;
Sleep(3000); //在Linux和Mac下要寫(xiě)作 sleep(3);
Base obj2 = obj1;
cout<<"obj2: count = "<<obj2.getCount()<<", time = "<<obj2.getTime()<<endl;
return 0;
}
運(yùn)行結(jié)果:
obj1: count = 1, time = 1488344372
obj2: count = 2, time = 1488344375
運(yùn)行程序,先輸出第一行結(jié)果,等待 3 秒后再輸出第二行結(jié)果。Base 類中的 m_time 和 m_count 分別記錄了對(duì)象的創(chuàng)建時(shí)間和創(chuàng)建數(shù)目,它們?cè)诓煌膶?duì)象中有不同的值,所以需要在初始化對(duì)象的時(shí)候提前處理一下,這樣淺拷貝就不能勝任了,就必須使用深拷貝了。
原文鏈接:https://blog.csdn.net/wteruiycbqqvwt/article/details/124641471
相關(guān)推薦
- 2022-11-05 ios開(kāi)發(fā)?try-catch引起的野指針問(wèn)題排查_(kāi)IOS
- 2022-06-10 C語(yǔ)言?推理證明帶環(huán)鏈表詳細(xì)過(guò)程_C 語(yǔ)言
- 2023-07-08 echarts 數(shù)值差距過(guò)大,有些數(shù)據(jù)會(huì)顯示不全
- 2022-08-19 python?GUI多行輸入文本Text的實(shí)現(xiàn)_python
- 2022-11-05 Android實(shí)現(xiàn)雙曲線折線圖_Android
- 2022-11-09 Android?使用maven?publish插件發(fā)布產(chǎn)物(aar)流程實(shí)踐_Android
- 2022-10-16 QT編寫(xiě)tcp通信工具(Server端)_C 語(yǔ)言
- 2022-05-02 C語(yǔ)言如何實(shí)現(xiàn)一些算法或者函數(shù)你知道嗎_C 語(yǔ)言
- 最近更新
-
- 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)證過(guò)濾器
- 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)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支