C++ string:準 STL Container

歷史

STL 最初是一套獨立的泛型庫(Alexander Stepanov 等人貢獻),后來被吸納進 C++ 標準庫;std::basic_string 則是早期 C++ 標準(Cfront / ARM 時代)就存在的“字符串類”,并非 STL 原生物。

std::string 實際上是 std::basic_string<char> 的 typedef,提供了類似 STL 容器的接口(迭代器、begin()/end()、push_back()、size()?等),因此常被稱作“近似 STL 容器”或“序列式容器”。

模擬實現 string 類

實現 string 類的構造、拷貝構造、賦值運算符重載、析構函數、以及對 string 對象的增刪改查。

成員變量

private:char* _str;size_t _size;size_t _capacity;

構造函數

提供缺省參數的構造函數,使用常量字符串初始化;因為需要兼容 C 字符串的 '\0',所以為 _str 多開 1 字節的空間用以存放 '\0' 。

myString(const char* str = ""):_size(strlen(str)),_capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}

拷貝構造?

注意需要對成員 _str 指向的空間深拷貝;另外還有一種寫時拷貝可以進一步節省空間并提高效率。

	myString(const myString& str):_str(nullptr),_size(str.size()),_capacity(str.capacity()){reserve(str.capacity());	// reserve 中已經為 '\0' 多開了 1 字節strcpy(_str, str.c_str());}

增 -- push_back

如果要向 _str 尾部增加元素,先要檢查空間是否足夠,如果不夠,封裝擴容邏輯至函數 reserve:

reserve

異地擴容至 n+1 字節,多出來的 1 字節是給 '\0' 的

	void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];	// 開新空間strcpy(tmp, _str);	// 拷貝數據,memcpy 也行swap(tmp, _str);	// 交換指針delete[] tmp;	// 釋放舊空間}}

接著在 push_back 中檢查空間是否足夠,如果不夠,使用一個 三目表達式 來決定擴容至多大空間;最后向開辟好的空間內寫入字符,并將下一個位置設置為 '\0',以兼容 C 類型的字符串;

	void push_back(const char& ch){if (_size == _capacity){size_t new_capacity = _capacity == 0 ? 2 : _capacity * 2;reserve(new_capacity);}_str[_size] = ch;_str[_size + 1] = '\0';_size++;}

增 -- insert

標準庫中實現了很多重載,我來模擬一部分:

myString& insert(size_t pos, const myString& str)

在 pos 位置,插入同類型 myString str;那么需要向后挪動 pos 位置之后的元素,每個元素挪動 str._size 個空間;這里需要獲取形參 str 的私有變量,實現簡單的函數來獲取:

	size_t size(){return _size;}size_t capacity(){return _capacity;}

