【C++】經典string類問題

目錄

1. 淺拷貝

2. 深拷貝?

3. string類傳統寫法

4. string類現代版寫法?

5. 自定義類實現swap成員函數

6. 標準庫swap函數的調用?

7. 引用計數和寫時拷貝


1. 淺拷貝

若string類沒有顯示定義拷貝構造函數與賦值運算符重載,編譯器會自動生成默認的,編譯器生成的默認版本只會簡單的復制指針地址,當用s1構造s2時,s1的_str指針存了"hello"的地址,拷貝給s2后,兩者都指向同一塊內存,這種拷貝方式叫做淺拷貝。當s1和s2先后析構時,這塊內存會被delete兩次,一旦其中一個對象釋放了這塊內存,_str所指的空間被釋放掉,另一個對象的_str指針就會變成野指針,再次釋放就會導致程序崩潰。

2. 深拷貝?

要避免淺拷貝問題,就需要自己實現深拷貝,讓s2有獨立的內存復制s1的內容,每個對象都有自己獨立的內存空間,這樣析構時各刪各的就不會出問題。

//拷貝構造函數(實現深拷貝)
string(const string& s)
{_str = new char[s._capacity + 1]; //新開辟內存strcpy(_str, s._str); //復制內容_size = s._size;_capacity = s._capacity;
}

如果一個類中涉及到資源的管理,其拷貝構造函數、賦值運算符重載以及析構函數必須要顯示給出來。

3. string類傳統寫法

class string
{
public://默認構造函數string(const char* str=""){if (str == nullptr) //strlen(nullptr)會觸發未定義行為可能導致程序崩潰{str = "";     //將nullptr轉為空字符串}_size = strlen(str);_str = new char[_size + 1];_capacity = _size;strcpy(_str, str);}//拷貝構造函數(深拷貝)string(const string& s):_str(new char[s._capacity + 1]), _size(s._size), _capacity(s._capacity){		strcpy(_str, s._str);  //拷貝字符串內容		}//賦值運算符重載(深拷貝)string& operator=(const string& s){if (this != &s)//防止自己給自己賦值{delete[] _str;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;return *this;}}//析構函數~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;	
};

4. string類現代版寫法?

class string
{
public://默認構造函數string(const char* str=""){if (str == nullptr){str = "";     //將nullptr轉為空字符串}_size = strlen(str);_str = new char[_size + 1];_capacity = _size;strcpy(_str, str);}//swap成員函數void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//拷貝構造函數優化 s2 = s1string(const string& s) //沒有在成員初始化列表進行顯式初始化時,使用了默認成員初始化器,防止默認初始化成隨機值。{string tmp(s._str); //用s的字符串數據創建局部對象tmp   這里也可以調用拷貝構造string tmp(s);swap(tmp);          //交換當前對象s2和tmp     tmp出了作用域調用析構函數銷毀}賦值運算符重載優化  s2 = s1//string& operator=(const string& s)//{		//	string tmp(s._str);//	swap(tmp);		//	return *this;//}//賦值運算符重載再優化  s2 = s1string& operator=(string tmp)  //傳值傳參觸發拷貝構造!使用s1構造局部對象tmp{swap(tmp);                 //交換當前對象s2和tmp return *this;}//析構函數~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;
};

5. 自定義類實現swap成員函數

當自定義string類實現了swap成員函數,執行?std::swap(s1,s2)?時會通過模版機制自動轉發到swap成員函數即s1.swap(s2),完成高效交換(交換內部指針,長度等,避免深拷貝),實現高效交換。如果自定義類中不實現成員swap,std::swap會走模版邏輯:

  • C++98:T c(a); a=b; b=c; 走“1次拷貝構造+2次拷貝賦值”的邏輯,3次深拷貝開銷。
  • C++11:T c(std::move(a)); a=std::move(b); b=std::move(c);移動構造+兩次移動賦值,依賴類的移動語義。

std::swap是一個模版函數,它的標準實現大致如下:

