strcpy函數_錯誤更正(拷貝賦值函數的正確使用姿勢)

這是一篇對什么是C++的The Rule of Three的錯誤更正和詳細說明。

閱讀時間7分鐘。難度???

d2ae8d894c9d5ed2f1ecbc94410cbc46.png

雖然上一篇文章的閱讀量只有凄慘的兩位數e8ed25c9ca7b7b4da45247666c85dae0.gif,但是懷著對小伙伴負責的目的,必須保證代碼的正確性。這是大廚做技術自媒體的態度。

前文最后一段代碼是這樣的:

class?Dog?{
?private:
???char* name;
???int?age;
?public:
???'...省略構造和拷貝構造函數...'
????//拷貝賦值函數
???Dog& operator=(const?Dog& that) {
?????name = new?char[strlen(that.name)+1];
?????strcpy(name, that.name);
?????age = that.age;
???}
???'...省略析構函數...'
};

先不談異常安全,這段拷貝賦值函數的代碼本身有什么問題?

有3個問題:

  • 沒有釋放原對象指針成員指向的內容

  • 沒有返回值

  • 沒有自賦值檢查

下面我們一個一個分析。

?1???沒有釋放原對象指針

這個問題很嚴重,因為一定會造成內存泄露

原因是指針所指的內存未被釋放,而指針又指向了別處。

例子如下,我們寫了一個main函數,長這樣:

int?main(int?argc, char* argv[])?{
????Dog D1("Bobby",?2);
????Dog D2("Teddy",?3);
????Dog D2 = D1;
}

D1和D2分別是是Dog的對象。根據構造函數的定義,D2中的name指針指向了字符數組“Teddy”。而當進行D2 = D1操作時,name =?new?char[strlen(that.name)+1]這一步會在D2中重新創建一個名字為name且指向“Bobby”的指針。

這么做也許編譯器不會報錯,但是會有問題。

因為在new一個name指針之前,原本的name指針指向的內存并沒有被釋放。而新的name指針只對新創建的內存負責,老的內存已經變成無主之地。看來內存泄露是逃不掉了。

這個問題看著復雜,解決的辦法倒是簡單,只需要在拷貝賦值函數體第一行加上 delete[] name就可以了。

class?Dog?{
?private:
???char* name;
???int?age;
?public:
???'...省略構造和拷貝構造函數...'
????//拷貝賦值函數
???Dog& operator=(const?Dog& that) {
?????delete[] name; //釋放原對象指針成員指向的內容
?????name = new?char[strlen(that.name)+1];
?????strcpy(name, that.name);
?????age = that.age;
???}
???'...省略析構函數...'
};
2???沒有返回值

第二個問題犯的錯很低級b530a02792ba4a9a483e332040a47170.png,拷貝賦值函數的行為和普通函數一樣需要一個返回值。而返回值的類型通常是類的對象的引用。

參照常用的寫法,這里返回*this(this是C++類的隱藏成員,表示對象本身)。

class?Dog?{
?private:
???char* name;
???int?age;
?public:
???'...省略構造和拷貝構造函數...'
????//拷貝賦值函數
???Dog& operator=(const?Dog& that) {
?????delete[] name; //釋放原對象指針成員指向的內容
?????name = new?char[strlen(that.name)+1];
?????strcpy(name, that.name);
?????age = that.age;
?????return?*this; //返回對象引用
???}
???'...省略析構函數...'
};

另外大家可能有疑問為什么返回值是一個引用而不是一個值呢?

答案是只有引用才能進行連續賦值。

假設有3個Dog對象:D1、D2、D3,如果返回值不是引用,那么類似D1 = D2 = D3將不能通過編譯。

?3???沒有自賦值檢查

什么叫做自賦值?

就是兩個相同對象之間用等號連接,比如:

int?main(int?argc, char* argv[])?{
????Dog D1("Bobby", 2);
????Dog D1 = D1; //同一個D1相互賦值
}

當然,一般不會有人寫出這樣的代碼來。這里只是舉個簡單的例子,但是如果在大型項目中不同開發者對同一對象取了不同的別名,那么自賦值的情況是有可能發生的。

對于上面的Dog類而言,如果執行D1 = D1,那么會發生下面的事情:

首先,對象D1中的name指針被析構,name指向的內存被釋放;

然后,下一行中的strlen(that.name)又用到了D1的name所指向的內存。

重點來了:這時你會驚訝地發現編譯器提示你name已經不存在了!!!

