????????Hello大家好!很高興我們又見面啦!給生活添點passion,開始今天的編程之路!
我的博客:<但凡.
我的專欄:《編程之路》、《數據結構與算法之美》、《題海拾貝》、《C++修煉之路》
歡迎點贊,關注!
1、string類初識
?????????<string>
?是 C++ 標準庫中用于處理字符串的頭文件。我們可以用string提供的豐富的接口來實現各種各樣的字符串操作。string類的底層其實就是順序表,只不過在存儲字符的同時還多存儲了一個‘\0’。再后續的使用講解中大家會感受到string的強大。
? ? ? ? 使用string類需要包含頭文件:
#include<iostream>
#include<string>
? ? ? ? ?好的現在我盡量用最直白的語言告訴大家為什么這個string類非常方便。
#include<iostream>
#include<string>
using namespace std;
int main()
{string s="Hello World!";cout << s;
}
?
? ? ? ? 我們創建了一個helloworld字符串,并且把他打印了出來。現在的字符串就像int, char一樣是一個類型,我們不需要再去像C語言一樣再去創建字符數組啊什么什么的。但是不僅如此,string類提供了非常多的接口,所以我們可以隨便的在這個字符串中增刪查改。具體怎么增刪查改,我們繼續往下看。
2、迭代器
? ? ? ? 我們可以把迭代器理解成和指針差不多的東西。因為我們想訪問它都得解引用。
? ? ? ? 對于迭代器類型的接收我們可以使用auto,auto可以自動識別變量的類型。但需要注意的是auto這個東西不推薦多用,因為很可能會識別錯誤。
auto不能作為函數的參數,可以做返回值,但是建議謹慎使用 。auto不能直接用來聲明數組
?
? ? ? ? 這些都是string類中返回迭代器的接口,我們一個一個來介紹:
2.1、begin和end
? ? ? ? ?begin是返回該字符串中第一個字符位置的迭代器,end是返回該字符串最后一個字符位置的下一位的迭代器(也就是‘\0’的位置)。我們可以使用begin和end對字符串進行遍歷:
#include<iostream>
#include<string>
using namespace std;
int main()
{string s="Hello World!";auto it1 = s.begin();auto it2 = s.end()-1;while (it1 != it2){cout << *it1 << " ";it1++;}
}
輸出結果:
?
? ? ? ? ?注意我們這里沒讓他輸出感嘆號!
????????有幾個細節需要提一下:第一,我們的迭代器是支持+-操作的,+就是讓迭代器挪到下一個位置,-與+相反。
第二,我們迭代器的比較其實是非常嚴格的,所以盡量使用 != 和== 來比較。
?2.2、rbegin和rend
? ? ? ? 這個rbegin和rend就有意思了啊,它實際上就是和begin和end進行了一個對調,rbegin返回的是整個字符串最后一個字符的位置,而rend返回的是第一個字符的位置的前一個。但需要注意,++begin其實是往前走,向前遍歷。也就是說我們現在從字符串最后向前是正方向。
?
#include<iostream>
#include<string>
using namespace std;
int main()
{string s="Hello World!";auto it1 = s.rbegin();auto it2 = s.rend();while (it1 != it2){cout << *it1 << " ";it1++;}
}
?
? ? ? ? ?需要注意一點,我們不能執行rbegin-1的操作,因為rbegin-1這個位置雖然物理內存上是存在的,但是對于反向迭代器來說是不合法的,屬于越界訪問。
2.3、cbegin、cend、crbegin、crend
? ? ? ? 這四個迭代器其實和上面我們介紹過的四個指向的位置沒有區別,但是他們迭代器的類型變成了const_iterator。也就是說,我們迭代器指向的這一塊內存存放的內容是不可以被修改的。
#include<iostream>
#include<string>
using namespace std;
int main()
{string s = "Hello World!";auto it1 = s.begin();(*it1)++;auto it2 = s.cbegin();(*it2)++;//報錯:表達式必須是可修改的左值
}
? ? ? ? 實際上后面四個我們不常用。
????????如果我們在不使用auto的情況接收以下兩種情況的迭代器,必須使用const_iterator:
void test(const string& s)
{string::const_iterator it = s.begin();string p = "asd";string::const_iterator it2 = p.cbegin();
}
? ? ? ?注意以下兩串代碼表達的意思并不相同,第一個是指迭代器的指向不能改變,也就是不能執行it2++這樣的操作,而第二個是迭代器的指向的內容不能被修改。
const string::iterator it2 = p.begin();string::const_iterator it = p.begin();
?3、capacity
以下是我們這一部分要介紹的接口:
3.1、size、length和max_size
? ? ? ? 其實這兩個接口都是返回字符串的長度。size()是為了和后面STL里面其他容器的接口一致而后設計出來的。我們一般使用size,不使用length。
? ? ? ? max_length就是返回我們這個字符串能夠容納的最大長度。
#include<iostream>
#include<string>
using namespace std;int main()
{//兩種定義string的方式string s = "Hello World!";//隱式類型轉換string s1("abc");cout << s.size() << endl;//包含空格cout << s1.size()<< endl;cout << s1.length() << endl;cout << s.max_size() << endl;//輸出2147483647
}
輸出結果:
?3.2、resize
? ? ? ? resize可以將字符串大小調整為 n 個字符的長度。
? ? ? ? 如果n小于當前字符串的長度,就會對該字符串進行截取,如果n大于當前字符串的長度,多余的位置用我們指定的字符串進行補充。如果沒有指定就用\0補充。
#include<iostream>
#include<string>
using namespace std;int main()
{//兩種定義string的方式string s = "Hello World!";//隱式類型轉換s.resize(5);cout << s << endl;s.resize(50);cout << s << endl;s.resize(100, 'c');cout << s << endl;
}
輸出結果:
?
?3.3、capacity
? ? ? ? capacity可以返回當前為字符串分配的存儲空間的大小,以字節表示。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello World!";cout << s.capacity() <<endl;
}
輸出結果:
?
? ? ? ? 現在我們可以把capacity用來觀察編譯器對string容量的自動擴容。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello World!";for (int i = 1;i <= 10;i++){s += "ssssssssss";//每次尾插10個字符cout << s.capacity() << endl;}
}
輸出結果:
?
3.4、reserve
????????reserve根據計劃的大小更改調整字符串容量,長度最多為 n 個字符。如果我們給的容量小于當前容量,他并不會縮小容量。這意味著他不會改變當前字符串的內容。但我說的不會縮小是針對我是用的編譯器vs2022來說的,不同環境下可能有不同的結果,可能縮也可能不縮。
????????但需要注意的是,如果給的大小大于當前的容量,他也不會嚴格按照你給的容量進行擴容,由于底層內存對齊規則和編譯器的優化(這不在討論的范圍內),他只會大于或等于你要求他擴容到的大小。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello World!";cout << s.capacity() << endl;//15s.reserve(5);cout << s.capacity() << endl;//15s.reserve(20);cout << s.capacity() << endl;//31s.reserve(40);cout << s.capacity() << endl;//47
}
?輸出結果:
? ? ? ? 所以說這個東西大家盡量別用,很可怕,用戶對于容量的可控性很低。
3.5、clear和empty
? ? ? ? clear可以情況當前串,使它變成空串,而empty可以判斷當前串是不是空串。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello World!";cout << s.empty() << endl;//0s.clear();cout << s.empty() << endl;//1
}
輸出結果:
?
4、Element access
? ? ? ? 這里我們介紹幾種接口用來訪問元素。
4.1、方括號
? ? ? ? 首先是最常用的方括號訪問元素。這里的訪問我們就把字符串看成一個下標從0開始的字符數組就好。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello World!";cout << s[0] << endl;cout << s[12] << endl;//訪問'\0
}
輸出結果:?
4.2、at
? ? ? ? at也可以訪問特定下標的元素:
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello World!";cout << s[0] << endl;cout << s[12] << endl;//訪問'\0cout << s.at(0) << endl;cout << s.at(4) << endl;
}
輸出結果:
?
? ? ? ? 需要注意,方括號訪問越界是直接斷言報錯,而at訪問越界是拋異常(我們之前介紹過異常)。但是只有debug版本才會斷言報錯,realease版本斷言就被忽略了。
?4.3、back和front
? ? ? ? 這兩個C++11新增的接口就是返回字符串首尾元素,沒啥好說的、
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello World!";cout << s.front() << endl;cout<<s.back()<<endl;
}
輸出結果:
?
?5、Modifiers
? ? ? ? 這里我們介紹幾個修改我們字符串的接口。
?????????
5.1、+=,insert和erase
? ? ? ? 之所以先介紹這三個,是因為這三個使我們最常用的。
? ? ? ? 首先來說+=,這個+=就是運算符重載了,可以支持我們的字符串相加:
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello";s += " World!";cout<<s<<endl;
}
輸出結果:
?
? ? ? ? ?接下來再來看insert,他可以支持我們在字符串中任意位置插入數據:
????????我們可以看一下,他支持了這么多函數重載。 現在我們挑幾個來實驗一下:
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello";s.insert(0, 1, '*');//在下標為0,傳入1個字符,內容為*cout<<s<<endl;s.insert(s.begin(), '&');//在begin迭代器指向的位置插入&cout << s << endl;s.insert(5, "pppppp");//從下標為5的位置插入字符串//注意下標為5的位置被改變了cout << s << endl;
}
輸出結果:
?
?????????erase支持我們在指定的位置進行刪除。
?使用案例:
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello";s.erase(s.begin());//刪掉迭代器指向位置的cout << s << endl;s.erase(0, 2);//從下標0開始,刪掉兩個字符cout << s << endl;s.erase(0, string::npos);//從下標0開始,刪掉npos個字符,實際上就刪完了,我們后面會介紹nposcout << s << endl;s += "yyy";s.erase(s.begin(), s.end());//刪除所有字符cout << s << endl;
}
輸出結果:
?5.2、append
? ? ? ? append可以在字符串末尾追加字符串。他既可以追加string字符串,也可以追加c風格字符串還可以追加多個字符,甚至還支持追加迭代器范圍內的字符。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello";s.append("ssss");//追加c風格string s1 = "***";s.append(s1);s.append(10, '@');//追加多個字符s.append(s.begin(), s.end());//相當于把這個字符串復制一遍加上去cout << s << endl;
}
輸出結果:
5.3、push_back
????????這個也是尾插,沒啥好說的。 但是這個只能插入單個字符。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello";s.push_back('H');cout << s << endl;
}
?5.4、assign
? ? ? ? assign可以以多種方式替換字符串的內容。
?使用案例:
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello";string s1("ppp");s.assign(s1);//另一串賦值cout << s << endl;s.assign("sssss");//c風格字符串cout << s << endl;s.assign(10,'o');//多個字符cout << s << endl;s.assign("*********", 2, 4);//從給定的string串下標2開始以后的4個字符進行替換cout << s << endl;string s2 = "666";s.assign(s1.begin(), s1.end());//迭代器cout << s << endl;
}
輸出結果:
?
5.5、replace
? ? ? ? 這個replace也是替換,但是他更靈活,我們可以自己指定需要替換的范圍。在這我就簡單列舉幾個例子:
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello";string s1("ppp");s.replace(2,3,s1);//從2開始替換3個字符為s1cout << s << endl;s.replace(2, 3,"sssss");//c風格字符串cout << s << endl;s.replace(2,10,5,'o');//從2開始以后10個字符替換為5個'o’cout << s << endl;
}
輸出結果:
? ? ? ? ?replace要謹慎使用因為如果替換的長度不對等他需要自己挪動數據,消耗時間。
?5.6、swap
? ? ? ? 這個就是單純的替換,沒啥好說的。但需要注意的是這個交換是string特有的只能交換字符串的淺拷貝,效率比算法庫自帶的swap更高。下一篇我們還會展開說這個地方。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello";string s1("ppp");s.swap(s1);cout << s << endl;
}
?
5.7、pop_back
? ? ? ? pop_back可以刪除最后一個字符?(注意不是‘\0’)。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello";s.pop_back();cout << s << endl;
}
輸出結果:?
????????好了這部分也算是講完了。大家應該可以感受得到,實際上string類在這地方設計的還是比較冗余的,有很多功能都是重復的。?
6、string operations
?6.1、c_str、data和copy
? ? ? ? 這個主要是讓我們的string類型字符串以c風格返回。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello";cout << s.c_str() << endl;
}
輸出結果:
? ? ? ? data的作用和c_str類似,但是在不同的標準中有一些區別。這個不過多說了因為不常用。
? ? ? ? copy可以將string串中的字符拷貝到字符數組中,但是必須手動添加終止符:
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello";char s1[100] = " ";s.copy(s1,s.size());s1[5] = '\0';cout << s1 << endl;
}
?輸出結果:
?6.2、find和rfind
? ? ? ? 這個是比較常用的,他可以支持我們以多種方式查找string串中出現的字符或字符串。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello***&&&@@";cout << s.find('*', 0) << endl;//5cout << s.find("*&", 0) << endl;//支持C風格字符串查找cout << s.find("9", 0) << endl;//未找到返回nposcout << s.find("*&#",0,2) << endl;//指定要查找我給定的C風格字符串的前兩個字符
}
輸出結果:
? ? ? ? 對于find來說,只要沒找到,就返回npos,也就是字符串能容納下的最大長度。
? ? ? ? rfind就是倒著往前找,但需要注意的是如果我們想讓他查找完全整個串的話我給給定的應該是從最后一個字符得下標開始查找:
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello***&&&@@";cout << s.rfind('*', 12) << endl;cout << s.rfind("*&", 12) << endl;//支持C風格字符串查找cout << s.rfind("9", 12) << endl;//未找到返回nposcout << s.rfind("*&#",12,2) << endl;//指定要查找我給定的C風格字符串的前兩個字符
}
?輸出結果:
?6.3、find_first_of和find_last_of
? ? ? ? find_first_of可以查找整個串中從前往后第一次出現這個字符或者給定的字符串中任意一個字符的位置。他經常用于檢驗這個字符串中是否包含某個特定字符。他和find的區別就是,就算給定的串與string串不完全匹配也沒關系,只要包含其中的一個字符就好。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello***&&&@@";cout << s.find_first_of('*', 0) << endl;cout << s.find_first_of("*&", 0) << endl;//支持C風格字符串查找cout << s.find_first_of("9", 0) << endl;//未找到返回nposcout << s.find_first_of("*&#", 0,2) << endl;//指定要查找我給定的C風格字符串的前兩個字符
}
?輸出結果:
?
? ? ? ? find_last_of就是和find_first_of反過來到這往前找而已,沒啥好說的。
?6.4、find_first_not_of和find_last_not_of
? ? ? ? 這兩個其實就是字面意思,查找第一個不是該字符或字符串的位置。一個是從前往后找,一個是從后往前找。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello***&&&@@";cout << s.find_first_not_of('H', 0) << endl;//cout << s.find_first_not_of("*&", 0) << endl;//支持C風格字符串查找cout << s.find_first_not_of("9", 0) << endl;//未找到返回nposcout << s.find_first_not_of("*&#", 0,2) << endl;//指定要查找我給定的C風格字符串的前兩個字符
}
?輸出結果:
?6.5、substr
? ? ? ? substr可以截取規定區域的字符串。但需要注意的是substr并不會改變原字符串。
#include<iostream>
#include<string>
using namespace std;int main()
{string s = "Hello***&&&@@";cout << s.substr(0,5) << endl;
}
輸出結果:
?6.6、compare
? ? ? ? compare就是字符串比較,和C語言里面的strcmp差不多的道理,都是字典序進行排序。
#include<iostream>
#include<string>
using namespace std;int main()
{//大寫字母ASCII碼比小寫字母小//按照字典序排序string s = "Hello";cout << s.compare("Assaa") << endl;string s1 = "Hello";cout << s.compare(s1) << endl;cout << s.compare(0, 3, s1) << endl;//s的0到3個字符和s1作比較cout << s.compare(0, 3, s1, 0, 3) << endl;//s的0到3個字符和s1的0到3個字符作比較}
輸出結果:
? ? ? ? 返回1表示s大于我們傳入的字符串,0表示相等,-1表示s小于我們傳入的字符串。
?7、getline
????????getline就是讀取當前這一行。什么意思?我們的scanf和cin都不能讀取包含有空格的字符串,所以我們得用getline讀取這一行能夠讀取空格。
#include<iostream>
#include<string>
using namespace std;int main()
{//輸入hello worldstring s;cin >> s;//hellocin.ignore(256, '\n');//刷新緩沖區,把剩下的world清掉string s2;getline(cin,s2);cout << s << endl;cout << s2 << endl;
}
?輸出結果:
? ? ? ? 好了,今天的內容就分享到這,我們下期再見!
?