網站首頁 編程語言 正文
MyString的構造、析構、拷貝構造、賦值運算
class String { char* str; public: String(const char* p = NULL) :str(NULL) { if (p != NULL) { str = new char[strlen(p) + 1];//strlen()計算至'\0'截至的字符數 strcpy(str, p); } else { str = new char[1]; //額外提供一個空間 *str = '\0'; } } ~String() { if (str != NULL) { delete[] str; } str = NULL; } //ostream& operator<<(const String* const this, ostream &out) ostream& operator<<(ostream& out)const //重載插入操作符 { if (str != NULL) { out << str; } return out; } String(const String& s):str(NULL) { //str = s.str; 淺拷貝 是同一個空間,會造成一個空間釋放兩次 //深拷貝 str = new char[strlen(s.str)+1]; strcpy(str, s.str); } String& operator=(const String& s) { if(this != &s) { delete[]str; str = new char[strlen(s.str)+1] strcpy(str,s.str); } return *this; } }; ostream& operator<<(ostream& out, const String& s) { s << out; //s.operator<<(cout); //operator<<(&s1,cout); return out; } int main() { String s1("123"); s1 << cout; //s1.operator<<(cout); //operator<<(&s1,cout); cout << s1 << endl; //operator<<(cout, s1); }
前面之所以對空指針構建對象提供一個空間的原因:使其在賦值重載中只有指向堆區一種情況進行處理
通過此方式進行等號運算符重載,然后調動拷貝構造對s2進行重寫構造
輸出流重寫
class String { char* str; public: String(const char* p = NULL) :str(NULL) { if (p != NULL) { str = new char[strlen(p) + 1]; strcpy(str, p); } else { str = new char[1]; //額外提供一個空間 *str = '\0'; } } ~String() { if (str != NULL) { delete[] str; } str = NULL; } //ostream& operator<<(const String* const this, ostream &out) ostream& operator<<(ostream& out)const //重載插入操作符 { if (str != NULL) { out << str; } return out; } }; int main() { String s1("123"); s1 << cout; //s1.operator<<(cout); //operator<<(&s1,cout); }
在這里通過改寫前的代碼 operator<<(&s1,cout);
不難看出,將cout初始化out,隨后將this.str輸出至out
ostream& operator<<(ostream& out)const
此處只能使用引用,因為cout在ostream類中進行轉移,該類將拷貝構造函數定義為保護訪問屬性,無法使用cout初始化out,繼而只能使用引用;同樣若我們不想使用實參去初始化形參,可以將拷貝構造函數定義為私有或保護類型
若希望輸出符合cout << s1 << endl;
此種形式,需要再寫一個全局函數
class String { char* str; public: String(const char* p = NULL) :str(NULL) { if (p != NULL) { str = new char[strlen(p) + 1]; strcpy(str, p); } else { str = new char[1]; //額外提供一個空間 *str = '\0'; } } ~String() { if (str != NULL) { delete[] str; } str = NULL; } //ostream& operator<<(const String* const this, ostream &out) ostream& operator<<(ostream& out)const //重載插入操作符 { if (str != NULL) { out << str; } return out; } }; ostream& operator<<(ostream& out, const String& s) { s << out; //s.operator<<(cout); //operator<<(&s1,cout); return out; } int main() { String s1("123"); s1 << cout; //s1.operator<<(cout); //operator<<(&s1,cout); cout << s1 << endl; //operator<<(cout, s1); }
通過此種形式進行翻轉,繼而達到符合 cout << s1 << endl;
的形式
MyString加號運算符重載
int main() { String s1("123"); String s2("456"); String s3; s3 = s1 + s2; S3 = s1 + "789"; s3 = "789" + s1; }
分別寫三個加號運算符重載,來對應上面的三個情況(類+類、類+字符串、字符串+類)
String operator+(const String& s)const { char *p = new char(strlen(this->str) + strlen(s.str) + 1); strcpy(p, this->str); strcat(p, s.str); return String(p); }
第一個為成員函數,但是存在內存泄漏,需要進行下面的步驟
在私有成員變量中,創建一個新的構造函數,直接將p給到str,而沒有創建新的空間;并且在加號運算符重載進行修改使其調用私有的構造函數
private: String(char*p,int)//兩個參數與公有構造區分 { str = p; } public: String operator+(const String& s)const { char *p = new char(strlen(this->str) + strlen(s.str) + 1); strcpy(p, this->str); strcat(p, s.str); return String(p,1); }
這樣就解決了原本內存泄漏的問題
接下來完成剩余兩個等號運算符重載
String operator+(const char* s)const { char* p = new char(strlen(this->str) + strlen(s) + 1); strcpy(p, this->str); strcat(p, s); return String(p, 1); //return *this + String(s) //上面的方式更方便,但是會構造兩個臨時對象 }
此處需要寫在類外,并且需要類內添加友元函數
friend String operator+(const char* t, const String s);
String operator+(const char* t, const String s) { char* p = new char(strlen(s.str) + strlen(t) + 1); strcpy(p, s.str); strcat(p, t); return String(p, 1); //return String(p) + s; 與上面同理,并且不需要友元函數 }
討論一個衍生問題
class String { private: char* str; public: String(const char* p = NULL) :str(NULL) { if (p != NULL) { str = new char[strlen(p) + 1]; strcpy(str, p); } else { str = new char[1]; //額外提供一個空間 *str = '\0'; } } ~String() { if (str != NULL) { delete[] str; } str = NULL; } String(const String& s) { //str = s.str; 淺拷貝 是同一個空間,會造成一個空間釋放兩次 //深拷貝 str = new char[strlen(s.str)]; strcpy(str, s.str); } String& operator=(const String& s) { if (this != &s) { delete[]str; str = new char[strlen(s.str)]; strcpy(str, s.str); } return *this; } }; String fun() { String s2("456"); return s2; } int main() { String s1; s1 = fun(); return 0; }
討論此程序執行的過程總共創建了多少個對象:
主函數運行首先開辟main函數棧幀,創建s1對象,默認構造只有大小為一的空間存放“\0”;之后執行fun()函數,分配fun棧幀,然后創建s2對象,創建一個堆區,str指向堆區空間;并且將按值返回,需要構建一個臨時對象(將亡值);
將亡值概念:表達式過程中所產生的不具有名字的一個實體,叫做將亡值;將亡值的生存期僅在表達式的調用過程中,表達式調用結束,將亡值就會結束
構建臨時對象調用拷貝構造,fun函數結束,s2生存期結束,調動析構函數;首先釋放s2調用資源,再歸還s2空間;回到主函數,把將亡值賦值給s1調用賦值語句,接著調用將亡值的析構函數進行釋放
這個過程中總共創建了三個對象,分別是s1、s2、將亡值對象
那么如果我們對fun以引用進行返回
String& fun() { String s2("456"); return s2; } int main() { String s1; s1 = fun(); return 0; }
當以引用返回,就不會返回一個s2的備份,從引用底層來看會返回s2的地址;這樣會從一個已死亡對象來獲取數據,繼而會得到隨機值
隨后介紹的右值拷貝構造與右值賦值語句可以解決這個問題
原文鏈接:https://blog.csdn.net/XXXTENTAC1ON/article/details/122852641
相關推薦
- 2023-01-08 簡化Cocos和Native交互利器詳解_React
- 2022-10-22 Python構建簡單線性回歸模型_python
- 2022-11-27 Python+decimal完成精度計算的示例詳解_python
- 2023-07-24 在excel里寫一行數字,復制后直接粘貼到表格不同列
- 2022-06-28 詳解Python中遞歸函數的原理與使用_python
- 2022-11-08 服務器nginx權限被拒絕解決案例_nginx
- 2023-01-03 Android?自定義Livedata使用示例解析_Android
- 2023-03-28 python?label與one-hot之間的互相轉換方式_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支