namespace std 
{// 默認模板:通過三次賦值實現交換(對于無swap成員函數的類類型交換:走深拷貝 對于內置類型的交換:無性能損耗)template <class T>void swap(T& a, T& b) {T temp = std::move(a);a = std::move(b);b = std::move(temp);}// 特化模板:若類型T存在swap成員函數,則調用該成員函數template <class T>void swap(T& a, T& b, std::enable_if_t<std::is_class_v<T> &&     {a.swap(b);     //……檢測T是否有swap成員函數,如果有就調用它。}
}

這個模版的特別之處在于:它會自動檢測類型T是否存在swap成員函數,若類型T未定義swap成員函數,std::swap會使用默認模版邏輯。

注意:std::swap模版的核心機制是嚴格匹配成員函數名swap,若成員函數名叫其它名字(如my_swap)無法轉發調用成員函數,只能走模版邏輯。

總結:所以,只要類有swap成員函數,調用std::swap(s1,s2)最終會轉調s1.swap(s2)。

    //swap成員函數void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}

示例:

int main()
{string s1("hello world");string s2("xxxxxxxxxxxxxxxxxxx");std::swap(s1, s2);//通過模版機制自動轉發調用成員函數s1.swap(s2)cout << s1 << endl;cout << s2 << endl;s1.swap(s2); //調用成員函數cout << s1 << endl;cout << s2 << endl;return 0;
}

運行結果:

std::string類自身實現了swap成員函數,用于高效交換兩個字符串的內部數據,直接交換內部的指針、大小、容量,相當于只交換“容器的殼”,數據本體不動,效率接近O(1),幾乎是“零拷貝”操作,比默認的拷貝邏輯快得多。

6. 標準庫swap函數的調用?

當調用 std::swap(basic_string) ?這個全局函數時,它內部實際上會轉發調用 basic_string 類的成員函數swap,也就是 std::basic_string::swap ?。 所以,當swap(s1,s2)時,會匹配到特化版本,實際執行的是string對象成員的swap邏輯,等價于s1.swap(s2),例:

#include <string>
#include <iostream>
using namespace std;int main() 
{string s1 = "Hello";string s2 = "World";//調用全局 swap(匹配特化版本)swap(s1, s2); //內部轉發調用s1.swap(s2);cout << "s1: " << s1 << ", s2: " << s2 << endl; // 輸出 s1: World, s2: Helloreturn 0;
}

簡單來說,怎么方便怎么寫,想寫什么寫什么,它們最終執行的是同一個邏輯。

成員函數風格:s1.swap(s2)? ?

全局函數風格:swap(s1,s2) std::(s1,s2)

7. 引用計數和寫時拷貝

引用計數

用來記錄有多少個對象正在引用該資源。在構造時,將資源的計數給成1,每增加一個對象使用該資源,就給計數增加1,當某個對象被銷毀時,先給該計數減1,然后再檢查是否需要釋放資源, 如果計數為1,說明該對象時資源的最后一個使用者,將該資源釋放;否則就不能釋放,因為還有其他對象在使用該資源。

寫時拷貝?

當對象被復制時,采用淺拷貝的方式,不立即拷貝實際數據(“寫時”才拷貝),此時多個對象共享同一資源。普通淺拷貝若多個對象共享數據,修改時會影響所有對象,所以當某個對象需要修改數據時,會觸發數據的深拷貝,確保修改不會影響其他共享該數據的對象。

比如:先構造s1,再調用拷貝構造構造s2,我們走淺拷貝,將引用計數+1變成2,銷毀s2的時候,不需要釋放資源,只需要將引用計數-1變成1,對象的資源留給最后一個使用者釋放。但是如果要修改s2,就需要引用計數-1,再執行深拷貝,修改s2的數據,對s2的修改不影響s1。

引用計數和寫時拷貝通過“延遲拷貝”和“共享資源”減少開銷,適用于讀多寫少的場景。

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

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

相關文章

kotlin中object:的用法

在Kotlin中&#xff0c;object: 用于聲明匿名對象&#xff08;Anonymous Object&#xff09;&#xff0c;這是實現接口或繼承類的輕量級方式&#xff0c;無需顯式定義具名類。以下是核心用法和場景&#xff1a; 1. 基本語法 val obj object : SomeInterface { // 實現接口ov…

