歡迎來到CILMY23的博客
🏆本篇主題為:深入探索C++之std::string:不止于字符串
🏆個人主頁:CILMY23-CSDN博客
🏆系列專欄:Python | C++ | C語言 | 數據結構與算法 | 貪心算法 | Linux
🏆感謝觀看,支持的可以給個一鍵三連,點贊關注+收藏。
?寫在前頭:?
了解完模板和STL后,我們要開始研究std命名空間中的string,作為我們STL學習的開端,string對我們重新認識C語言中的字符串有很大意義。在上文中我們說過容器(Containers),容器是用來存儲數據的對象,如數組、鏈表、樹結構等。STL提供了多種類型的容器,本期我們將了解string類,雖然string并沒有被歸為容器,但它也和容器類似。
string?
一、string的前瞻
1.1 C語言中的字符串
C語言中,字符串是以'\0'結尾的一些字符的集合,為了操作方便,C標準庫中提供了一些str系列的庫函數, 但是這些庫函數與字符串是分離開的,不太符合OOP(面向對象編程)的思想,而且底層空間需要用戶自己管理,稍不留神可能還會越界訪問。
?我寫C語言字符串系列篇的時候還是深有體會的,稍不留神就越界訪問導致出錯,有時候還難排查出來。
1.2 string的基本概念
string 類是 C++ 標準庫中的一部分,它提供了對字符序列的封裝,使得字符序列的操作變得既簡單又安全。
????文檔閱讀?
我們來看看文檔是怎么說的:文檔的閱讀網站我放在了文章末尾,在這里我分了兩部分的文檔基本閱讀
🍀🍀圖一:?
?🍀🍀圖二:
????信息翻譯
- 字符串是表示字符序列的類
- 標準的字符串類提供了對此類對象的支持,其接口類似于標準字符容器的接口,但添加了專門用于操作 單字節字符字符串的設計特性。
- string類是使用char(即作為它的字符類型,使用它的默認char_traits和分配器類型(關于模板的更多信 息,請參閱basic_string)。
- string類是basic_string模板類的一個實例,它使用char來實例化basic_string模板類,并用char_traits 和allocator作為basic_string的默認參數(根于更多的模板信息請參考basic_string)。
- 注意,這個類獨立于所使用的編碼來處理字節:如果用來處理多字節或變長字符(如UTF-8)的序列,這個類的所有成員(如長度或大小)以及它的迭代器,將仍然按照字節(而不是實際編碼的字符)來操作。?
????總結
1. string是表示字符串的字符串類
2. 該類的接口與常規容器的接口基本相同,再添加了一些專門用來操作string的常規操作。3. string在底層實際是:basic_string模板類的別名,typedef basic_string<char, char_traits, allocator> string;
4. 不能操作多字節或者變長字符的序列。
?? 在使用string類時,必須包含 #include 頭文件以及 using namespace std;
二、string類的常用接口
?💫String 類的默認成員函數
string的默認成員函數我們在這里主要看以下三部分:?
🍃構造函數(?)
?進入構造函數的文檔界面,我們看到有七個構造函數,其中重點掌握的,也是最常用的,我在圖片中已經標出來了。剩下的了解就差不多了。
在這里我們要了解第三個,前面兩個很容易理解,但是第三個是什么呢?npos又是什么呢?
我們來看聲明和定義,文檔在底下給了我們的定義?
string (const string& str, size_t pos, size_t len = npos)?
?解析我在圖片中標出來了,大家可以研究研究。
?substring constructor是指字串構造。
現在主要的問題是 npos 是個什么東西??文檔給我們提供了鏈接,我們可以點擊查看(鏈接)
?
這里一看我們就知道了,原來 npos 是 size_t (無符號整數)的最大值,這就涉及原碼反碼和補碼了(不懂的小伙伴可以看看鏈接)。?
-1 的補碼是32個1,也就是2的32次方-1,但是string構造函數并不會開到這么多空間,所以就只會判定拷貝直到str的末尾。
🍭🍭實際操作?
寫了這么多,我們來實際操作一下。在vs 2019上對這幾個構造函數進行使用:記得包含頭文件<string>
string s0;
string s1("CILMY23");
string s2(s1,2,3);
string s3(s2);//拷貝構造
string s4(s1, 3, string::npos);//從第三個位置開始,拷貝到str字符串末尾
string s5(10, '*');
string s6("CILMY23", 2);
結果如下:?
?
🍃析構函數
對析構函數我們不必關注太多,稍微看看就行
在學習的時候,我們知道析構函數的功能即可?
?🍃賦值運算符重載
在這里賦值運算符重載主要給了三種方式?
?這三種方式的實操如下:
🍭🍭實際操作?
string s7;s7 = s2; //將s2拷貝給s7
s7 = "CILNY23";//將常量字符串賦值給s7
s7 = '*';//將單字符*賦值給s7
?結果如下:
?賦值運算符重載用的最多的還是前兩種,一般第三種還是挺少用的。
?💫String 類的容量操作
string 的容量操作一共有以下操作:
?我們重點學習:size,empty,clear,reserve,resize
?🎈size(?),length,capacity,max_size
在string類中,size和length是一樣的,都是返回字符串的長度。,它們表示的是字符串中,有效字符的個數,而capacity表示的是容量,max_size,它返回 string 類型對象最多包含的字符數。也就是string類型支持的最大字符數,超過這個數,將無法支持,編譯器會拋出 length_error 異常。
?我們知道在字符串的末尾是有\0的,在這里是不計入size和length的。
為什么這里會相同呢?
?這就不得不提及STL和String的先后問題了,其實是String比STL先出現的,所以String在剛剛的網站中我們是在Container中是找不到String的,但是它跟容器差不多,所以就增加了一個size接口,起初最先我們使用的是length,size()與length()方法底層實現原理完全相同,引入size()的原因是為了與其他容器的接口保持一致,一般情況下基本都是用size()。?
說完了size和length,我們接下來看看capacity
?
capacity返回的是開辟空間的大小,它是一個const成員函數,this指針指向的內容是不可以改變的 ,它和size是不一樣的,有可能等于size,也可能比size還多
我們可以看一下在vs上它是如何擴容的,?
size_t sz = s1.capacity();
int n;
for (n = 0; n < 1000; n++)
{s1.push_back('*');if (sz != s1.capacity()){sz = s1.capacity();cout << "capacity changed->" << s1.capacity() << endl;}}
結果如下:
我們可以看到它的擴容機制大概是按1.5倍來擴容的?
而g++底下的擴容機制是按照兩倍擴容的。?
所以說具體的這些細節,是不確定的。?
?🍭🍭實際操作?
string s1("hello CILMY23");cout << s1.size() << endl;
cout << s1.length() << endl;
cout << s1.capacity() << endl;
cout << s1.max_size() << endl;
cout << s1.npos << endl;
?結果如下:
但是我這里max_size可能是受到了系統或者編譯器的限制,在我查閱很多文檔后,都說它比npos還小1個,這里得考慮字符'\0'。所以最大長度是不確定的,一般給的是當前平臺最大值。
?🎈empty
?文檔介紹:這是一個負責測試string是否為空表,如果是空就返回true,否則返回false
這個函數較為簡單就不進行實操演示了?
?🎈clear(?)
clear函數主要是刪除這個string中包含的字符,讓它變成一個空表,我們知道空表的判定,是長度length為0,所以這里的clear是不會動capacity容量的,是只會清除有效字符。
?clear()
?被調用,string
?對象仍然存在,它的內存沒有被釋放,但其內容被清空了。
?🎈reserve(?)
它的作用是請求改變容器的容量。具體來說,它會預分配足夠多的內存空間以便存儲至少指定數量的元素,這個數量由函數的參數指定。這意味著使用?reserve
?可以減少由于添加新元素導致的多次內存重新分配操作,從而提高程序性能。
需要注意的是,reserve 函數只改變容器的容量(即可存儲的元素數量,而不必進行再分配),但不改變容器的大小(即實際存儲的元素數量)。也就是說,即使調用了 reserve,size() 和 length() 函數的返回值也不會因此改變;reserve 也不會影響容器中已有元素的內容。
??🍭🍭實際操作?
void Teststring2()
{string s;// 測試reserve是否會改變string中有效元素個數s.reserve(100);cout << s.size() << endl;cout << s.capacity() << endl;// 測試reserve參數小于string的底層空間大小時,是否會將空間縮小s.reserve(50);cout << s.size() << endl;cout << s.capacity() << endl;
}// 利用reserve提高插入數據的效率,避免增容帶來的開銷
?結果如下:
但是你開100的空間,編譯器不一定會只給你一百個空間,在g++當中,確實是開了一百空間,如果看會不會縮容,就是縮小空間的話,vs是不會縮小的。也就是reserve是比capacity大才會擴容。
?🎈resize(?)
reserve會比resize用的更加廣泛些,讓我們來看看resize吧
resize重載了兩個函數:?
resize(size_t n) 與 resize(size_t n, char c)都是將字符串中有效字符個數改變到n個,不同的是當字
符個數增多時;resize(n)用0來填充多出的元素空間,resize(size_t n, char c)用字符c來填充多出的
元素空間。
注意:resize在改變元素個數時,如果是將元素個數增多,可能會改變底層容量的大小,如果是將元素個數減少,底層空間總大小不變。?
?🍭🍭實際操作?
void Teststring1()
{string s("hello, world");cout << s.size() << endl;cout << s.length() << endl;cout << s.capacity() << endl;// 將s中有效字符個數增加到10個,多出位置用'a'進行填充// “aaaaaaaaaa”s.resize(10, 'a');cout << s.size() << endl;cout << s.capacity() << endl;// 將s中有效字符個數增加到15個,多出位置用缺省值'\0'進行填充// "aaaaaaaaaa\0\0\0\0\0"// 注意此時s中有效字符個數已經增加到15個s.resize(15);cout << s.size() << endl;cout << s.capacity() << endl;cout << s << endl;// 將s中有效字符個數縮小到5個s.resize(5);cout << s.size() << endl;cout << s.capacity() << endl;cout << s << endl;
}
?結果如下:
?🎈shrink_to_fit(?)?
它說的是減少字符串所占用的空間以匹配它的實際內容大小。會請求字符串對象減少其容量,以適應它的實際大小,釋放未使用的內存。
?🍭🍭實際操作??
假設你有一個字符串?s
,如果你在對它進行了多次修改之后,希望確保它不占用額外的空間,你可以調用shrink_to_fit
std::string s = "aaaaaa";// ... 對字符串s進行了一些操作,可能使其大小發生了變化s.shrink_to_fit(); // 釋放未使用的內存
💫string類對象的訪問及遍歷操作?
類對象的訪問和遍歷操作主要有以下兩個部分,重點部分是operator[],和迭代器(也就是begin,end)。
??類對象的訪問
🌠opertor[](?)?
運算符重載后的[],支持了兩個版本,它的作用是返回pos所在字符串中指向的位置,如果pos和字符串的長度相等,那它就會返回一個‘\0’。
這兩個版本主要的差別不是很大,前一個的權限是可讀可寫,后一個的權限是只讀,它們構成了函數重載,是因為this指針被const修飾了。
??🍭🍭實際操作??
我們進入實際操作更能理解重載后的[],?它采用的是 【下標】的形式
void Teststring3()
{string s1("hello cilmy");const string s2("Hello cilmy");cout << s1 << " " << s2 << endl;cout << s1[0] << " " << s2[0] << endl;s1[0] = 'H';cout << s1 << endl;// s2[0] = 'h'; 代碼編譯失敗,因為const類型對象不能修改
}
?我們使用它就像使用數組一樣方便快捷,并且可以特定位置返回修改對應位置的字符。
結果:
🌠at
?在平常使用中,我們更經常使用 [] ,但 at 的功能和它是類似的,同樣都是重載了兩個對應的函數,也是返回對應下標的字符,區別不同的點在于,at在訪問字符串時會執行范圍檢查。如果嘗試訪問的位置超出了字符串的當前長度,它會拋出一個異常,從而避免了潛在的未定義行為。
???🍭🍭實際操作??
at的實際操作如下所示: 采用 at(下標) 的形式
//at 和 [] 的區別
void test4()
{string str = "hello";try {// 安全的訪問方法,但可能拋出異常char ch1 = str.at(10);}catch (const std::out_of_range& e) {// 處理越界訪問cout << "越界訪問異常: " << e.what() << '\n';}// 不安全的訪問方法,但不會拋出異常,在越界時行為未定義char ch2 = str[10];
}
🌠back和front
back和front看起來是C++11才有的,目前大部分編譯器都支持這個版本,它實際上是為了規范才提供的,要跟其他容器保持一致,back和front的返回無非就是字符串的最后一個字符和第一個字符。在這里就不做展開講解了,看看文檔就好啦。
??string類的遍歷
?string類的遍歷支持三種方式,一種是迭代器,一種是重載后的[],一種是范圍for,但范圍for的本質還是迭代器實現的,而且迭代器的遍歷是主流的一種方式,更經常使用迭代器,此外我們還會涉及到逆置輸出字符串,那就讓我們進入實操看看吧。
?🍭🍭實際操作??
?🌠[](?)?
?假設我們有個字符串s,采用 size() + [] 的方式進行遍歷字符串s,但是注意,這個可不同于C語言中的?[]?,那樣是等價于 *()
string s("hello CILMY23");
//[]的遍歷
for (int i = 0; i < s.size(); i++)
{cout << s[i] << " ";cout << s.operator[](i) << " ";
}
cout << endl;//修改
for (int i = 0; i < s.size(); i++)
{s[i] += 1;cout << s[i] << " ";
}cout << endl;//如果你不想修改,那就用const版本const string s2("hello CILMY23");
for (int i = 0; i < s.size(); i++)
{//無法修改s2[i] += 1;cout << s2[i] << " ";
}cout << endl;}
?🌠迭代器(?)?
?迭代器它的行為有點類似指針
string str("hello CILMY23");
//第二個遍歷方式,迭代器+begin()+end()
string::iterator it = str.begin();while (it != str.end())
{cout << (*it) << " ";++it;
}cout << endl;
?在這里我們就要補充迭代器的begin位置和end位置了,begin是h,end是‘\0’
同樣它也能修改字符內容?
//修改字符
string::iterator it1 = str.begin();
while (it1 != str.end())
{*it1 -= 1;cout << (*it1) << " ";++it1;
}cout << endl;
?從使用的角度上,[]確實是方便,但是更通用的還是迭代器,迭代器是容器的核心訪問,它能訪問更多的情況。
?🌠?范圍for
?它的本質其實也是一個迭代器
string str1("hello CILMY23");for (auto e : str1)
{cout << e << " ";
}cout << endl;
?推薦在正向遍歷的時候使用,反正也是簡潔使用。
💫string類對象的修改操作?
類對象的修改操作一共有以下這么多:
1?? push_back
push_back? 的功能是在尾部插入一個字符。但是要注意的是,它在設計上,是真的只能插入一個字符
我們來看實際操作:
string s("hello");s.push_back('C');
s.push_back('I');
s.push_back('L');
s.push_back('M');
s.push_back('Y');cout << s << endl;
?2?? append
我們可以看到append一共重載了六個,?append
?是?string
?的一個成員函數,用于向字符串的末尾添加內容。
我們來看實際操作下的append
void test7()
{// 創建一個string對象s,并初始化為"hello"string s("hello");// 向s追加字符串"CILMY23",s變為"helloCILMY23"s.append("CILMY23");cout << s << endl; // 輸出:helloCILMY23// 向s追加了10個'*'字符,s變為"helloCILMY23**********"s.append(10,'*');cout << s << endl; // 輸出:helloCILMY23**********// 創建另一個空的string對象s1string s1;// 向s1追加字符串"xx hello CILMY23 xx",s1變為"xx hello CILMY23 xx"s1.append("xx hello CILMY23 xx");// std::string的begin和end函數返回指向字符串第一個字符和尾后字符的迭代器,respectively// 這里,通過迭代器向s追加s1的部分內容:"x hello CILMY23 x"// ++s1.begin() 移動到s1的第二個字符,--s1.end()移動到s1的最后一個有效字符s.append(++s1.begin(), --s1.end());// 輸出s此時的內容:helloCILMY23**********x hello CILMY23 xcout << s << endl;// 注意:第三次追加實際上省略了s1字符串的首尾兩個'x'
}
?當然我們在實際操作中,更喜歡用 重載后的+=
3?? operator+=(?)
根據以下的文檔說明,我們可以直接在字符串末尾添加一個字符串,或者單個字符,又或者是另一個字符對象。?
??🍭🍭實際操作??
void test8()
{string s("hello");string s1("xxxxxx");s += ' ';s += "CILMY23";s += s1;cout << s << endl;
}
?+=使用起來簡單方便,清楚明白,在使用中,我個人還是更偏向使用+=的。
4???insert
insert這個函數,重載形式也蠻多的,在C++中,std::string 類同樣提供了 insert 函數來在字符串中的指定位置插入內容。與 append 相比,insert 提供了更高的靈活性,它允許你在字符串的任何位置添加字符或字符串。
? ?????具體介紹
insert(size_type pos, const std::string& str):
//在當前字符串的 pos 位置插入字符串 str。insert(size_type pos, const std::string& str, size_type subpos, size_type sublen):
//將字符串 str 從索引 subpos 開始的 sublen 長度的子字符串插入到當前字符串的 pos 位置。insert(size_type pos, const char* s):
//將字符串 s 插入到當前字符串的 pos 位置。insert(size_type pos, const char* s, size_type n):
//將字符串 s 的前 n 個字符插入到當前字符串的 pos 位置。insert(size_type pos, size_type n, char c):
//在當前字符串的 pos 位置插入 n 次字符 c。insert(iterator p, char c):
//在迭代器 p 指定的位置插入一個字符 c。insert(iterator p, size_type n, char c):
//在迭代器 p 指定的位置插入 n 次字符 c。insert(iterator p, InputIterator first, InputIterator last):
//在迭代器 p 指定的位置插入另一個字符串或字符數組,該字符串由 first 到 last 指定的范圍決定。
?這個主要作為了解即可,不需要過于在意去記憶。insert最經常用的還是insert(size_type pos, const char* s),切記不要多用,因為它的底層是挪動數據,這樣會造成代碼效率低下。
5?? assign
?assign:v. 分派,布置(工作、任務);分配(某物);指派,派遣;確定(價值、功能、時間、地點);轉讓(財產、權利)
assign的意思如上,在C++標準庫中,std::string 的 assign 函數用于給字符串賦新值。這個函數替換字符串的當前內容,可以從多種不同類型的源賦值,例如從另一個 string、從字符串,或是直接從字符數組來賦值。
?? ?????具體介紹
assign(const std::string& str):
//用字符串?str?的內容替換當前字符串的內容。assign(const std::string& str, size_type subpos, size_type sublen):
//用?str?的一個子串替換當前字符串的內容,這個子串從?subpos?開始,長度為?sublen。assign(const char* s):
//用字符串?s?的內容替換當前字符串的內容。assign(const char* s, size_type n):
//用字符串?s?的前?n?個字符替換當前字符串的內容。assign(size_type n, char c):
//用?n?個重復的字符?c?替換當前字符串的內容。assign(InputIterator first, InputIterator last):
//用兩個迭代器?first?和?last?指定范圍內的字符替換當前字符串的內容。
?大概就介紹到這里,erase 用的還是比較多的。
6?? erase?(?)
?erase還是挺好用的,erase 函數用來刪除字符串中的一部分內容,是一個非常有用的成員函數,允許多種不同的用法以適應不同的需求。
?????具體操作
string str = "Hello, World!";// 用法1: 刪除從位置3開始的5個字符
str.erase(3, 5);
cout << str << '\n'; // 輸出: Hel, World!// 重置字符串
str = "Hello, World!";// 用法2: 刪除位置為0的字符
auto it = str.erase(str.begin());
cout << str << '\n'; // 輸出: ello, World!
cout << *it << '\n'; // 輸出e,即下一個字符// 重置字符串
str = "Hello, World!";// 用法3: 刪除第3個字符到第6個字符之間的所有字符,包括第3個,不包括第6個
str.erase(str.begin() + 2, str.begin() + 6);
cout << str << '\n'; // 輸出: He World!
當然它的底層也是通過挪動數據實現的,能不用盡量不用。
7??replace
?在C++中,string 的 replace 成員函數允許我們替換字符串中的某部分內容。這個函數非常靈活,提供了多種重載版本。
??????具體操作
string str = "I like C++ programming.";// 示例 1: 使用另一個字符串替換
str.replace(7, 3, "Python");
cout << str << '\n'; // 輸出: I like Python programming.// 示例 2: 使用迭代器范圍和字符串替換
str.replace(str.begin(), str.begin() + 6, "He loves");
cout << str << '\n'; // 輸出: He loves Python programming.// 示例 3: 使用C風格字符串替換部分內容
str.replace(8, 6, "Java", 4);
cout << str << '\n'; // 輸出: He loves Java programming.// 示例 4: 用字符替換一部分內容
str.replace(str.begin(), str.begin() + 8, 4, 'X');
cout << str << '\n'; // 輸出: XXXX Java programming.
?這里就不多做演示了,感興趣的讀者可以自己實驗一下
💫string類對象的字符串操作??
💧find(?)
?如果我們想覆蓋字符串,刪除,替換,需要找到一個位置的時候,find()就顯得極其重要。find 函數用于在字符串內查找子串或字符的第一個出現位置。如果找到了匹配項,它就返回匹配項的下標;如果沒有找到,它則返回 npos,這是一個特別定義的常量,表示不存在的位置。
find(const std::string& str, size_type pos = 0) const:
從位置?pos?開始搜索字符串?str。
find(const char* s, size_type pos = 0) const:
從位置?pos?開始搜索以空字符結尾的字符串?s。
find(const char* s, size_type pos, size_type n) const:
從位置?pos?開始,搜索字符串?s?的前?n?個字符。
find(char c, size_type pos = 0) const:
從位置?pos?開始,搜索字符?c。
???????具體操作
string str = "We are looking for the term in this string.";// 查找子串
size_t found = str.find("term");
if (found != std::string::npos)cout << "Found 'term' at index: " << found << '\n';// 查找C風格的字符串
found = str.find("the term");
if (found != std::string::npos)cout << "Found 'the term' at index: " << found << '\n';// 查找單個字符
found = str.find('t');
if (found != std::string::npos)cout << "Found 't' at index: " << found << '\n';// 從某一位置后開始查找
found = str.find('i', 10);
if (found != std::string::npos)cout << "Found 'i' after index 10 at index: " << found << '\n';// 查找不存在的字符串
found = str.find("nonexistent");
if (found == std::string::npos)cout << "Did not find 'nonexistent'.\n";
💧rfind?
在C++中,string 類提供了 rfind 函數用于從字符串的末尾開始向前搜索子串或者字符的最后一次出現的位置。如果找到了匹配項,它返回該匹配項的起始下標,如果沒有找到,它返回npos。
rfind(const std::string& str, size_type pos = npos) const:
從位置 pos 開始向前搜索字符串 str 的最后一次出現。
rfind(const char* s, size_type pos = npos) const:
從位置 pos 開始向前搜索字符串 s 的最后一次出現。
rfind(const char* s, size_type pos, size_type n) const:
從位置 pos 開始向前搜索字符串 s 的前 n 個字符的最后一次出現。
rfind(char c, size_type pos = npos) const:
從位置 pos 開始(默認為字符串的末尾)向前搜索字符 c 的最后一次出現。
????具體操作?
string str = "The rain in Spain falls mainly in the plain.";// 查找子串的最后一次出現
size_t found = str.rfind("in");
if (found != std::string::npos)cout << "Last occurrence of 'in' found at index: " << found << '\n';// 查找字符的最后一次出現
found = str.rfind('a');
if (found != std::string::npos)cout << "Last occurrence of 'a' found at index: " << found << '\n';// 查找子串的最后一次出現,但不超過索引 10
found = str.rfind("in", 10);
if (found != std::string::npos)cout << "Last occurrence of 'in' before index 10 found at index: " << found << '\n';// 查找不存在的字符串
found = str.rfind("xyz");
if (found == std::string::npos)cout << "'xyz' not found.\n";
?rfind 用于找到特定的字符或者子串在字符串中最后一次出現的位置。如果需要從字符串的結尾向前查找,rfind 是個理想的選擇。如果想要找字符串中某個子串或字符的第一個出現,應當使用 find 函數。
💧c_str
c_str()?返回一個指向正規C字符串的指針,常量,即以空字符結束的字符數組。這個方法用于獲取一個C風格的字符串版本,通常是為了與需要傳統C字符串的C語言API兼容。
這是一個非常實用的功能,因為它允許 string?對象以C風格的字符串形式與那些早期設計的或者以純C編寫的接口(APIs)交互。例如,一些系統調用和庫函數要求傳入一個以?NULL?終止的字符數組(char*?或?const char*),這時你可以使用?c_str()?函數。
?
????具體操作
string str = "Example string";// 使用c_str()獲取C風格的字符串const char* cstyle_str = str.c_str();// 輸出C風格的字符串cout << cstyle_str << endl;
在上面的例子中,c_str()?返回了一個指向以?NULL?結尾的字符串?'Example string\0'?的指針。這個指針可以被傳遞給任何需要C風格字符串的函數。
需要注意的一點是,由?c_str()?返回的字符數組的生存期與它所屬的?string?對象相同,并且如果在?c_str()?調用后對原字符串對象進行了修改(除了添加字符到字符串的末尾以外),返回的字符指針可能就不再有效。因此,最好是在需要使用C風格字符串的時候才調用?c_str(),并且避免在之后修改字符串。
💧substr?(?)
substr 函數用于從字符串中提取一個子串。這個方法非常靈活,允許你指定開始位置和需要提取的子串的長度。如果不指定長度,則默認提取從開始位置到字符串末尾的所有字符。
?這里是?substr?函數的聲明:
std::string substr(size_type pos = 0, size_type len = npos) const;
- pos?參數指定了子串開始的位置,默認值為0。
- len?參數指定了子串的長度。默認值?npos?是一個特殊的值,表示直到字符串的末尾。
這個函數會返回一個新的 string?對象,包含從?pos?開始、長度為?len?的子串。如果?pos?是字符串的長度或更大,函數會拋出out_of_range?異常。如果?pos?加上?len?超出了字符串的末尾,那么只會提取到字符串的末尾為止的子串。
????具體操作?
string text = "Hello, World!";// 提取從位置6開始到末尾的所有字符
string sub1 = text.substr(6);
cout << "sub1: " << sub1 << endl; // 輸出 "World!"// 提取從位置0開始的5個字符
string sub2 = text.substr(0, 5);
cout << "sub2: " << sub2 << endl; // 輸出 "Hello"// 嘗試從超出字符串長度的位置開始提取子串
try {string sub3 = text.substr(50, 2);
}
catch (out_of_range& e) {cout << "Caught an out_of_range exception: " << e.what() << endl;// 捕獲異常
}
// 如果len參數超出字符串的末尾,則從pos到字符串末尾的所有字符都被提取
string sub4 = text.substr(7, 20);
cout << "sub4: " << sub4 << endl; // 輸出 "World!"}
?這里的捕獲異常大家先了解就可以了,具體的之后我們會詳細講解。
💫string類對象的非成員函數重載???
🌼swap(?)?
在這里我們重點關注swap函數就好了。
swap 成員函數用于將兩個字符串對象的內容進行交換。這個操作是高效的,通常只涉及指針和大小值的交換,而非真正的字符數據復制,因此即使字符串的內容很長,使用 swap 也不會導致大量的數據復制。
文檔如下:?
?????具體操作?
?
🌼operator+
operator+實現字符串的連接操作。這個操作符可以用來連接兩個 string 對象,或者一個 string 對象與一個字符串(const char* 類型),或者一個 string 對象與一個字符(char 類型)。operator+ 會創建一個新的 string 對象來存儲連接后的字符串結果。
文檔如下:
?????具體操作?
string str1 = "Hello, ";
string str2 = "World!";// 使用operator+連接兩個std::string對象
string result1 = str1 + str2;
cout << result1 << endl; // 輸出:Hello, World!// 使用operator+連接std::string對象和C風格字符串
const char* cstr = " Have a nice day!";
string result2 = result1 + cstr;
cout << result2 << endl; // 輸出:Hello, World! Have a nice day!// 使用operator+連接string對象和單個字符
char ch = '!';
string result3 = result1 + ch;
cout << result3 << endl; // 輸出:Hello, World!!
?operator+?對于?string?對象的操作通常生成一個新的字符串,包含操作符兩邊操作數連接后的結果。當使用?operator+?進行字符串連接時,如果連接涉及多個操作(例如,多個?+?操作符的串聯),每個連接操作都可能會生成一個臨時的字符串對象,這可以增加額外的內存分配和復制的開銷。在處理很多字符串連接操作的時候,使用成員函數?append?或?operator+=?可以提高效率。
🌼getline
getline 是一個標準庫函數,它從輸入流中讀取文字直到遇到換行符(默認是 '\n'),并將讀取的內容(不包括換行符)存入一個 string 對象。這個函數能夠處理不定長度的輸入,非常適合用于從 cin 或任何其他輸入流(如 ifstream 文件輸入流)讀取一行文本。
文檔如下:
getline?函數的聲明如下:
std::istream& getline(std::istream& is, std::string& str, char delim = '\n');
- is?是輸入流的引用。
- str?是將輸入內容存儲的?string?對象的引用。
- delim?是可選的分隔符,默認為換行符?'\n',當遇到此字符時,getline?停止讀取。如果提供此參數,getline?將會使用該字符作為行的終止符。
?????具體操作?
string line;cout << "Please enter your name: ";
getline(std::cin, line);cout << "Hello, " << line << "!" << endl;
?該程序提示用戶輸入名字,然后讀取一行輸入并存入?line?字符串,接著輸出名字。
如果輸入字符超過 string?的最大長度,getline?會導致 length_error?異常。但由于 string?可以處理非常大的字符串,所以在實際程序中發生這種情況的概率非常低。getline?函數是處理輸入中的字符串行非常有用的工具,它確保了即使輸入中含有空格也能夠正確得到整行的字符串。
🌼operator<< 和 operator>>
流提取 和 流插入,其實就是和我們使用cout << /(cin >>)
在做輸入操作/(輸出)的時候一樣,控制臺會先去等待我們輸入一個值/(通過去緩沖區中拿取數據,然后將其顯示在控制臺上)
它們支持直接進行對string對象的插入和提取。
三、string類的接口一覽圖?
?接口一覽:從文檔中整理了一份全部接口的思維導圖
文檔網站:
cplusplus.com/reference/string/string/?kw=stringhttps://cplusplus.com/reference/string/string/?kw=string
字符編碼:
【Python】python編程初探2---字符編碼,輸入和輸出,初識數據類型-CSDN博客https://blog.csdn.net/sobercq/article/details/137061735
文檔學習小結:
sequences?:n. 序列,順序;繼起的事,是sequence的復數形式
features : n.產品特點,特征;容貌;嘴臉(feature 的復數)
specifically :?adv. 特意,專門地;明確地,具體地;具體來說,確切地說;局限性地;專門;
instantiation : n. 實例化
handle :??v. 拿;處理,應付;操縱;觸(球);經營,管理
default :?adj. 默認的
variable :adj. 易變的,多變的;時好時壞的;可變的,可調節的;(數)(數字)變量的;(植,動)變異的,變型的;(齒輪)變速的
assignment :??n. 作業,任務;(工作等的)分配,指派;(財產、權利的)轉讓,在默認成員函數中,意指賦值。
substring?:n. 子串;子鏈
constructor :n. 構造函數;構造器;建造者
portion :n. (某物的)一部分;(尤指餐館中食物的)一份,一客;(責任、過失、職責等的)一份,一部分;<法律>(根據法律贈與或遺留給繼承人的)一份財產;<古> 命運,天數
spans :?v. 跨越;持續;貫穿(span 的第三人稱單數)
consecutive :?adj. 連續的,不間斷的
?assign:v. 分派,布置(工作、任務);分配(某物);指派,派遣;確定(價值、功能、時間、地點);轉讓(財產、權利)
🛎?感謝各位同伴的支持,本期C++就講解到這啦,如果你覺得寫的不錯的話,可以給個一鍵三連,點贊,關注+收藏,若有不足,歡迎各位在評論區討論。????