目錄
1 Modifiers部分
1.1 assign的使用
?1.2 insert的使用
1.3 erase的使用
1.4 replace的使用
2 capacity部分
2.1 max_size的使用
2.2 capacity的使用
2.3 reserve的使用
2.4 shrink_to_fit簡介
2.5 resize的使用
2.6 clear的使用
3 String operations部分
3.1 c_str的使用
3.2 copy的使用
3.3 find和rfind的使用
3.4 substr的使用
3.5 find_first_of(not)和find_last_of(not)的使用
3.6 compare的使用
4 Non-member function overloads部分
4.1 operator+的使用
4.2?relational operators
4.3 operator<< 和 operator>>的使用
4.4 getline的使用
5 補充函數
6 有關編碼
1 Modifiers部分
上文已經介紹了+=,append,push_back,pop_back,這里介紹assign,insert,erase,replace。
1.1 assign的使用
assign的使用類似于賦值,會完全覆蓋原來的字符串,進行賦值:
int main()
{string s1("Hello world");s1.assign("xx");cout << s1;return 0;
}
但是查看文檔就發現函數重載挺冗余的,實際上常用的也就是第三個了,類比的化,第二個也可以連蒙帶猜的試一下:
int main()
{string s1("Hello world");string s2("123456789");s1.assign(s2,2,5);cout << s1;return 0;
}
?1.2 insert的使用
insert在鏈表中使用過,當然是名字使用過,插入的意思,有意思的是string沒有直接的支持頭插,但是間接的支持了頭插,可以使用insert進行實現:
int main()
{string s1("Hello world");string s3 = "123456";s3.insert(0,s1);cout << s3 << endl;return 0;
}
第一個重載的使用如上,pos位置插入數據,打印出來就是Hello world123456,類似的是第三個:
int main()
{string s3 = "123456";s3.insert(0,"132");cout << s3 << endl;return 0;
}
當然還有迭代器版本的,前四個函數挺類似的,不作介紹,這里介紹一下迭代器版本的:
int main()
{string s1("123456");string s2("Hello world");s1.insert(s1.end(), s2.begin(), s2.end());cout << s1 << endl;return 0;
}
第一個參數是選擇插入的位置,后面是區間,但是注意的是這個區間是左閉右開的。
但是離譜的是insert不支持直接插入單個字符,要插入單個字符使用第四個函數重載:
int main()
{string s1("123456");s1.insert(0, 1, 'x');cout << s1 << endl;return 0;
}
有疑問了就,可以插入一個只有一個字符的字符串,但是這種情況只能這樣寫:
int main()
{string s1("123456");char ch = 'a';s1.insert(0, 1, ch);cout << s1 << endl;return 0;
}
但是不太推薦使用,因為它的時間復雜度是O(N),底層的實現原理是移動每個數據到相應位置,時間復雜度即一個循環,是O(N)。
1.3 erase的使用
有插入就會有刪除,erase即刪除:
參數問題現在已經不大了,使用如下:
int main()
{string s1("abcdefg");s1.erase();//清空字符串cout << s1 << endl;string s2("abcdefg");s2.erase(0);//清空字符串cout << s2 << endl;return 0;
}
int main()
{string s3("abcdefg");s3.erase(0, 3);//abc清空cout << s3 << endl;string s4("abcdefg");s4.erase(s4.begin(), s4.begin() + 4);//abcd清空cout << s4 << endl;return 0;
}
迭代器和npos的使用如上,但是仍然不推薦使用,因為和insert是一樣的,時間復雜度為O(N)。
因為缺省值是npos,所以刪除的長度可以離譜但是下標不能離譜:
int main()
{string s5("abcdefg");s5.erase(2,100);cout << s5 << endl;return 0;
}
1.4 replace的使用
replace的英文是代替,顧名思義咯~
多吧?第一個函數重載使用如下:
int main()
{string s1("123456");s1.replace(2,2,"a");cout << s1 << endl;return 0;
}
從3開始把兩個字節替換成a字符。
為什么說它坑呢?引入一道題,將一段字符串中的所有空格全部替換為x,用C語言就是:
int main()
{string s2("hello world hello Byte");for(int i = 0;i < s2.size();i++){if (s2[i] == ' '){s2[i] = 'x';}}cout << s2 << endl;return 0;
}
用C++的replace就是:
int main()
{string s3("hello world hello Byte");for (int i = 0; i < s2.size(); i++){if(s3[i] == ' '){s3.replace(i,1,"x");}}cout << s3 << endl;return 0;
}
看似沒有差別?因為這里的空格剛好是一個字符,如果是還要涉及數據移動的問題,效率自然就低下了,所以慎用。
當然方法很多,多多選擇。
2 capacity部分
上篇已經介紹了,size,length,本文介紹max_size,resize,capacity,reserve,clear,shrink_to_fit。
2.1 max_size的使用
這個函數實際上是一個冗余的設計,即一個字符串能開到多大:
int main()
{string s1("123456");cout << s1.size() << endl;cout << s1.max_size() << endl;return 0;
}
max_size()的結果是看64位還是32位情況,64位是:
32位是:
雖然結果不一樣,但是大小都是挺驚人的,因為它只有這一個功能,實際上也沒有哪個字符串會開這么大,就了解一下即可。
2.2 capacity的使用
由前面的學習可以得知capacity是容量的意思,所以當我們調試一個字符串的時候總會發現:
一個串里面有size,有capacity,有其他元素,那么空間不夠了,capacity就會自動擴容,我們要了解的就是它的擴容規則:
int main()
{string s1("123456");cout << "Now capacity:" << s1.capacity() << endl;for (int i = 0; i < 200; i++){s1.push_back('x');if (s1.capacity() == s1.size()){cout << "Capacity Changed:" << s1.capacity() << endl;}}return 0;
}
尾插200個數據,使其擴容:
結果如下,我們可以發現擴容的方式很奇怪,一開始是約等于2倍,后面逐漸變成了1.5倍
這是Vs環境下的擴容,在Linux環境下就是標準的2倍擴容:
但是實際上呢,這里的capacity都是少了一個單位的,比如上面的應該是16,32,38,71,因為要給斜杠0預留一個空間。
可以看到不同的編譯器實現擴容的時候有一定差異,Vs下的編譯器的是clang,Linux環境下的編譯器是g++,實現的時候都有差異,但是不管Vs還是Linux,capacity都是少了1的,因為斜杠0.
2.3 reserve的使用
reserve?reverse?傻傻分不清咯~
reverse是逆置,而reserve的中文意思是預存,保留,是用來開空間的:
它實際影響的只有capacity,不會影響size:
int main()
{string s1("123456");cout << s1.size() << " " << s1.capacity() <<endl;s1.reserve(100);cout << s1.size() << " " << s1.capacity() <<endl;return 0;
}
我們開了100個空間,但是實際上開了111個空間?
Linux環境下還是老老實實的要多少開多少。
所以reserve和capacity一樣,平臺直接有差異。
reserve還可以用來進行縮容,但是因為vs默認是不縮容的,所以這里呢給個結論:
如果縮容,最低只能縮容到15(實際是16),因為string有一個成員是buff數組,如果string的長度不超過16就會存到buff數組上去,就不用在堆上單獨開開空間了,因為數組的大小是定的,16,所以再怎么縮容,都不會比15小。
但是也不是一無用處這個函數,比如涉及到多次開空間的題目,我們可以提前開好,省略了一下開空間的步驟,效率稍微高一點。
2.4 shrink_to_fit簡介
這個函數是縮容,但是是在C++11中引進的,雖然可以實現我們想要的功能,但是實際上內存越來越發達的時代,我們更多的追求的是效率,空間不夠的情況是比較少見的,而且這個函數使用代價挺大的,我們大多數人以為的縮容是這樣的:
有一塊空間,釋放到不需要的部分,但是在動態內存管理章節我們知道,釋放空間不能一段一段的釋放,只能一整塊的釋放,所以實際的縮容是把原來的空間釋放掉,重新開一塊我們想要的小空間,這個代價挺大的,所以不推薦使用。
2.5 resize的使用
前文提及,capacity的擴容只會影響capacity,不會影響size,但是resize兩個都會影響到:
int main()
{string s1("123456");cout << s1.size() << " " << s1.capacity() << endl;s1.resize(100);cout << s1.size() << " " << s1.capacity() << endl;s1.resize(5);cout << s1.size() << " " << s1.capacity() << endl;return 0;
}
如果我們是resize100的話,6后面的就全是斜杠0了,但是如果我們縮容,就會刪除一部分數據,比如這里的6就被刪除了。
int main()
{string s2("hello world");s2.resize(20, 'x');cout << s2 << endl;return 0;
}
resize插入數據會擴容,這里就是在d后面插入x知道字符串長度為20。
也可以當刪除使用,size小于原來的size就可以了。
2.6 clear的使用
clear清除,使用起來還是很簡單的:
int main()
{string s1("123456");cout << s1 << endl;s1.clear();cout << s1 << endl;return 0;
}
清除完畢。
3 String operations部分
前文已經介紹了,,,前文沒有介紹,嘿嘿,這里介紹c_str,copy,find,rfind,find_first_of,find_last_of,fin_first_not_of,find_last_not_of,substr,compare。
對于get_allocator簡單掠過,它涉及到了配置器,就不多介紹了。
3.1 c_str的使用
c_str返回的是字符串的指針,c++和C語言混用的使用就可能有點坑:
int main()
{string file("Test.cpp");FILE* fout = fopen(file.c_str(), "r");char ch = fgetc(fout);while (ch != EOF){cout << ch;ch = fgetc(fout);}return 0;
}
比如這里的fopen不加file. 的話,那么c_str就讀取不到了,但是在C語言里面這里都是直接放的指針,沒有考慮其他的,C++考慮的還要多一些。
當然函數本身的使用還是很簡單的,返回指針而已。
3.2 copy的使用
copy的本質是賦值,但是不是給string類的賦值,是給字符數組賦值,并且返回值是賦值過去的字符串長度,可以理解為返回的是len
int main()
{string s1("abcdefg");char str[20] = "123456";size_t ret = s1.copy(str, 3, 2);cout << s1 << endl;cout << str << endl;cout << ret << endl;return 0;
}
3.3 find和rfind的使用
find即尋找,rfind同理,倒過來尋找,也可以指定從哪里開始尋找,返回的就是找到的位置的下標,如果沒有找到返回的就是npos,find可以用來找子串也可以用來尋找單個字符,這里我們試試分割后綴:
string file("string.cpp.zip");
3.4 substr的使用
戛然而止,因為要分割字符串,我們還需要了解一個函數叫做substr,這個函數有利于我們分割字符串,參數兩個,一個是位置,一個是長度,如果不給長度,就是默認分割到字符串結束,所以有了find返回的下標,我們這里就可以實現分割.zip:
int main()
{string file("string.cpp.zip");size_t pos = file.rfind('.');string suffix = file.substr(pos);cout << suffix << endl;return 0;
}
那么為什么我們使用rfind呢?因為有兩個.,我們使用find,找到的就是第一個.,所以我們應該倒過來尋找,找到之后久交給substr就可以了。
當然,我們想要分割一段區間也是可以的:
int main()
{string s1("I am the best");string s = s1.substr(5, 3);cout << s << endl;return 0;
}
給一個區間,然后分割了3個字符,the就被分割出來了。
3.5 find_first_of(not)和find_last_of(not)的使用
首先看一下函數的參數,沒有什么特殊的地方,那就隨便過了?不能,因為最大的誤區在它的名字這里,find_first_of,一翻譯就是,找最開始的什么什么,如果這樣想就大錯特錯辣,構造和初始化有關系嗎從名字上看,絲毫沒有,這個也是同理的,這個的真正用法其實是:
int main()
{string str("Please, replace the vowels in this sentence by asterisks.");size_t found = str.find_first_of("aeiou");while (found != string::npos){str[found] = '*';found = str.find_first_of("aeiou", found + 1);}cout << str << endl;return 0;
}
這里介紹第二個重載的使用,其他的就可以類比了,這個函數的真正用法是找任意字符,即在一個字符串里面找參數中的任意字符,找到了就會返回下標,那么就可以對下標進行一些操作,比如修改,找到了也要記得修改找的位置。
last同理,只是last是從字符串末尾開始查找的,實際使用沒啥區別:
void SplitFilename(const std::string& str)
{std::cout << "Splitting: " << str << '\n';std::size_t found = str.find_last_of("/\\");std::cout << " path: " << str.substr(0, found) << '\n';std::cout << " file: " << str.substr(found + 1) << '\n';
}
int main()
{std::string str1("/usr/bin/man");std::string str2("c:\\windows\\winhelp.exe");SplitFilename(str1);SplitFilename(str2);return 0;
}
只是根據不同的需求使用不同的函數罷了,比如我要分割一個網站,從末尾開始分割就可以了。
not的使用就不介紹了,有了前面兩個函數的使用,這個看看也就會了。
3.6 compare的使用
compare的使用是字典序的比較,和strcmp是一樣的,別看它參數多,比較我們使用第一個就可以了,其他重載要是一定要使用,看看文檔也就會了:
int main()
{string s1("123456");string s2("321654");cout << s1.compare(s2) << endl;return 0;
}
int main()
{string s1("123456");string s2("321654");cout << (s1 < s2) << endl;return 0;
}
但是比較不僅可以用compare哦,還可以使用重載后的< >,就和cout能打印自定義類型一樣。
4 Non-member function overloads部分
這部分呢就是非成員函數的部分了,重載為了全局函數,本文介紹operator+,operator>> ,operator<<,getline,relational operators。
4.1 operator+的使用
這個函數說來奇怪,重載為全局函數不是因為多特殊,只是為了支持某種形式,先看一般使用:
int main()
{string s1("123456");string s2("Hello world");cout << s1 + s2 << endl;cout << s1 + "xxx" << endl;cout << "xxx" + s1 << endl;cout << s1 + 'a' << endl;cout << 'b' + s1 << endl;return 0;
}
看起來也沒什么奇怪的,那么特殊的原因是因為原來是想支持"xxx" + s1,但是發現重載為成員函數之后的第一個參數鐵定是this指針,所以為了支持那種形式就重載成全局函數咯,當然不影響使用。
4.2?relational operators
compare的使用已經使用過了重載后的<,這里深入一點,我們看到相關的重載有很多個,實際上就是進行比較的,類比于日期類的多個比較,這里我們結合日期類的寫法也就好理解了,當然,比較都是按照字典序進行比較的。
int main ()
{std::string foo = "alpha";std::string bar = "beta";if (foo==bar) std::cout << "foo and bar are equal\n";if (foo!=bar) std::cout << "foo and bar are not equal\n";if (foo< bar) std::cout << "foo is less than bar\n";if (foo> bar) std::cout << "foo is greater than bar\n";if (foo<=bar) std::cout << "foo is less than or equal to bar\n";if (foo>=bar) std::cout << "foo is greater than or equal to bar\n";return 0;
}
4.3 operator<< 和 operator>>的使用
這個就太簡單辣:
int main()
{string s1("123456");cout << s1 << endl;return 0;
}
直接就過了。
4.4 getline的使用
我們先上一個實際例子:
int main()
{string s1;cin >> s1;cout << s1 << endl;return 0;
}
原本程序的意思是輸入123 asd并進行打印的,但是>>重載了并不會打印完,這是因為空格,換行對于字符串的讀取來說都是結束標志:
int main()
{string s1;cin >> s1;while (1){cin >> s1;cout << s1 << endl;}return 0;
}
這樣就會更形象了,那么怎么結束這個程序呢?有兩種方法,一種是ctrl + c,暴力殺進程,直接結束,第二種是ctrl + z。
回歸正題,我們想要讀取空格怎么辦?
這里就需要用到getline了:
int main()
{string s2;getline(cin, s2);cout << s2 << endl;return 0;
}
這是第二個函數的重載,第一個的最后一個參數就是讓你自己設置結束標志,有興趣可以自己試試。
5 補充函數
對于string的學習不免遇到大數運算,這里提供一個函數,to_string和stoi,分別是整型轉字符串,字符串轉整型,但是不要用在大數運算上面,只能是說有一點點關系:
int main()
{string s2("111111");int ret1 = stoi(s2);cout << ret1 << endl;int ret2 = 112345648;string s3 = to_string(ret2);cout << s3 << endl;return 0;
}
相關的文檔就不上圖了,有興趣自己看看。
6 有關編碼
編碼是一種文字的映射,最基本的是ASCII編碼,這是美國科學家研究的,最初用來存儲他們的相關語言,最特殊的就是$,這個是ASCII一個標志性符號:
那么就引入了一個問題,全世界的文字那么多,不同的語言幾千種甚至上萬,該如何存儲呢?更不用說漢字常見的就有幾千個,所以漢字加起來是近9萬了,如果在計算機上表達自己的語言成為了一個難題:
int main()
{string s1("計算機");string s2("123456");return 0;
}
當我們存入了這樣的字符串,我們在內存能看到計算機嗎?
為什么會是?,編譯器是如何打印計算機三個字的?這里我們不得不佩服計算機科學家們,引入了一個種新編碼,叫做Unicode,稱為萬國碼,發明出來就是為了保證計算機能打印絕大多數國家的語言的:
那么一臺計算想要保證能打印不同的語言只靠一個ASCII可不夠,所以引入了utf8編碼,可以理解為utf8包含了Unicode和ASCIIm,與此同時還有utf16,utf32:
可以看到utf8通過一個字節的開頭判斷這個字屬于哪個字節范圍,所以計算打印的時候實際上是按照編碼表去尋找最后打印的,那么16和32因為存儲的成本變高了,所以不太推薦使用,目前很多機器都是使用的utf8,vs和Linux環境下使用的都是utf8。
那可能會問了,和string有什么關系?string是字符的集合:
看到這個basic了嗎?我們學習的都是1個字節的字符串集合,通過編碼的介紹不難猜出字符不止只有一個字節,比如:
還存在這種寬字節的字符,這就是編碼的意義,存儲更多的字符,使得計算機成為一門共同的語言!
感謝閱讀!