5561f274749912e9a46bc7730ffb8541.gif

因為在編譯器看來,你在做對同一對象先釋放了內存再使用的非法事情!

就好比你是拆遷大隊的,你沒有確認拆的是不是自己的房子就不管三七二十一直接拆了,然而你晚上還要回家住......

0080f35ac95f4c86aed8d1df71ba601b.gif

C++真的燒腦,僅僅是不小心把自己賦值給了自己就把自己的一部分給搞丟了,這在其他語言中似乎是天方夜譚。但是C++似乎很情愿把事情搞復雜。

幸好,自賦值問題也很容易修復,只需要在delete指針之前做一個自賦值的判斷。

完整代碼如下:

class?Dog?{
?private:
???char* name;
???int?age;
?public:
???'...省略構造和拷貝構造函數...'
????//拷貝賦值函數
???Dog& operator=(const?Dog& that) {
?????if(this?!= &that) { //判斷是否自賦值
?????????delete[] name; //釋放原對象的指針指向的內容
?????????name = new?char[strlen(that.name)+1];
?????????strcpy(name, that.name);
?????????age = that.age;
?????}
?????return?*this;
???}
???'...省略析構函數...'
};

this?!= &that這個判斷的寫法看上去莫名其妙,大廚來給大家分析一下:

this代表D1=D1中等號左邊的D1,&that代表等號右邊的D1的引用(本質上還是D1)。this和&that二者如果相等就說明是同一個對象,那么拷貝賦值函數就直接返回對象的引用。

至此,三個問題終于都解決了ec71b8812d70d7797fba6e819f193a5c.png

?4???總結時刻

通過以上問題的剖析可以發現,C++一大半奇奇怪怪行為的背后都有一個處理不當的指針。

另外,寫一個正確的類真的一點都不簡單,需要考慮內存泄露,返回值類型,自賦值等等情況。

打住,再說下去大廚真的轉行成C++專業勸退師了。

98bd8ddea5826b57827661d8ef0864e4.png

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

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

相關文章

將Java應用程序打包為一個(或胖)JAR

這篇文章的目標是一個有趣但非常強大的概念:將應用程序打包為單個可運行的JAR文件,也稱為一個或胖 JAR文件。 我們習慣了大型WAR歸檔文件,其中包含所有打包在某些公用文件夾結構下的依賴項。 使用類似于JAR的打包,情況有所不同&a…

學習java的第三天,猜字符的小程序

關于猜字符的小程序 主要實現:隨機輸出5個字母,用戶輸入猜測的字母,進行對比得出結果 主要有3個方法:主方法main(); 產生隨機字符的方法generate(); 比較用戶輸入的字符與隨機產生的字符的方法check()&…

《Linux命令行與shell腳本編程大全 第3版》創建實用的腳本---10

以下為閱讀《Linux命令行與shell腳本編程大全 第3版》的讀書筆記,為了方便記錄,特地與書的內容保持同步,特意做成一節一次隨筆,特記錄如下:轉載于:https://www.cnblogs.com/guochaoxxl/p/7894995.html

python安裝包找不到setup_如何安裝沒有setup.py的Python模塊?

在系統上開始使用該代碼的最簡單的方法是:>將文件放入機器上的目錄中,>將該目錄的路徑添加到您的PYTHONPATH步驟2可以從Python REPL完成如下:import syssys.path.append("/home/username/google_search")您的文件系統的外觀示例&#xf…

Spring Batch中面向TaskletStep的處理

許多企業應用程序需要批處理才能每天處理數十億筆交易。 必須處理這些大事務集,而不會出現性能問題。 Spring Batch是一個輕量級且強大的批處理框架,用于處理這些大數據集。 Spring Batch提供了“面向TaskletStep”和“面向塊”的處理風格。 在本文中&a…

布局中常見的居中問題

說到布局除了浮動以及定位外還有一個不得不提的點,那就是居中,居中問題我們在網頁布局當中經常遇到,那么以下就是分為兩部分來講,一部分是傳統的居中,另一種則是flex居中,每個部分又通過分為水平垂直居中來…

unity json解析IPA后續

以前說到的,有很大的限制,只能解析簡單的類,如果復雜的就會有問題,從老外哪里看到一片博客,是將類中的list 等復雜對象序列化, using UnityEngine; using System.Collections; using System.Collections.…

改善代碼質量之內連臨時變量