接下來向后挪動元素,但是要判斷所需空間是否足夠,不夠就擴容:

	myString& insert(size_t pos, const myString& str){if (_capacity < _size + str.size())	// 判斷接下來需要的空間是否足夠{reserve(_size + str.size());}}

發現編譯這段報錯,原因是形參 str 對象被 const 修飾,不能調用非 const 成員函數,將成員函數也用 const 修飾其 this 指針即可:

	size_t size() const{return _size;}size_t capacity() const{return _capacity;}

擴容問題解決,從 pos 位置開始,向后挪動元素:

	myString& insert(size_t pos, const myString& str){if (_capacity < _size + str.size())	// 判斷接下來需要的空間是否足夠{reserve(_size + str.size());}size_t begin = pos, end = _size;	// _size 處的'\0'一起挪動while (end >= begin)	// 挪動數據{_str[end + str.size()] = _str[end];end--;}}

現在向 pos 處寫入 str._str 的數據,但需要獲取?str._str 私有指針變量以訪問其數據:

	char* c_str() const{return _str;}

插入數據之后,返回原 myString 對象引用:

myString& insert(size_t pos, const myString& str){if (_capacity < _size + str.size())	// 判斷接下來需要的空間是否足夠{reserve(_size + str.size());}size_t begin = pos, end = _size;	// _size 處的'\0'一起挪動while (end >= begin)	// 挪動數據{_str[end + str.size()] = _str[end];end--;}for (size_t i = 0; i < str.size(); i++)	// 插入數據{_str[pos + i] = str.c_str()[i];}return *this;}

但是在挪動數據時,有個 bug :如果傳入形參 pos 為 0 ,也就是頭插;

那么 begin 為 0 ,挪動數據 while 的循環終止條件是 end < begin ,end 要等于 -1 循環才會終止;

但關鍵問題在于 end 變量類型是無符號整數 size_t,當 end = 0,再 end-- 之后,end 會變成 42億多,這使 while 變成死循環;

解決辦法:可以強制類型轉換,不會有什么問題

		int begin = (int)pos, end = (int)_size;	// _size 處的'\0'一起挪動while (end >= begin)	// 挪動數據{_str[end + str.size()] = _str[end];end--;}

myString& insert (size_t pos, size_t n, char c)

在 pos 處插入 n 個 c 字符:判斷空間容量是否足夠 -- 挪動數據 -- 插入數據,與上面的實現類似,累了。

增 -- append

理解為什么大佬們會吐槽 string 的設計繁瑣了(lll¬ω¬)

myString& append (const myString& str)

追加同類型 myString :檢查容量,copy 該 myString 數據到 this->_str 末尾。

	myString& append(const myString& str){if (_capacity < _size + str.size()){reserve(_size + str.size());}strcpy(_str + _size, str.c_str());	// strcpy 會一并拷貝字符串末尾的 '\0'return *this;}

刪 -- pop_back

刪掉最后一個元素,這個接口是 C++11 增加的,可能是為了向 STL 的容器看齊吧

    void pop_back(){_size--;}

刪 -- erase

myString& erase (size_t pos = 0, size_t len = npos)

從 pos 開始,刪掉 len 個字符,npos 是無符號最大整數 0x7fffffff

實際上,是將從 pos+len 到 _size 的數據向前挪動 len 個位置進行覆蓋,最后 _size-len 即可

注意,如果 pos+len > _size ,即 len > _size-pos ,就會越界訪問,所以要處理 len 過大的問題

	myString& erase(size_t pos = 0, size_t len = string::npos){assert(pos < _size);if (len > _size - pos)	// 處理 len 過大造成越界訪問{len = _size - pos;}for (size_t i = 0; i < _size-(pos+len)+1; i++)	// 向前覆蓋數據{_str[pos + i] = _str[pos + len + i];}_size -= len;return *this;}

查 -- find

size_t find (const myString& str, size_t pos = 0) const

從 pos 位置開始,找子串,直接調用 C庫函數 strstr 找,strstr 找到返回 該子串在原字符串中的指針,找不到會返回 NULL

	size_t find(const myString& str, size_t pos = 0) const{char* sub = strstr(_str + pos, str.c_str());if (sub){return sub - _str;}else{return string::npos;}}

size_t find(char c, size_t pos)

找字符首次出現位置

    size_t find(char c, size_t pos){for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;}

創建子串 -- substr

myString substr (size_t pos = 0, size_t len = npos) const

從字符串 pos 位置開始,取 len 個字符創建新的子串,并傳值返回

為新對象開空間,拷貝子串內容

	myString substr(size_t pos = 0, size_t len = string::npos) const{assert(pos < _size);if (len > _size - pos)	// 處理 len 過大造成越界訪問{len = _size - pos;}myString sub;sub.reserve(len);	// 存 len 字節memcpy(sub._str, _str + pos, len);	// memcpy可以拷貝固定大小sub._size = len;sub._str[_size] = '\0';	// len 不包括 '\0'return sub;}

賦值運算符重載

myString& operator= (const myString& str)

兩個已存在的對象之間的賦值運算符重載,注意深拷貝

	myString& operator= (const myString& str){char* tmp = new char[str.capacity() + 1];strcpy(tmp, str._str);delete[] _str;	// 釋放舊空間_str = tmp;	// 指向新空間_size = str._size;_capacity = str._capacity;}

析構函數

~myString()

注意釋放對象中的資源即可

	~myString(){delete[] _str;_size = _capacity = 0;}

小結

基本沒有復用,主要是練個手熟;要想快,還得當 CV programmer!

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

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

相關文章

Golang學習筆記--語言入門【Go-暑假學習筆記】

目錄 基礎語法部分相關概念 基礎語法部分概念詳解 可見性 導包 內部包 運算符 轉義字符 函數 風格 函數花括號換行 代碼縮進 代碼間隔 花括號省略 三元表達式 數據類型部分相關概念 數據類型部分概念詳解 布爾類型 整型 浮點型 復數類型 字符類型 派生類型…

linux中kill 命令使用詳解

在Linux系統里&#xff0c;kill命令的主要功能是向進程發送信號&#xff0c;以此來控制進程的運行狀態。下面為你詳細介紹它的使用方法&#xff1a; 基礎語法 kill [選項] [進程ID]進程ID也就是PID&#xff0c;可通過ps、pgrep或者top等命令來獲取。 常用信號及其含義 信號可以…

Nginx 安裝與 HTTPS 配置指南:使用 OpenSSL 搭建安全 Web 服務器

Nginx 安裝與 HTTPS 配置指南:使用 OpenSSL 搭建安全 Web 服務器 一、Nginx安裝 1. 安裝依賴項 sudo yum groupinstall "Development Tools" -y # 非必須 sudo yum install pcre pcre-devel zlib zlib-devel openssl openssl-devel -y2.下載Nginx wget http://n…

寫個 flask todo app,簡潔,實用

- 此項目雖然看起來簡單&#xff0c;實際上&#xff0c;修改成自己喜歡的樣子&#xff0c;也是費時間的。 - 別人都搞AI 相關的項目&#xff0c;而我還是搞這種基礎的東西。不要灰心。 - 積累。不論項目大小&#xff0c;不論難易&#xff0c;只看是否有用。項目地址&#xff1a…

4麥 360度定位

要在 ESP32 上用 4 個麥克風實現 360 聲源定位&#xff0c;通常思路是通過 時延估計&#xff08;TDOA&#xff09; 幾何計算&#xff0c;核心流程&#xff1a;陣列布置將 4 個麥克風等間距布置成正方形&#xff08;或圓形&#xff09;。記陣列中心為原點&#xff0c;麥克風編號…

使用yolov10模型檢測視頻中出現的行人,并保存為圖片

一、使用yolov10模型檢測視頻中出現的行人&#xff0c;并保存為圖片&#xff0c;detect_person.py代碼如下&#xff1a;from ultralytics import YOLOv10 import glob import os import cv2 import argparsedef detect_person(videoPath, savePath):if not os.path.exists(save…

現在希望用git將本地文件crawler目錄下的文件更新到遠程倉庫指定crawler目錄下,命名相同的文件本地文件將其覆蓋

git checkout main git pull origin main $source “D:\黑馬大數據學習\crawler” $dest Join-Path (Get-Location) “crawler” if (-not (Test-Path $dest)) { New-Item -ItemType Directory -Path $dest | Out-Null } Copy-Item -Path $source* -Destination $dest -Recur…

網絡調制技術對比表

&#x1f4ca; 網絡調制技術全維度對比表?調制技術??簡稱??頻譜效率??抗噪性??功率效率??復雜度??關鍵特性??典型應用場景??幅度鍵控?ASK低差高低/低電路簡單&#xff0c;易受干擾遙控器、光通信(OOK)?頻移鍵控?FSK低-中中中中/中抗噪較好&#xff0c;頻譜…

優化 Elasticsearch JVM 參數配置指南

一、概述 Elasticsearch 是基于 JVM 的搜索和分析引擎。JVM 參數的合理配置直接影響著 Elasticsearch 的性能和穩定性。盡管 Elasticsearch 已經提供了默認的 JVM 設置&#xff0c;但在某些特定場景下&#xff0c;我們可能需要進行適當的調整和優化。 本文將詳細講述如何安全、…

Python, Go 開發如何進入心流狀態APP

要開發一款基于Python和Go語言、幫助用戶進入“心流”狀態&#xff08;高度專注、高效愉悅的心理狀態&#xff09;的應用&#xff0c;需結合兩種語言的技術優勢&#xff08;Go的高并發與性能、Python的靈活性與AI生態&#xff09;及心流觸發機制&#xff08;清晰目標、即時反饋…

一文詳解手機WiFi模塊與連接

目錄 1 硬件模塊 1.1 Wifi射頻模 1.2 電源管理模塊 2 軟件與協議棧 2.1 系統服務層 2.2 認證與協議處理 3 連接流程 3.1 開啟WiFi與掃描 3.2 選擇網絡與認證 3.3 連接與IP分配 4 特殊連接方式 4.1 WPS快速連接 4.2 熱點模式&#xff08;AP模式&#xff09; 4.3 U…

Java 網絡編程詳解:從基礎到實戰,徹底掌握 TCP/UDP、Socket、HTTP 網絡通信

作為一名 Java 開發工程師&#xff0c;你一定在實際開發中遇到過需要與遠程服務器通信、實現客戶端/服務端架構、處理 HTTP 請求、構建分布式系統等場景。這時&#xff0c;Java 網絡編程&#xff08;Java Networking&#xff09; 就成為你必須掌握的核心技能之一。Java 提供了豐…

Java面試題(中等)

1. 計算機網絡傳輸層有哪些協議&#xff1f;分別適用于什么場景&#xff1f;TCP協議(傳輸控制協議)?&#xff1a;面向連接、可靠傳輸&#xff0c;流量控制、擁塞控制。適用于要求數據完整性的場景&#xff0c;如文件傳輸、網頁瀏覽、電子郵件等。UDP協議 (用戶數據報協議)?&a…

Apache 消息隊列分布式架構與原理

消息隊列 基本概念 定義 消息隊列&#xff08;Message Queue, MQ&#xff09;是一種分布式中間件&#xff0c;通過異步通信、消息暫存和解耦生產消費雙方的機制&#xff0c;提供消息的順序性保證、可靠投遞和流量控制能力&#xff0c;廣泛應用于微服務解耦、大數據流處理等場景…

ModernBERT如何突破BERT局限?情感分析全流程解析

自2018年推出以來&#xff0c;BERT 徹底改變了自然語言處理領域。它在情感分析、問答、語言推理等任務中表現優異。借助雙向訓練和基于Transformer的自注意力機制&#xff0c;BERT 開創了理解文本中單詞關系的新范式。然而&#xff0c;盡管成績斐然&#xff0c;BERT 仍存在局限…

股票Level2逐筆成交及十檔訂單簿分鐘級Tick歷史行情數據詳細解析

本地股票數據處理與分析實戰指南 在量化投資與金融數據分析領域&#xff0c;高效處理本地存儲的股票數據是核心能力之一。本文將從數據類型定義、解析流程及實際應用角度&#xff0c;系統介紹如何基于CSV文件管理股票分鐘數據、高頻Tick數據、逐筆數據、Level2歷史行情等多樣化…

面向互聯網2C業務的分布式類Manus Java框架

本文介紹了阿里巴巴推出的分布式類ManusAgent框架——ali-langengine-dflow&#xff0c;旨在解決現有Agent架構在互聯網2C業務場景中的局限性。文章從背景出發&#xff0c;分析了當前主流Agent架構&#xff08;如Manus、字節TARS、AutoGLM&#xff09;存在的問題&#xff0c;如…

Java-82 深入淺出 MySQL 內部架構:服務層、存儲引擎與文件系統全覆蓋

點一下關注吧&#xff01;&#xff01;&#xff01;非常感謝&#xff01;&#xff01;持續更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持續更新中&#xff01;&#xff08;長期更新&#xff09; AI煉丹日志-30-新發布【1T 萬億】參數量大模型&#xff01;Kim…

開發避坑短篇(6):Vue+Element UI 深度選擇器實現表單元素精準對齊的技術實踐

需求 el-form 表單的el-input和el-select默認寬度度不一致&#xff0c;導致不對齊&#xff0c;如下圖。那么如何設置讓el-input和el-select的寬度度一致并對齊&#xff1f;<el-form class"page-form" :model"addForm" :rules"rules" :disable…

rust-參考與借用

參考與借用 在清單4-5中的元組代碼的問題在于&#xff0c;我們必須將String返回給調用函數&#xff0c;這樣我們才能在調用calculate_length之后繼續使用String&#xff0c;因為String已經被移動到了calculate_length中。相反&#xff0c;我們可以提供一個對String值的引用。引…