從零開始:C++ String類的模擬實現

文章目錄

  • 引言
  • 1.類的基本結構
  • 2.構造函數和析構函數
  • 3.基本成員函數
  • 總結

在這里插入圖片描述

引言

在C++編程中,字符串操作是非常常見且重要的任務。標準庫中的std::string類提供了豐富且強大的功能,使得字符串處理變得相對簡單。然而,對于學習C++的開發者來說,深入理解std::string的內部實現原理是非常有益的。通過親手實現一個類似的String類,不僅可以幫助我們掌握面向對象編程的基本概念,還能增強我們對內存管理和字符串操作的理解。

在這篇博客中,我們將從零開始,逐步實現一個自定義的C++ String類。我們的目標是構建一個功能完整且高效的字符串類,同時盡可能地模仿std::string的行為。我們將討論類的基本結構、構造函數和析構函數的實現、基本成員函數的設計、運算符重載、內存管理,以及如何編寫測試代碼來驗證我們的實現。

通過這篇文章,您將學到如何在C++中進行動態內存分配和管理,如何實現深拷貝和移動語義,如何重載運算符以提升類的易用性,等等。無論您是剛剛入門的C++學習者,還是希望深入理解C++底層實現的開發者,這篇文章都將為您提供寶貴的知識和實踐經驗。

讓我們一起來探索C++ String類的實現之旅吧!

1.類的基本結構

1.1定義類

#include<iostream>
#include<assert.h>
using namespace std;
namespace lyrics
{class string{public:typedef char* iterator;typedef const char* const_iterator;//迭代器iterator begin();iterator end();const_iterator begin()const;const_iterator end()const;//構造函數string(const char* str = "");string(const string& s);//析構函數~string();//const char* c_str() const;//返回大小size_t size() const;//運算符重載char& operator[](size_t pos);const char& operator[](size_t pos)const;//空間擴容void reserve(size_t n);//尾插一個字符void push_back(char ch);//尾插一個字符串void append(const char* str);//運算符重載+=操作string& operator+=(char ch);string& operator+=(const char* str);//插入操作,插入一個字符串和插入一個字符void insert(size_t pos, char ch);void insert(size_t pos, const char* str);//刪除某段字符void erase(size_t = 0, size_t len = npos);//查找某個字符串或者字符size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);//賦值拷貝string& operator=(const string& s);//交換函數void swap(string& s);//取子串string substr(size_t pos = 0, size_t = npos);//比較函數運算符重載bool operator<(const string& s)const;bool operator<=(const string& s)const;bool operator>(const string& s)const;bool operator>=(const string& s)const;bool operator==(const string& s)const;//清理void clear();private:size_t _size;size_t _capacity;char* _str;const static size_t npos;};//非成員函數,,重載流插入和流提取istream& operator>>(istream& is, string& str);ostream& operator<<(ostream& is, string& str);
}

用命名空間形成類域將其與全局作用域隔開,防止發生命名沖突
1.2私有成員變量

  1. size_t _size;

_size表示當前string的有效空間

  1. size_t _capacity;

_capaciity表示當前string的總的空間容量

  1. char _str;*

_str表示存儲字符串的指針

