strcpy函数_错误更正(拷贝赋值函数的正确使用姿势)
這是一篇對什么是C++的The Rule of Three的錯誤更正和詳細說明。
閱讀時間7分鐘。難度???
雖然上一篇文章的閱讀量只有凄慘的兩位數(shù),但是懷著對小伙伴負責(zé)的目的,必須保證代碼的正確性。這是大廚做技術(shù)自媒體的態(tài)度。
前文最后一段代碼是這樣的:
class?Dog?{?private:
???char* name;
???int?age;
?public:
???'...省略構(gòu)造和拷貝構(gòu)造函數(shù)...'
????//拷貝賦值函數(shù)
???Dog& operator=(const?Dog& that) {
?????name = new?char[strlen(that.name)+1];
?????strcpy(name, that.name);
?????age = that.age;
???}
???'...省略析構(gòu)函數(shù)...'
};
先不談異常安全,這段拷貝賦值函數(shù)的代碼本身有什么問題?
有3個問題:
沒有釋放原對象指針成員指向的內(nèi)容
沒有返回值
沒有自賦值檢查
下面我們一個一個分析。
?1???沒有釋放原對象指針
這個問題很嚴重,因為一定會造成內(nèi)存泄露。
原因是指針所指的內(nèi)存未被釋放,而指針又指向了別處。
例子如下,我們寫了一個main函數(shù),長這樣:
int?main(int?argc, char* argv[])?{????Dog D1("Bobby",?2);
????Dog D2("Teddy",?3);
????Dog D2 = D1;
}
D1和D2分別是是Dog的對象。根據(jù)構(gòu)造函數(shù)的定義,D2中的name指針指向了字符數(shù)組“Teddy”。而當(dāng)進行D2 = D1操作時,name =?new?char[strlen(that.name)+1]這一步會在D2中重新創(chuàng)建一個名字為name且指向“Bobby”的指針。
這么做也許編譯器不會報錯,但是會有問題。
因為在new一個name指針之前,原本的name指針指向的內(nèi)存并沒有被釋放。而新的name指針只對新創(chuàng)建的內(nèi)存負責(zé),老的內(nèi)存已經(jīng)變成無主之地。看來內(nèi)存泄露是逃不掉了。
這個問題看著復(fù)雜,解決的辦法倒是簡單,只需要在拷貝賦值函數(shù)體第一行加上 delete[] name就可以了。
class?Dog?{?private:
???char* name;
???int?age;
?public:
???'...省略構(gòu)造和拷貝構(gòu)造函數(shù)...'
????//拷貝賦值函數(shù)
???Dog& operator=(const?Dog& that) {
?????delete[] name; //釋放原對象指針成員指向的內(nèi)容
?????name = new?char[strlen(that.name)+1];
?????strcpy(name, that.name);
?????age = that.age;
???}
???'...省略析構(gòu)函數(shù)...'
};2???沒有返回值
第二個問題犯的錯很低級,拷貝賦值函數(shù)的行為和普通函數(shù)一樣需要一個返回值。而返回值的類型通常是類的對象的引用。
參照常用的寫法,這里返回*this(this是C++類的隱藏成員,表示對象本身)。
class?Dog?{?private:
???char* name;
???int?age;
?public:
???'...省略構(gòu)造和拷貝構(gòu)造函數(shù)...'
????//拷貝賦值函數(shù)
???Dog& operator=(const?Dog& that) {
?????delete[] name; //釋放原對象指針成員指向的內(nèi)容
?????name = new?char[strlen(that.name)+1];
?????strcpy(name, that.name);
?????age = that.age;
?????return?*this; //返回對象引用
???}
???'...省略析構(gòu)函數(shù)...'
};
另外大家可能有疑問為什么返回值是一個引用而不是一個值呢?
答案是只有引用才能進行連續(xù)賦值。
假設(shè)有3個Dog對象:D1、D2、D3,如果返回值不是引用,那么類似D1 = D2 = D3將不能通過編譯。
?3???沒有自賦值檢查
什么叫做自賦值?
就是兩個相同對象之間用等號連接,比如:
int?main(int?argc, char* argv[])?{????Dog D1("Bobby", 2);
????Dog D1 = D1; //同一個D1相互賦值
}
當(dāng)然,一般不會有人寫出這樣的代碼來。這里只是舉個簡單的例子,但是如果在大型項目中不同開發(fā)者對同一對象取了不同的別名,那么自賦值的情況是有可能發(fā)生的。
對于上面的Dog類而言,如果執(zhí)行D1 = D1,那么會發(fā)生下面的事情:
首先,對象D1中的name指針被析構(gòu),name指向的內(nèi)存被釋放;
然后,下一行中的strlen(that.name)又用到了D1的name所指向的內(nèi)存。
重點來了:這時你會驚訝地發(fā)現(xiàn)編譯器提示你name已經(jīng)不存在了!!!
因為在編譯器看來,你在做對同一對象先釋放了內(nèi)存再使用的非法事情!
就好比你是拆遷大隊的,你沒有確認拆的是不是自己的房子就不管三七二十一直接拆了,然而你晚上還要回家住......
C++真的燒腦,僅僅是不小心把自己賦值給了自己就把自己的一部分給搞丟了,這在其他語言中似乎是天方夜譚。但是C++似乎很情愿把事情搞復(fù)雜。
幸好,自賦值問題也很容易修復(fù),只需要在delete指針之前做一個自賦值的判斷。
完整代碼如下:
class?Dog?{?private:
???char* name;
???int?age;
?public:
???'...省略構(gòu)造和拷貝構(gòu)造函數(shù)...'
????//拷貝賦值函數(shù)
???Dog& operator=(const?Dog& that) {
?????if(this?!= &that) { //判斷是否自賦值
?????????delete[] name; //釋放原對象的指針指向的內(nèi)容
?????????name = new?char[strlen(that.name)+1];
?????????strcpy(name, that.name);
?????????age = that.age;
?????}
?????return?*this;
???}
???'...省略析構(gòu)函數(shù)...'
};
this?!= &that這個判斷的寫法看上去莫名其妙,大廚來給大家分析一下:
this代表D1=D1中等號左邊的D1,&that代表等號右邊的D1的引用(本質(zhì)上還是D1)。this和&that二者如果相等就說明是同一個對象,那么拷貝賦值函數(shù)就直接返回對象的引用。
至此,三個問題終于都解決了
?4???總結(jié)時刻
通過以上問題的剖析可以發(fā)現(xiàn),C++一大半奇奇怪怪行為的背后都有一個處理不當(dāng)?shù)闹羔槨?/p>
另外,寫一個正確的類真的一點都不簡單,需要考慮內(nèi)存泄露,返回值類型,自賦值等等情況。
打住,再說下去大廚真的轉(zhuǎn)行成C++專業(yè)勸退師了。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的strcpy函数_错误更正(拷贝赋值函数的正确使用姿势)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 眼睑手术多少钱啊?
- 下一篇: 地下城与勇士毁灭之护符