待增轉載于:https://www.cnblogs.com/muyl/articles/6940896.html

python 矩陣元素相加_Numpy中元素級運算

標量與矩陣的運算:加法:values [1,2,3,4,5]values np.array(values) 5#現在 values 是包含 [6,7,8,9,10] 的一個 ndarray乘法:x np.multiply(some_array, 5)x some_array * 5矩陣與矩陣的運算:加法:對應元素相加,但形狀必須相…

排序算法——桶排序

把數據放進若干個桶,然后在桶里用其他排序,近乎分治思想。從數值的低位到高位依次排序,有幾位就排序幾次。例如二位數就排兩次,三位數就排三次,依次按照個十百...的順序來排序。 第一次排序:50 12 …

原型設計模式:創建另一個小車

創建對象確實是一個耗時的過程,也是一件昂貴的事情。 因此,我們現在正努力節省時間和金錢。 我們該怎么做? 克隆奇跡多莉 有人記得多莉嗎? 是的,是綿羊,是第一個被克隆的哺乳動物。 好吧,我不想…

java實現周期任務_java定時任務的實現方式

本文列舉常見的java定時任務實現方式,并做一定比較。1. 循環內部sleep實現周期執行創建一個thread,run() while循環里sleep()來實現周期性執行; 簡單粗暴,作為一個初學者很容易想到。public class Task1 {public static void main(String[] a…

磁盤鏡像工具Guymager

磁盤鏡像工具Guymager在數字取證中,經常需要對磁盤制作鏡像,以便于后期分析。Kali Linux提供一款輕量級的磁盤鏡像工具Guymager。該工具采用圖形界面化方式,提供磁盤鏡像和磁盤克隆功能。它不僅生成dd的鏡像,還能生成EWF和AFF鏡像…

python怎么寫代碼求年華收益率_如何計算年化收益率?

關于投資年化收益率的計算,三思君覺得主要有三種,分別是一次性投資的收益率計算、定期定額的收益率計算、不定期不定額的收益率計算。1.一次性投資的收益率計算對于一次性投資的收益率,相信大家都會計算。比如,小李同學去年買了一…

HTTPS協議在Tomcat中啟用的配置

本文將講解HTTPS協議在Tomcat中啟用是如何配置的。 概念簡介 Tomcat 服務器是一個免費的開放源代碼的Web 應用服務器,屬于輕量級應用服務器,在中小型系統和并發訪問用戶不是很多的場合下被普遍使用,是開發和調試 JSP 程序的首選。 HTTP 超文本…

CSS3實現煙花特效 --web前端

煙花特效&#xff0c;比較簡單&#xff0c;直接貼代碼了……<!DOCTYPE html><html lang"en"><head> <meta charset"UTF-8"> <title>css3實現煙花特效</title> <style> * { margin: 0; padding: 0; } html{ widt…

虛擬機 java 開發_深入淺出 Java 虛擬機 · 通往高級 Java 開發的必經之路

第一章 JVM 內存模型Java 虛擬機(Java Virtual MachineJVM)的內存空間分為五個部分&#xff0c;分別是&#xff1a;程序計數器Java 虛擬機棧本地方法棧堆方法區。下面對這五個區域展開深入的介紹。1.1 程序計數器1.1.1 什么是程序計數器&#xff1f;程序計數器是一塊較小的內存…

java.lang.ClassNotFoundException:如何解決

本文適用于當前面臨java.lang.ClassNotFoundException挑戰的Java初學者。 它將為您提供此常見Java異常的概述&#xff0c;這是一個示例Java程序&#xff0c;可支持您的學習過程和解決策略。 如果您對與更高級的類加載器相關的問題感興趣&#xff0c;我建議您復習有關java.lang…

iOS10 App跳轉到系統設置

實現類似萬能鑰匙中點擊一個Wi-Fi跳轉到系統Wi-Fi設置界面的功能。 NSString * urlString "App-Prefs:rootWIFI";if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:urlString]]) {if ([[UIDevice currentDevice].systemVersion doubleValue…

python生成器 圖片分類_python批量處理圖片圖片Python迭代器和生成器介紹

Python迭代器和生成器介紹迭代器迭代器是一個實現了迭代器協議的對象&#xff0c;Python中的迭代器協議就是有next方法的對象會前進到下一結果&#xff0c;而在一系列結果的末尾是&#xff0c;則會引發StopIteration。在for循環中&#xff0c;Python將自動調用工廠函數iter()獲…