
前言string是 C 中處理字符串的核心工具比 C 語言的char*更安全高效這篇文章從string的基礎(chǔ)概念出發(fā)聚焦構(gòu)造、訪問、容量、修改等核心接口搭配測試、案例如分割網(wǎng)址講清用法與場景最后補(bǔ)充編譯器差異幫你系統(tǒng)掌握string的高效使用。一、string類的使用1.1 什么是stringC 語言中字符串是以\0結(jié)尾的若干個字符的集合為了操作方便C 語言string.h頭文件提供了一些系列的庫函數(shù)但是這些庫函數(shù)與字符串是分離開的不符合面向?qū)ο蟮乃枷攵业讓涌臻g需要用戶自己管理稍不留神可能還會越界訪問。基于上面這些原因C 標(biāo)準(zhǔn)庫提供了string類string類中提供了各種函數(shù)接口比如類的六個默認(rèn)成員函數(shù)、字符串插入刪除、運(yùn)算符重載等等我們可以使用string來實例化對象然后通過string的各種接口來完成對該對象的各種操作。string類的實現(xiàn)框架大概如下namespacestd{templateclassTclassstring{public:// string 的各種成員函數(shù)private:T*_str;size_t _size;size_t _capacity;//string 的其他成員變量比如npos};}我們打開 cplusplus.com 搜索string會發(fā)現(xiàn)string其實是basic_string類模板使用字符類型char實例化得到的一個類前面我們說了string是管理字符的指針那么string為什么要設(shè)計成模板呢因為編碼不同存儲一個字符所需要的空間也不同現(xiàn)如今常見的編碼有如下三類ASCII 編碼ASCII 全稱為美國信息交換標(biāo)準(zhǔn)代碼這是我們接觸到的第一種編碼由于英語所有的英文字母 (包括大小寫) 加上各種標(biāo)點符號以及特殊字符一共只有一百多種所以我們使用一個字節(jié)即可表示所有字符但是根據(jù) ASCII 編碼設(shè)計出的計算機(jī)不能表示其他國家的語言比如中文、日文、韓文等等。UnicodeUnicode 就是我們的統(tǒng)一碼也叫萬國碼它是為了解決傳統(tǒng)的字符編碼方案的局限而產(chǎn)生的它為每種語言中的每個字符設(shè)定了統(tǒng)一并且唯一的二進(jìn)制編碼以滿足跨語言、跨平臺進(jìn)行文本轉(zhuǎn)換、處理的要求。Unicode 主要包括 utf-8utf-16 與 utf-32其中的 8、16 與 32 分別代表了字符的最小空間為 1、2 以及 4 字節(jié)。由于 utf-8 支持 ASCII且字符最小單位為一字節(jié)節(jié)省了空間所以現(xiàn)在使用率最高的編碼方式就是它。GBKGBK 即 “國標(biāo)”是我國專門為漢字設(shè)計的一套編碼其中一個字符最小占用空間為兩個字節(jié)。1.2 使用前的準(zhǔn)備用string類時代碼里必須加這兩句#includestring// 包含頭文件usingnamespacestd;// 引入std命名空間二、string構(gòu)造函數(shù)函數(shù)原型功能說明示例string()無參默認(rèn)構(gòu)造創(chuàng)建空字符串string s;string(const string str)拷貝構(gòu)造用已有string對象構(gòu)造新對象string s1(s0);string(const string str, size_t pos, size_t len npos)子串構(gòu)造從pos開始截取len個字符string s(s0, 2, 4);string(const char* s)C 風(fēng)格字符串構(gòu)造用char*初始化string s(hello);string(const char* s, size_t n)截取 C 串前n個字符string s(hello, 3);string(size_t n, char c)重復(fù)字符填充構(gòu)造string s(5, A);string s1;string tmphello;string s2tmp;// 拷貝構(gòu)造對象實例化時賦值strings3(tmp,2,2);// 從 tmp 的索引 2 開始取 2 個字符strings4(string);// C 風(fēng)格字符串構(gòu)造strings5(string,2);// 取前 2 個字符strings6(5,A);// 5 個 Acout無參默認(rèn)構(gòu)造s1 長度s1.size()endl;cout拷貝構(gòu)造s2endl;cout子串構(gòu)造s3endl;coutC 串構(gòu)造s4endl;cout截取 C 串s5endl;cout重復(fù)字符s6endl;2.1 參數(shù)詳解string(const char* s)用于通過 C 語言風(fēng)格的字符串構(gòu)造string對象string(const string str, size_t pos, size_t len npos)則是從已有string對象對應(yīng)的字符串中截取從pos下標(biāo)開始的len個字符來構(gòu)造新的string對象。參數(shù)規(guī)則若pos超過字符串的最大下標(biāo)會觸發(fā)斷言錯誤若原字符串str長度不足或len取值為string::npos則會從pos位置開始復(fù)制到字符串末尾。pos越界使用npos缺省值string對應(yīng)字符串后面有沒有\(zhòng)0string test1hello;stringtest2(test1,5);couttest2endl;運(yùn)行后控制臺無輸出且未觸發(fā)斷言警告。這個現(xiàn)象可以說明string內(nèi)部會在存儲的字符串末尾維護(hù)\0但由于\0是不可打印字符所以輸出時不會顯示而我們從原字符串的末尾位置下標(biāo) 5截取字符時剛好取到了這個\0因此控制臺沒有打印出可見內(nèi)容。string::npos定義為-1在賦值給size_t類型時觸發(fā)有符號int到無符號size_t的算術(shù)轉(zhuǎn)換-1的補(bǔ)碼全 1被解釋為size_t的最大值。若平臺是 32 位系統(tǒng)size_t是 32 位無符號整數(shù)此時npos的值為 232 - 1若平臺是 64 位系統(tǒng)size_t是 64 位無符號整數(shù)此時npos的值為 2?? - 1。我之所以能直接用cout打印string對象是因為標(biāo)準(zhǔn)庫中為string重載了配合cout和配合cin這兩個輸入輸出運(yùn)算符具體接口介紹后面講。2.2 單參數(shù)構(gòu)造的隱式類型轉(zhuǎn)換// 單參數(shù)構(gòu)造函數(shù)支持隱式類型轉(zhuǎn)換// 張三是 const char* 類型會先調(diào)用 string(const char* s) 構(gòu)造一個臨時 string 對象// 再通過拷貝構(gòu)造把這個臨時對象的內(nèi)容復(fù)制給 s。// 但實際編譯時編譯器會自動優(yōu)化這個過程直接用 張三 構(gòu)造 s只調(diào)用 1 次構(gòu)造函數(shù)// 省去了臨時對象和拷貝的開銷。string s張三;如果想禁止這種隱式轉(zhuǎn)換可以給單參數(shù)構(gòu)造函數(shù)加explicit關(guān)鍵字比如explicit string(const char* s)這樣就不能直接用string s 張三得顯式寫string s(張三)。三、string類對象的訪問及遍歷操作函數(shù)名稱函數(shù)功能對應(yīng) const 版本begin()返回指向字符串第一個字符的迭代器cbegin()end()返回指向最后一個字符的下一個位置的迭代器cend()rbegin()反向迭代器返回指向最后一個字符的迭代器crbegin()rend()反向迭代器返回指向第一個字符的前一個位置的迭代器crend()補(bǔ)充operator[]非 const 版本char operator[](size_t pos)—— 可讀寫const 版本const char operator[](size_t pos) const—— 僅讀3.1operator[]功能返回pos位置的字符這里先提前預(yù)告一下string里面有一個成員函數(shù)是size獲取有效字符的個數(shù)那么我們就這樣看這樣寫遍歷了。3.2begin end功能begin獲取第一個字符的迭代器 end獲取最后一個字符下一個位置的迭代器迭代器是 C 容器如string、vector、map的 “通用訪問工具”本質(zhì)是封裝了指針的對象作用是? 遍歷容器中的元素讀 / 寫? 屏蔽不同容器的底層實現(xiàn)差異比如數(shù)組、鏈表遍歷方式不同但迭代器用法統(tǒng)一。核心特點用法像指針支持*it取元素、it移動、it-取成員等操作分類適配場景比如begin()返回指向第一個元素的迭代器end()返回 “最后一個元素的下一個位置的迭代器”注意失效容器擴(kuò)容 / 縮容 / 刪除元素時迭代器可能失效指向無效內(nèi)存需重新獲取后面實現(xiàn)時講解。大家可以把string這個容器中迭代器就是指針begin返回的是指向第一個字符的指針end返回的是指向最后一個字符下一個位置的指針這是因為string容器存儲的是字符串可以直接通過指針對字符串進(jìn)行解引用操作等當(dāng)然并不是所有迭代器的底層都是用指針實現(xiàn)的遍歷3.3rbegin rend功能和begin/end的區(qū)別是rbegin/rend是反向遍歷rbegin指向的是最后一個數(shù)據(jù)的位置rend指向的是第一個元素3.4 const 迭代器這個和上述介紹的迭代器沒本質(zhì)區(qū)別不同的就是const迭代器指向的對象不能被修改四、string類對象的容量操作4.1 size功能返回字符串有效大小不包含末尾的\04.2 length功能返回字符串有效長度不包含末尾的\0注length和size的功能完全一致返回結(jié)果相同只是命名來源不同 ——length是早期為字符串設(shè)計的接口size是后續(xù)為了統(tǒng)一所有容器如vector的接口風(fēng)格新增的二者在string中是等價的。4.3 capacity功能返回空間總大小string的自動擴(kuò)容規(guī)則在插入數(shù)據(jù)且當(dāng)前空間不足時string會自動擴(kuò)容不同編譯器的擴(kuò)容策略不同VS 環(huán)境下第一次擴(kuò)容為當(dāng)前容量的 2 倍后續(xù)擴(kuò)容為當(dāng)前容量的 1.5 倍g 環(huán)境下每次擴(kuò)容均為當(dāng)前容量的 2 倍。4.4 empty功能檢測字符串是否為空串是返回true否則返回false4.5 clear功能清空有效字符4.6 reserve重要功能提前給字符串分配好指定大小的內(nèi)存空間不會改變size【場景1】reserve要求 100 個字節(jié)空間但卻開辟了 111 個字節(jié)空間為啥呢這是 VS 編譯器的內(nèi)存對齊 / 容量對齊策略導(dǎo)致的因為string是一個類就會有內(nèi)存對齊。reserve(100)只是要求 “至少 100 字節(jié)”VS 會在滿足需求的基礎(chǔ)上額外多分配一些空間比如按特定規(guī)則對齊所以實際開辟了 111 字節(jié)?!緢鼍?】reserve請求空間小于當(dāng)前容量時的容量變化縮容當(dāng)reserve(n)中n小于當(dāng)前字符串容量時標(biāo)準(zhǔn)將 “縮小容量” 定義為非約束請求 —— 編譯器 / 容器實現(xiàn)可以自主決定是否縮容但通常會選擇 “不縮容”比如本示例中當(dāng)前容量 15請求的 10容量保持 15 不變最終保證容量不小于n即可。reserve在新版 VS 里的行為? 當(dāng) n 當(dāng)前容量正常擴(kuò)容滿足容量≥n? 當(dāng) n 當(dāng)前容量完全不縮容容量保持原樣僅保證不低于 n但不會主動減小。4.7 resize重要功能將有效字符的個數(shù)改成n個多出的空間用 “字符 c” 或者\(yùn)0填充【場景1】n size字符串會被擴(kuò)展到n個字符新增的位置默認(rèn)填充\0空字符如果顯式傳第二個參數(shù)比如resize(n, a)則用指定字符填充?!緢鼍?】n size字符串會被截斷只保留前n個字符超出部分直接丟棄不可逆。ps以上內(nèi)容我是一個一個講的并加入實例下面開始我會把他們做成一個表格來講重要的我會把他單獨拎出來講五、string類對象的修改操作接口功能簡述備注push_back(char c)在字符串尾部插入一個字符僅支持單字符append(const string str)在字符串尾部追加一個字符串支持string/ C 串operator(const string str)在字符串尾部追加字符串最常用支持單字符、C 串、string等重載c_str()返回const char*形式的 C 風(fēng)格字符串末尾含\0方便與 C 接口交互find(const string str, size_t pos 0)從pos開始向后查找目標(biāo)首次出現(xiàn)位置找不到返回string::nposrfind(const string str, size_t pos npos)從pos開始向前查找目標(biāo)最后一次出現(xiàn)位置找不到返回string::npossubstr(size_t pos 0, size_t n npos)從pos開始截取n個字符并返回新string越界自動截斷不拋異常string s1;s1.push_back(s);couts1endl;s1.append(tring:);couts1endl;s1 hello world;couts1endl;couts1.c_str()endl;couts1.find(hello)endl;couts1.find(ljm)endl;couts1.rfind(hello)endl;couts1.rfind(ljm)endl;string tmps1.substr(8,999);couttmpendl;5.1operator【功能】在字符串后追加字符串str5.2 c_str功能返回 C 格式的字符串驗證string s1;s1 hello world;s1[5]\0;couts1endl;couts1.c_str()endl;5.3 find 和 rfind功能從字符串pos位置開始往后找字符c返回該字符在字符串中的位置功能從指定位置開始從后往前搜索目標(biāo)內(nèi)容在字符串中最后一次出現(xiàn)的位置5.4 substr功能在str中從pos位置開始截取n個字符然后將其返回5.5 insert功能任意位置插入內(nèi)容注意對于非法插入操作程序一般會拋出異常來提示錯誤。不過我現(xiàn)在還沒學(xué)到異常處理的相關(guān)內(nèi)容所以這里就不寫異常捕獲的代碼了之前用到的幾個插入接口遇到非法插入時也會以拋異常的方式報錯。5.6 erase功能刪除字符串中任意位置的內(nèi)容這個接口用起來確實挺靈活但得謹(jǐn)慎使用 —— 它底層的效率比較低會涉及大量的數(shù)據(jù)移動操作。5.7 問題findsubstr小案例分割網(wǎng)址前置知識//https://blog.csdn.net/HSWLJM?typeblogstring urlhttps://blog.csdn.net/HSWLJM?typeblog;size_t pos1url.find(:);cout協(xié)議url.substr(0,pos1)endl;size_t pos2url.find(/,pos13);cout域名url.substr(pos13,pos2-pos1-3)endl;cout資源名url.substr(pos21)endl;小案例所有空格替換成 %20//https://mp.csdn.%20%20%20%20%20%20%20net/string shttps://mp.csdn. net/;string ret;for(autoc:s){if(c! ){retc;}else{ret%20;}}coutretendl;六、string類非成員函數(shù)6.1operator功能拼接字符串不會修改原字符串盡量少用因為傳值返回導(dǎo)致深拷貝效率低6.2operator和operatoroperator功能流提取運(yùn)算符重載operator功能流插入運(yùn)算符重載6.3 getline功能獲取一行字符串和不同的是它僅僅以換行為結(jié)束標(biāo)識如果想讀取包含空格的整行字符串得用getline(cin, s)6.4 relational operators功能大小比較比的是 ASCII 編碼