js代碼04

題目 非常好。我們剛剛看到了回調函數在處理多個異步操作時會變得多么混亂&#xff08;回調地獄&#xff09;。為了解決這個問題&#xff0c;現代 JavaScript 提供了一個更強大、更優雅的工具&#xff1a;Promise。 Promise&#xff0c;正如其名&#xff0c;是一個“承諾”。…

Jenkins初探-通過Docker部署Jenkins并安裝插件

簡介 本文介紹了使用Docker安裝Jenkins并進行初始配置的完整流程。主要內容包括&#xff1a; (1)通過docker pull命令獲取Jenkins鏡像&#xff1b;(2)使用docker run命令啟動容器并映射端口&#xff1b;(3)訪問Jenkins界面獲取初始管理員密碼&#xff1b;(4)安裝推薦插件并創…

嵌入式開發:GPIO、UART、SPI、I2C 驅動開發詳解與實戰案例

&#x1f4cd; 本文為嵌入式學習系列第二篇&#xff0c;基于 GitHub 開源項目&#xff1a;0voice/EmbeddedSoftwareLearn &#x1f4ac; 作者&#xff1a;0voice &#x1f440; 適合對象&#xff1a;嵌入式初學者、STM32學習者、想搞明白外設驅動開發的C語言學習者 一、驅動是什…

常用 Linux 命令和 shell 腳本語言整理

目錄 一、Linux 命令大全 1、文件和目錄操作 &#xff08;1&#xff09;ls 列出目錄內容 &#xff08;2&#xff09;pwd 查看當前目錄 &#xff08;3&#xff09;cd 切換目錄 &#xff08;4&#xff09;mkdir 創建目錄 &#xff08;5&#xff09;cp 復制文件或目錄 &…

YOLOv12_ultralytics-8.3.145_2025_5_27部分代碼閱讀筆記-autobackend.py

autobackend.py ultralytics\nn\autobackend.py 目錄 autobackend.py 1.所需的庫和模塊 2.def check_class_names(names: Union[List, Dict]) -> Dict[int, str]: 3.def default_class_names(data: Optional[Union[str, Path]] None) -> Dict[int, str]: 4.cla…

【MySQL基礎】MySQL索引全面解析:從原理到實踐

MySQL學習&#xff1a; https://blog.csdn.net/2301_80220607/category_12971838.html?spm1001.2014.3001.5482 前言&#xff1a; 在前面我們基本上已經把MySQL的基礎知識都進行了學習&#xff0c;但是我們之前處理的數據都是十分少的&#xff0c;但是如果當我們的數據量很大…

第三十五章 I2S——音頻傳輸接口

第三十五章 I2S——音頻傳輸接口 目錄 第三十五章 I2S——音頻傳輸接口 1 I2S概述 1.1 簡介 1.2 功能特點 1.3 工作原理 1.4 利用DMA通信的I2S 1.4.1 I2S配合DMA通信工作原理 1.4.2 配置要點 2 應用場景 2.1 消費類音頻設備 2.2 專業音頻設備 2.3 通信設備 2.4 汽車電子 2.5 嵌…

產品-Figma(英文版),圖像的布爾類型圖例說明

文章目錄 Union SelectionSubtract SelectionIntersect SelectionExclude SelectionFlatten Selection Union Selection 把多個形狀合并成一個新的完整形狀&#xff0c;保留所有外部輪廓&#xff0c;內部不被切割。由于紅色的長方形在外面的一層&#xff0c;所以切割后&#x…

Windows CMD命令分類大全

?? ?一、系統與磁盤管理? ?系統信息? systeminfo&#xff1a;查看詳細硬件及系統配置&#xff08;版本/內存/補丁&#xff09;211 winver&#xff1a;快速檢查Windows版本11 msinfo32&#xff1a;圖形化系統信息面板811?磁盤工具? chkdsk /f&#xff1a;修復磁盤錯誤&…

【Dify系列】【Dify1.4.2 升級到Dify1.5.0】