  1. const static size_t npos;

npos表示一個靜態變量

1.3公有成員函數

公有成員函數代碼上有標識

2.構造函數和析構函數

2.1構造函數

這里我們直接將構造函數和拷貝構造寫成一個函數

string::string(const char* str)//指定類域//strlen的效率很低//初始化列表+寫在內部函數:_size(strlen(str))
{_str = new char[_size + 1];_capacity = _size;strcpy(_str, str);
}

2.2賦值拷貝函數

注意:這里賦值拷貝函數由于我們不知道兩個串到底有多長,所以我們直接將需要賦值拷貝的串給釋放了,然后重新開一個空間,將s中的串拷貝給新的空間,這樣雖然很暴力,但是少了很多不必要的討論

string& string::operator=(const string& s)
{char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;return *this;
}

2.3c_str函數

const char* string::c_str() const
{return _str;
}

** 2.4析構函數**

由于str的空間是我們手動開辟的所以,需要我們用Delete來釋放,這里釋放之后將其置位空指針即可,然后重置我們的size和capacity

string::~string()
{delete[] _str;_str = nullptr;_size = 0;_capacity = 0;
}

3.基本成員函數

3.1獲取字符串長度

size_t string::size() const
{return _size;
}

3.2operator[]重載

這里直接返回pos位置對應的元素即可

char& string::operator[](size_t pos)
{assert(pos < _size);return _str[pos];//返回pos位置的字符
}

3.3const版本的operator[]重載

//const版本的[]重載
const char& string::operator[](size_t pos)const
{return _str[pos];
}

3.4預開辟空間

注意:這里預開辟的空間要是比實際空間小,則不進行操作,若預開辟的空間比實際空間大,則進行空間的開辟

void string::reserve(size_t n)
{if (n > _capacity){//開新空間char* tmp = new char[n + 1];//拷貝數據strcpy(tmp, _str);//釋放新空間delete[] _str;//指向新空間_str = tmp;//更新容量_capacity = n;}
}

3.5尾插

這里尾插一個字符也很簡單,先檢查一下空間是否允許再插入,如果空間不夠則先開辟兩倍的空間,如果以前的空間是0,則先預開辟4個空間

//尾插一個字符
void string::push_back(char ch)
{if (_capacity == _size){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size++] = ch;_str[_size] = '\0';
}

3.6尾插一個字符串

這里尾插一個字符串,只需要先檢查一下空間是否夠用,然后再進行尾插,尾插可以直接調用字符串拷貝函數,將字符串拷貝到指定的位置

//尾插一個字符串
void string::append(const char* str)
{size_t len = strlen(str);if (_capacity == _size){reserve(_size + len);//當前的size+len}//strcat(_str, str);//效率不高//從當前位置開始自己去找\0,所以效率不高strcpy(_str + _size, str);//_str+_size就是\0的位置_size += len;
}

3.7迭代器

注意:下面的迭代器iterator是提前在頭文件中聲明好的,在.cpp文件中直接用,不明白的可以看上面的頭文件中的聲明

  • 非const版本的迭代器
//普通版本的迭代器
string::iterator string::begin()
{return _str;
}
string::iterator string::end()
{return _str + _size;
}
  • const版本的迭代器

//const版本的迭代器
string::const_iterator string::begin()const
{return _str;
}
string::const_iterator string::end()const
{return _str + _size;
}

** 3.8operator+=重載**

由于在實際使用中push_back和append的使用確實比較少,,也沒有+=方便,所以下面我們直接重載一個operator+=操作,+=操作只需要復用上面的push_back和append即可

//運算符重載
//傳引用返回出了作用域這個對象還在
string& string::operator+=(char ch)
{push_back(ch);return *this;
}
string& string::operator+=(const char* str)
{append(str);return *this;
}

3.9隨機插入一個字符串和一個字符

  • 插入一個字符

這里還是需要檢查一下空間是否重充足,還需要檢查一下插入的位置是否合法,insert的效率也不是很高,因為它需要移動插入位置后面的整個子串,當頭插的時候時間復雜度變成了O(N)

void string::insert(size_t pos, char ch)
{assert(pos <= _size);if (_capacity == _size){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}size_t end = _size + 1;while (end > pos)//因為有符號和無符號比較,兩個類型不同會將有符號強制類型轉換成無符號//所以這里直接把pos強制類型轉換成int{_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size++;
}
  • 插入一個字符串

插入一個字符串,可以直接服用insert插入單個字符串的版本,這里我寫成了注釋,大家可以試試,如果不想復用還是可以參考上面插入單個字符串的思路,但是需要注意的是,移動的距離不是1了變成len了,還有一個需要注意的點,就是控制邊界條件,當end到達pos+len的時候由于這個位置的元素還是需要被移動,所以這里是大于的是pos+len-1

void string::insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_capacity == _size){reserve(_size + len);//當前的size+len}//第一種方法//int end = len - 1;//while (end >= 0)//{//	insert(pos, str[end]);//	end--;//}size_t end = _size + len;//找到插入的后一個位置while (end > pos + len - 1){_str[end] = _str[end - len];end--;}memcpy(_str + pos, str, len);_size += len;
}

4.0刪除某段字符串

注意:在聲明中len的缺省參數給的是npos,當我的長度大于pos對應的后面對應的長度的時候,這時候就有多少刪多少,所以我們需要判斷一下,第一個if判斷的就是判斷我們刪除的長度是否已經超過了后面的長度,如果超過了就直接進入第一個if刪除后面的所有,也就是把pos位置置為\0,然后將_size更新,如果不是的話可以直接將pos+len位置的子字符串拷貝到pos位置之后

//從pos位置刪除len個字符
void string::erase(size_t pos, size_t len)
{assert(pos < _size);//當刪除的長度len大于后面的長度的時候//直接把后面的刪完if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);//直接把后面的copy到前面_size -= len;}
}

4.1查找函數

  • 查找單個字符
size_t string::find(char ch, size_t pos)
{for (size_t i = pos;i < _size;i++){if (_str[i] == ch){return i;}}return npos;
}
  • 查找字符串

查找字符串的話可以直接用C語言的庫函數進行查找

size_t string::find(const char* sub, size_t pos)
{const char* str = strstr(_str + pos, sub);return str - _str;
}

4.2深拷貝

//深拷貝
string::string(const string& s)
{_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}

4.3交換函數

這里不用庫里的交換函數因為庫里的交換函數的效率太低了,我們可以簡單看看庫里交換函數的代碼

在這里插入圖片描述

這里可以看到庫里的swap函數是直接拷貝構造一個零時的對象,然后進行兩次賦值拷貝,這樣做效率是極低的,因為是內置類型,兩次賦值拷貝都會進行創建新空間,然后釋放舊的空間,這樣的成本是很大的,所以可以直接寫一個swap對內置類型進行交換,直接交換兩個指針的指向,還有size和capacity即可

void string::swap(string& s)
{//內置類型交換代價更小std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}

4.4取子串

string string::substr(size_t pos, size_t len)
{//檢查pos是否合法assert(pos <= _size);//如果len大于后面的長度那么就后面有多少取多少if (len > _size - pos){//直接取后面的子串string sub(_str + pos);//從pos位置開始進行拷貝構造!!!!//返回子串return sub;}else{//構造子串string sub;//預開辟空間sub.reserve(len);//循環拷貝for (size_t i = 0;i < len;i++){sub += _str[pos + i];}//返回子串return sub;}
}

4.5比較函數operator的一系列重載

這里只需要重載兩個即可,其他的只需要進行復用就夠了,比較函數的重載可以直接調用C語言中的字符串比較函數

bool string::operator<(const string& s)const
{return strcmp(_str, s._str) < 0;
}
bool string::operator<=(const string& s)const
{return *this < s || *this == s;
}
bool string::operator>(const string& s)const
{return !(*this <= s);
}
bool string::operator>=(const string& s)const
{return !(*this < s);
}
bool string::operator==(const string& s)const
{return strcmp(_str, s._str);
}

4.6流插入和流提取

  • 流插入

注意:流插入重載的時候需要清除前面的字符串,所以這里我們提供了一個clear函數進行以前字符串的清理,這里由于is不能識別空格或者回車,所以我們直接調用is的成員函數get,get可以識別空格和回車,然后識別到回車之后,直接停止賦值,返回值是istream

void string::clear()
{_str = '\0';_size = 0;
}
istream& operator>>(istream& is, string& str)
{str.clear();char ch = is.get();while (ch != ' '&& ch != '\n'){str += ch;}return is;
}
  • 流提取

流提取也不用直接訪問成員變量,流提取可以直接一個字符一個字符的訪問,通過operator[]的重載訪問,一個一個大打印

ostream& operator<<(ostream& os, string& str)
{for (size_t i = 0;i < str.size();i++){os << str[i];}return os;
}

總結

在這篇博客中,我們從零開始,逐步實現了一個自定義的 C++ String 類。通過這個過程,我們不僅深入了解了字符串操作的內部工作原理,還掌握了許多 C++ 編程的重要概念和技巧。讓我們回顧一下我們在這篇文章中所做的工作:

  1. 類的基本結構
    我們定義了 String 類的基本結構,包括私有成員變量和公共成員函數。我們了解了如何封裝數據,保護類的內部實現細節,并提供一個干凈的公共接口。

  2. 構造函數和析構函數
    我們實現了默認構造函數、拷貝構造函數、移動構造函數和析構函數,確保我們的 String 類能夠正確地初始化、復制、移動和銷毀對象。我們討論了深拷貝和移動語義的區別,以及如何有效地管理資源。

  3. 基本成員函數
    我們實現了獲取字符串長度的 length 函數和返回 C 風格字符串的 c_str 函數。這些函數使我們的 String 類更實用,并與 C++ 標準庫中的 std::string 類的行為保持一致。

  4. 運算符重載
    我們重載了拷貝賦值運算符和移動賦值運算符,以確保我們的 String 類支持賦值操作,同時有效地管理內存。我們還可以進一步擴展,重載其他運算符,如加法運算符和比較運算符。

  5. 內存管理
    我們深入探討了動態內存分配和釋放的細節,確保我們的 String 類不會產生內存泄漏。通過使用 RAII(資源獲取即初始化)原則,我們構建了一個健壯且高效的字符串類。

  6. 示例和測試
    通過示例代碼和單元測試,我們驗證了 String 類的正確性和功能。這不僅提高了我們的代碼質量,也幫助我們發現并修復了潛在的問題。

  7. 優化與改進
    雖然我們的 String 類已經具備了基本功能,但還有許多可以進一步優化和擴展的地方。我們可以添加更多的成員函數,如子字符串查找、字符串替換等,來增強類的功能。此外,性能優化也是一個重要方面,可以通過減少不必要的內存分配和拷貝來實現。

通過實現這個自定義的 String 類,我們不僅學會了如何在 C++ 中操作字符串,還增強了我們的面向對象編程技能和內存管理能力。希望這篇文章能夠激發您對 C++ 編程的興趣,并鼓勵您繼續探索和學習更多的編程技巧和設計模式。

感謝您的閱讀!如果您有任何問題或建議,請隨時在評論區留言,我們將一起討論和交流。

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

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

相關文章

C語言----斐波那契數列(附源代碼)

各位看官們好&#xff0c;當我寫了上一篇博客楊輝三角后&#xff0c;有一些看官叫我講一下斐波那契數列。對于這個大家應該是有了解的。最簡單的規律就是f(n)f(n-2)f(n-1)。就是當前是前兩項之和&#xff0c;然后下標1和0都是1.從第三項開始計算的。那么我們知道規律&#xff0…

位圖(c++)

文章目錄 1.位圖概念2.位圖的實現3.應用&#xff08;解決整形存在或次數問題&#xff09;3.1存在問題3.2次數問題 5.搜索的方法對比&#xff1a; 1.位圖概念 和哈希一樣&#xff0c;都是一個表來記錄某個元素的個數或者存在與否&#xff1b;不同的是哈希使用的計算機定義的完整…

旅游卡創業的機會在哪里?

在當今社會&#xff0c;旅游已經成為了人們休閑娛樂的重要方式之一。 隨著經濟的發展和人們生活水平的提高&#xff0c;越來越多的人開始追求更高品質的旅游體驗。因此&#xff0c;旅游卡創業應運而生&#xff0c;為游客提供了更加便捷、實惠的旅游服務。那么&#xff0c;旅游…

群輝部署小雅alist實現視聽盛會

最近群輝搭建起來了&#xff0c;開始整蠱影視庫&#xff0c;之前搞過nastool。這次折騰下小雅alist。 1.下載并安裝 直接在群輝的docker里面下載映像 主要映射下端口和文件夾 #token mytoken.txt 獲取地址&#xff1a;https://alist.nn.ci/zh/guide/drivers/aliyundriv…

Git使用(2):遠程倉庫

一、創建遠程倉庫 登錄碼云Gitee - 基于 Git 的代碼托管和研發協作平臺。 點擊右上角&#xff0c;新建倉庫。 創建完成&#xff0c;復制倉庫地址接下來要使用。 二、將idea項目推送到碼云 首先創建本地倉庫VCS -> Create Git Repository。然后選擇Manage Remotes&#xff0…

服務器是網絡中的重要設備

眾所周知&#xff0c;服務器是網絡中的重要設備&#xff0c;要接受少至幾十人、多至成千上萬人的訪問&#xff0c;因此對服務器具有大數據量的快速吞吐、超強的穩定性、長時間運行等嚴格要求。但是&#xff0c;今天我們了解的是GPU服務器&#xff0c;很明顯&#xff0c;從字面上…

機器學習的目的

機器學習的目的是讓計算機能夠從數據中學習并改善性能&#xff0c;以執行特定的任務而無需明確的編程指令。具體來說&#xff0c;機器學習旨在實現以下幾個主要目標&#xff1a; 1. 預測與泛化&#xff1a; 機器學習的一個主要目標是通過學習數據的模式和特征&#xff0c;從而對…

舊衣回收,整個項目環節詳細拆解

日常舊衣服很多人果斷丟垃圾箱&#xff0c;殊不知這背后隱藏著商機。大把人都在掘金的項目。 舊衣回收&#xff0c;眼下市場覆蓋率才占10%。絕對的藍海&#xff0c;干這種項目成本很低。小到自家的舊衣回收能換小錢&#xff0c;大到開公司做分揀撈利潤。 說到這里&#xff0c…

用友hr軟件統一認證與致遠OA單點登錄身份周期管理怎么做

一、引言 隨著企業信息化建設的深入&#xff0c;各類管理軟件如用友HR、致遠OA等已經成為事業單位日常運營不可或缺的工具。用友HR軟件以其強大的人力資源管理功能&#xff0c;幫助企事業單位實現員工信息的集中管理&#xff1b;而致遠OA則以其便捷的辦公流程管理&#xff0c;…

機器學習概念:一些基本概念

目錄 數據集 (Dataset)&#xff1a;用于訓練和評估模型的數據集合。 特征 (Feature)&#xff1a;描述數據的屬性或變量&#xff0c;用于訓練模型。 標簽 (Label)&#xff1a;在監督學習中&#xff0c;與輸入數據相關聯的輸出結果。 模型 (Model)&#xff1a;對數據的某種假…

springcloud簡單了解及上手

springcloud微服務框架簡單上手 文章目錄 springcloud微服務框架簡單上手一、SpringCloud簡單介紹1.1 單體架構1.2 分布式架構1.3 微服務 二、SpringCloud與SpringBoot的版本對應關系2022.x 分支2021.x 分支2.2.x 分支 三、Nacos注冊中心3.1 認識和安裝Nacos3.2 配置Nacos3.3 n…

C++ 并發編程指南(11)原子操作 | 11.6、計算機內存結構

文章目錄 一、計算機內存結構1、內存的基本組成2、內存的類型3、內存的結構層次4、CPU架構5、局部性原理6、總結 前言 在探討計算機的運行效率和數據處理能力時&#xff0c;內存結構無疑是一個至關重要的部分。內存&#xff0c;作為計算機系統中的關鍵組件&#xff0c;承擔著存…

vue從入門到精通(一):Vue模板語法

一&#xff0c;模板語法 Vue 使用一種基于 HTML 的模板語法&#xff0c;使我們能夠聲明式地將其組件實例的數據綁定到呈現的 DOM 上。所有的Vue模板都是語法層面合法的 HTML&#xff0c;可以被符合規范的瀏覽器和 HTML 解析器解析。 Vue模板語法有2大類: 插值語法: 功能:用于解…

請介紹下H264的多參考幀技術及其應用場景,并請說明下為什么要有多參考幀?

H.264&#xff08;也稱為H.264/AVC&#xff09;的多參考幀機制是其編碼效率和質量提升的關鍵部分。這個機制允許編碼器在編碼當前幀時&#xff0c;參考多個之前已編碼的幀。這種多參考幀的方法為編碼器提供了更多的選擇&#xff0c;使其能夠更準確地預測當前幀的內容&#xff0…

【保姆級介紹自動化的講解】

&#x1f308;個人主頁: 程序員不想敲代碼啊 &#x1f3c6;CSDN優質創作者&#xff0c;CSDN實力新星&#xff0c;CSDN博客專家 &#x1f44d;點贊?評論?收藏 &#x1f91d;希望本文對您有所裨益&#xff0c;如有不足之處&#xff0c;歡迎在評論區提出指正&#xff0c;讓我們共…

SCP‘s Story

越過“第二夜”的星星&#xff0c;越過“邁克爾連續線”和“禁運線”&#xff0c;在“煤炭之路”最遠的一站&#xff0c;有一顆眼淚。這不是織物或紙上的撕裂&#xff0c;而是現實中的撕裂&#xff0c;是物理定律和常識失效的地方。 有些人稱之為黑洞&#xff0c;銀河系中最大…

【C語言】4.C語言數組(2)

文章目錄 6. 二維數組的創建6.1 ?維數組的概念6.2 ?維數組的創建 7. 二維數組的初始化7.1 不完全初始化7.2 完全初始化7.3 按照?初始化7.4 初始化時省略?&#xff0c;但是不能省略列 8. 二維數組的使用8.1 ?維數組的下標8.2 ?維數組的輸?和輸出 9. 二維數組在內存中的存…

利用一段代碼輕松繞過PHP授權系統

利用一段代碼輕松繞過PHP授權系統 第一步&#xff1a;首先你需要改名全局文件 比如說全局文件 common.php&#xff0c;那么 你將他改為core.php 第二步&#xff1a;創建文件 創建一個文件&#xff0c;和改名前的全局文件名稱一樣&#xff0c;然后把以下代碼復制進去就OK了 …

行列視在做報表之前需要準備哪些前期工作

行列視是一款功能強大的生產數據分析和報表生成工具&#xff0c;使用它進行報表制作之前&#xff0c;確實需要一些前期準備工作&#xff0c;以確保報表的準確性和有效性。以下是進行行列視報表制作前需要準備的一些關鍵步驟&#xff1a; 1.明確報表需求&#xff1a; - 確定報表…

【MySQL01】【 Explain 命令詳解】

文章目錄 一、前言二、Explain 概覽三、Explain 詳解1. id2. select_type3. table4. type5. possible_keys6. key7. key_len8. ref9. rows10. filtered11. extra 列 四、補充1. EXPLAIN 擴展1.1 Extend EXPLAIN1.2 JSON 格式的執行計劃 2. Intersection、Union、Sort-Union 索引…