1. 升級前準備工作 1.1 數據備份&#xff1a; 進入原安裝包 docker 目錄&#xff0c;備份“volumes”文件夾&#xff0c;此文件夾包含了 Dify 數據庫數據&#xff1a; rootjoe:/usr/local/dify/docker/volumes# pwd /usr/local/dify/docker/volumesrootjoe:/usr/local/dify/…

DeepSeek網頁版隨機點名器

用DeepSeek幫我們生成了一個基于html5的隨機點名器&#xff0c;效果非常棒&#xff0c;如果需要加入名字&#xff0c;請在代碼中按照對應的格式添加即可。 提示詞prompt 幫我生成一個隨機點名的HTML5頁面 生成真實一點的名字數據 點擊隨機按鈕開始隨機選擇 要有閃動的效果 &…

前后端分離實戰2----后端

戳我抵達前端 項目描述&#xff1a;用Vscode創建Spring Bootmybatis項目&#xff0c;用maven進行管理。創建一個User表&#xff0c;對其內容進行表的基本操作&#xff08;增刪改查&#xff09;&#xff0c;顯示在前端。 項目地址&#xff1a;戳我一鍵下載項目 運行效果如下&…

深入 ARM-Linux 的系統調用世界

1、引言 本篇文章以 ARM 架構為例&#xff0c;進行講解。需要讀者有一定的 ARM 架構基礎 在操作系統的世界中&#xff0c;系統調用&#xff08;System Call&#xff09;是用戶空間與內核空間溝通的橋梁。用戶態程序如 ls、cp 或你的 C 程序&#xff0c;無權直接操作硬件、訪問文…

LabVIEW鍵盤鼠標監測控制

通過Input Device Control VIs&#xff0c;實現對鍵盤和鼠標活動的監測。通過AcquireInput Data VI 在循環中持續獲取輸入數據&#xff0c;InitializeKeyboard與InitializeMouse VIs 先獲取設備ID 引用&#xff0c;用于循環內監測操作&#xff1b;運行時可輸出按鍵信息&#xf…

Linux 系統管理:自動化運維與容器化部署

在現代 IT 基礎設施中&#xff0c;自動化運維和容器化部署是提高系統管理效率和可維護性的關鍵。Linux 系統因其穩定性和靈活性而被廣泛應用于服務器和數據中心。本文將深入探討 Linux 系統管理中的自動化運維和容器化部署技術&#xff0c;幫助系統管理員實現高效運維和快速部署…

直播 APP 開發需要多少成本

直播行業的火爆催生了大量直播 APP 開發需求&#xff0c;而開發成本是開發者最關注的問題之一。其成本構成復雜&#xff0c;受功能需求、開發方式、技術難度等多種因素影響。? 基礎功能開發是成本的重要組成部分。用戶注冊登錄、直播間創建與管理、視頻播放、聊天互動等功能開…

Reactor操作符的共享與復用

在 Reactor 中&#xff0c;transform 和 transformDeferred 是兩個用于代碼復用和操作符鏈封裝的高級操作符。它們允許你將一組操作符封裝成一個函數&#xff0c;并在適當的時候應用到響應式流中。以下是它們的詳細總結&#xff1a; 1. transform 操作符 作用&#xff1a;tran…

C#中的Converter詳解

Converter是C#中一個非常有用的概念&#xff0c;主要用于類型轉換。它通常以委托或接口的形式出現&#xff0c;允許開發者定義如何將一種類型轉換為另一種類型。下面我將詳細介紹Converter的概念、使用場景&#xff0c;并以布爾型轉換為例展示具體應用。 Converter的基本概念 …

LabVIEW熒光微管圖像模擬

利用LabVIEW平臺&#xff0c;集成 PI 壓電平臺、Nikon 熒光顯微鏡及Andor sCMOS 相機等硬件&#xff0c;構建熒光微管滑行實驗圖像序列模擬系統。通過程序化模擬微管運動軌跡、熒光標記分布及顯微成像過程&#xff0c;為生物醫學領域微管跟蹤算法測試、運動特性分析提供標準化仿…