文章目錄
- 一、string介紹
- 二、string使用
- 構造函數
- 析構函數
- 賦值運算符重載
- string的遍歷+修改方法
- 1、下標+[]
- 2、迭代器
- 3、范圍for
- 迭代器使用詳解
- const迭代器
- 反向迭代器(reverse)
- Capacity(容量相關)
- size/length
- max_size
- capacity
- clear/empty
- shrink_to_fit(縮容)
- reserve(擴容)
- resize
- element access(數據訪問)
- operator[]/at
- back/front
- Modifiers(修改相關)
- append(追加)/operator+=
- assign(賦值)
- insert/erase
- repalce
- String operations
- c_str/data
- copy
- substr
- find/rfind
- find_first_of/find_last_of
- 非成員函數
- 關系運算符
- operator+
- getline
- 三、總結
一、string介紹
在學習string之前我們要先了解string其實誕生的比STL早,所以從發展歷史角度來看你它應該歸于標準庫,但是從廣義來看它又應歸于STL,因為它也是數據結構,它也有STL的各種通用接口。
從這里我們可以看到我們要學習的string是屬于C++標準庫的類,并且是經過類模板實例化的具體類型,本質上可以看作的管理char字符的順序表,也可以簡單理解成字符串。
注意:要使用string需要using namespace std和包string頭文件。
二、string使用
構造函數
string的構造函數很多,這里我們介紹最常見的幾種。
1、默認構造 string()
string s1;
2、用常量字符串構造string(const char* s)
string s2("good moring");
3、拷貝構造 string(const string& str)
string s3(s2);
4、子串構造 string(const string& str, size_t pos, size_t length = npos)
它的功能是用string的一部分用來拷貝構造,該部分是從pos這個位置到length位置,其中length給了一個缺省參數npos(缺省參數只能用常量,全局變量或者靜態變量),它是string的靜態成員變量,它的值是整型的最大值,因為string一般不會有這么長,所以用這個值就是為了讓string有多少拷貝多少,直到結尾。
string s4(s3, 1, 7);
5、拷貝前n個字符構造 string(const char* str, size_t n)
const char* str = "hello world";string s5(str, 7);
6、用n個c字符構造 string(size_t n, char c)
string s6(100, 'x');
析構函數
因為string沒有動態開辟空間的成員變量,所以就不需要回收資源,調用它的默認析構函數就好了,所以析構我們就不展開討論了。
賦值運算符重載
string s1("good moring");string s2;s2 = s1;s2 = "wusaqi";s2 = 'x';
string的遍歷+修改方法
平時我們使用string這個類,遍歷和修改操作是不可避免的,接下來小編會介紹三種方法來實現。
1、下標+[]
我們首先來認識一下string里的operator[]函數,它可以返回string的第pos位置的字符,如果越界了還會報錯,并且返回的是引用,代表返回后你還可以修改它。
我們首先可以看到它重載了兩個函數,一個參數(也就是this指針)沒被const修飾,返回值為char&,一個參數被const修飾,返回值為const char&,但是我們一般只用實現const版本的就行了,因為普通對象也可以調用const,可這里為什么要實現兩種呢?因為這樣可以讓對象使用到最匹配它的函數,因為編譯器會去找最匹配對象的函數,不然普通對象去調用后返回的對象就變成不能修改了,違背了我們本意。
string s1("good morning");for (int i = 0; i < s1.size(); i++){s1[i]++;}cout << s1 << endl;
這樣我們就能遍歷和修改這個string了,讓每一個字符+1。 這里的size接口能返回string的字符個數,但是我們看c++網站就會發現還有一個和size一樣功能的接口length,這里其實就涉及到我們之前談到的歷史發展的原因,在STL還沒出現的時候string用的就是length,但是當后面STL歸進來后為了統一接口才又引入的size。
2、迭代器
在STL容器中都會有迭代器(iterator),它是類型屬于這個容器的類型的類域,迭代有遍歷的意思,迭代器的用法和指針很相似,可以先把它簡單想象成像指針一樣的類型對象。
我們要得到迭代器就需要2個成員函數:begin() 和 end(),begin() 返回第一個數據位置的迭代器,end() 返回最后一個數據下一個位置的迭代器,所以可以得到這兩個成員函數返回的迭代器的區間是左閉右開的。
用法如下:
string::iterator it1 = s1.begin();while (it1 != s1.end()){//改變迭代器所在位置的值(*it1)--;//讓迭代器向前移動it1++;}
迭代器的意義:
1、同一類似的方式遍歷修改容器。
2、算法脫離具體底層結構,和底層結構解耦,算法獨立模板實現針對多個容器處理。
3、范圍for
在學習范圍for之前我們要先了解一些C++11的新特性:
1、auto是關鍵字,可以自動推導對象的類型,它和具體類型一樣,不加&是拷貝,加&才是引用。當auto推導指針時,可以用 auto 也可以用 auto* ,只不過前者可以推導任意類型,后者只能推導指針。
auto x = 8;auto y = 8.8;cout << x << endl;cout << y << endl;int& z = x;auto m = z; //拷貝auto& n = z; //引用auto p = &x;auto* p = &x; //只能推導指針
范圍for也是C++11支持的新特性,所有容器都支持,它的用法如下:
string s1("hello world");for (auto ch : s1){cout << ch << " ";}
上面這段代碼做了什么?簡而言之就是3個自動:
自動取容器數據賦值給ch(ch位置的名稱可以隨意取)
自動判斷結束
自動迭代(向后遍歷)
1、但是范圍for無法修改容器里的數據,因為它的底層本質還是會替換成迭代器,就是把*迭代器賦值傳遞給ch,所以改變ch對容器里的數據沒有影響,如果想改變容器里數據就要在zuto后邊加&,成為引用。
string s1("hello world");for (auto& ch : s1){cout << ch << " ";}
2、范圍for里面的auto也可以寫成具體類型,但是一定要和容器的數據類型匹配。
string s1("hello world");for (int ch : s1){ch++;}
3、范圍for不僅支持容器,數組也支持。
int arr[] = { 1,2,3,4,5 };for (auto ch : arr){cout << ch << " ";}
迭代器使用詳解
先前我們簡單了解了一下迭代器,接下來小編詳細介紹一下string中各種迭代器和它們的用法。
const迭代器
我們看上圖可以發現begin()和end()都有兩個重載函數,一個普通版,一個const版,當對象沒被const修飾時調用普通版,當對象被const修飾時則調用const版,這里我們要注意其中const版本不是const iterator,而是const_iterator,因為如果是前者的話const是修飾迭代器本身無法改變,但是迭代器遍歷會一直改變,實踐中沒法使用,后者才是修改迭代器指向的內容無法改變,這里可以把const_iterator簡單理解成一種類型。
string s1("hello world");string::iterator it1 = s1.begin();const string s2("hello world");string::const_iterator it2 = s1.begin();
反向迭代器(reverse)
獲取反向迭代器的函數也有兩種重載,也是一種普通版,一種const版,這里注意,因為是反向迭代器,所以它是從后向前遍歷的,所以rbegin()返回的是最后一個字符的位置,rend()返回的是第一個字符的前一個位置,迭代器++也是從后往前走的。
迭代器在STL容器中的意義(用法):
1、遍歷和修改容器數據。
2、可以把容器數據以迭代器發方式傳給算法,因為算法一般是按函數模板傳迭代器區間的方式,用迭代器訪問容器數據就可以實現算法和容器的底層解耦。
這里還要補充一點,cbegin()和cend()是返回確定的const對象,但是實踐中很少這樣用,了解一下就行。
Capacity(容量相關)
在了解容量相關接口之前我們先認識一下string包含哪些內容,具體等我們后面講到底層實現時小編再仔細講解,這里我們先大致了解一下:其中有有動態開辟的字符數組_str,字符數組中的有效數據個數_size,還有數組容量_capacity,這里的我們要介紹的接口就是圍繞它來展開。
size/length
它們都是返回字符個數的接口,前面我們詳細介紹過,這里小編就不贅述了。
max_size
這其實是一個沒有參考意義的接口,了解一下就行,它返回的是理想情況下size最大能有多大,也就是string最長能有多長。
capacity
我們在使用string過程中難免會插入數據,但是它的容量是動態開辟的,如果你插入一次擴容一次效率就會變得很低,所以引入capacity就是為了解決這一問題,要擴容就會在當前容量的基礎上2倍或1.5倍擴容。
1、這里我們還要注意size和capacity的返回值規定均不包含 \0,所以這里size顯示是11,但是實際上是12,還要包含\0,capacity能存15個有效字符,所以實際要開16個字符空間。
2、擴容機制C++并沒有統一規定,所以具體實現都由編譯器廠商實現,比如vs是第一次二倍擴容,后續是1.5倍,而linux(g++)則是二倍擴容。
clear/empty
clear和empty很簡單,這里就一起講了。
clear是將string中所有數據清空,但是capacity不會改變,可以理解成我們在單鏈表時的size–。
empty就是string對象判空,若當前對象為空,則返回1,若不為空則返回0。
shrink_to_fit(縮容)
- 文檔中說明該接口是把空間減少到可以適配size,就是把多余的空間都釋放了,但是我們看文檔下面:這個請求是no-binding(不具有約束力的),所以編譯器可能由于內存對齊之類的原因最后實現出來capacity是大于size的,也就是并沒有把多余的空間全部釋放。
- C++規定動態申請的連續的數組空間只支持完整的申請和釋放,不能部分釋放,所以縮容代價是很大的,它不支持原地縮容,我們想縮容只能重新申請一塊空間,再把數據拷貝過來,再釋放舊空間。
- 所以縮容可以理解成是以時間換空間,在如今來看,我們普遍都會采用以空間換時間,現代設備內存普遍都比較大,時間在我們看來會更重要。
reserve(擴容)
- 我們看文檔可以發現擴容也不是完完全全按照你給的n值來擴容的,假設你要擴到100,最后是可能比100還大的。
- 當n比capacity小時,我們會期望它能縮容,但是讀文檔它對縮容是不具有約束力的,也就是縮與不縮是取決于編譯器,所以我們要縮容最好使用shrink_ti_fit,但是縮容我們都是不建議用的。
- reverse是不會影響字符串內容和長度的。
- 擴容是我們常用的接口,因為當我們確定知道需要多少空間時,可以用它提前開好,避免 多次擴容,提高效率。
resize
這是改變字符串長度的接口,我們看到它重載了兩個函數,當n比size大時,它會插入字符直到符合我們想要的長度,如果沒有指定字符,默認會插入 ‘\0’,如果指定了字符,就會插入我們指定的字符。
改變字符串長度有三種情況,讓小編來舉例介紹:
1、n大于capacity:插入數據+擴容
(這里擴容也有可能比要求的空間大)
2、n大于size小于capacity:插入數據
3、n小于size: 刪除數據
(刪除多余字符,只保留前n個字符,不會縮容)
當我們不指定字符插入時,就會默認插入 ‘\0’,這個過程打印不好觀察,我們借助監視來看:
element access(數據訪問)
operator[]/at
- operator[]我們前面也介紹過,小編這里想補充的是它不同于字符數組的指針+解應用訪問,對于越界的檢查只是抽查行為,并且只能檢查越界寫。operator[]因為它是函數,所以對越界檢查非常嚴格,我們后面學習底層會發現其實就是函數一來就用了assert斷言,但是assert在release下會不起作用,并且assert是直接終止程序。
- at功能和operator[]相同,但是它越界檢查的方式是拋異常,可被捕獲,不會影響程序終止。
- 一般情況下我們還是用operator[],因為程序最開始都要走debug版,一但越界就能馬上檢查出來。
back/front
back是返回尾部位置的字符,front是返回頭部位置的字符,這兩個我們都用的比較少。
Modifiers(修改相關)
append(追加)/operator+=
我們前面介紹的push_back只能插入單字符,接下來這兩個接口功能相似,都可以在string后面追加單字符、常量字符串或者string對象,但是我們更喜歡用operator+=,因為理解起來更形象。
assign(賦值)
這和我們前面介紹到的operator=功能也差不多,只是多重載了幾種賦值方法。
insert/erase
string沒有提供頭部的插入刪除接口,所以要實現頭部或者中間部分的插入刪除就需要借助這兩個接口:
insert:在指定位置之前插入數據。
erase:刪除指定位置及指定位置之后長度為n的字符或字符串,如果不傳長度就會將這個位置及之后的所有數據全部刪完,因為給了個缺省值npos(整型的最大值)。
(但是這兩個接口要謹慎使用,因為string底層是數組,在數組當中插入刪除就勢必會挪動數據,會使時間效率變低。)
repalce
這個接口可以實現將單個字符或者字符串替換成單個字符或字符串。
(該接口也要謹慎使用,因為也有可能涉及數據的挪動)
String operations
c_str/data
在實際項目中,我們可能會遇到一些接口只能用C語言調用,比如文件操作fopen只能通過const char*類型的對象來調用,但是在C++項目中只有string,該接口就是為了解決這一問題,它能返回string在堆上的動態數組的首元素的地址。
data的功能和c_str完全一致,這也是一些歷史原因造成的。
copy
這是拷貝接口,從pos位置開始拷貝len個字符給s,返回值是拷貝過來的有效數據個數,但是這個接口我們很少用。我們拷貝子串通常是用下面介紹的substr。
substr
這個拷貝接口和copy的不同在于它是用子串構造一個string對象再返回,使用更方便。
find/rfind
這是查找接口,從pos位置開始向后查找指定內容,找到了就返回找到位置下標,未找到或者找完了就返回npos。
下面這個例子就是find和replace一起使用,將字符串中所有空格字符替換為%%,pos+2目的是替換后不再從頭開始找,而是從%%的下一個位置開始找,提高效率。但是replace我們都不建議用,更建議用第二種方法。
第二種方法就是創建一個新string,先將新空間開到和原字符串一樣的大小,依次遍歷原字符串若遍歷到空格就插入##,若不為空格就按原字符插入。
(該方法就是以空間換時間,不用像replace那樣挪動數據)
rfind基本和find一樣,只不過它是從后往前找。
find_first_of/find_last_of
這兩個接口我們用的非常少,它的功能是找到并返回目標字符串中所有你提供的字符串的字符的下標,比如下面這段代碼,它能找到str中所有‘a’‘e’ ‘i’ ‘o’ 'u’的字符的下標并返回。
find_last_of是從后往前找。
std::string str ("Please, replace the vowels in this sentence by asterisks.");std::size_t found = str.find_first_of("aeiou");while (found!=std::string::npos){str[found]='*';found=str.find_first_of("aeiou",found+1);}std::cout << str << '\n';
非成員函數
關系運算符
關系運算符放在非成員函數里的原因是為了支持第二種字符串與string比較的情況,因為如果是成員函數那么第一個參數必定是string(this指針)。
operator+
把它放在非成員函數的原因和關系運算符一樣。
getline
這個接口是為了解決我們要提取一串字符串的情況,因為如果我們用cin提取字符串它只會提取到空格或者換行符之前,如果這段字符有多個空格比如“good morning wusaqi”,cin只會提取“good”,如果用getline就可以讀一行,直到遇到換行符才終止。
我們看到它還有一個重載版本,三個參數delim可以讓我們自己規定讀取結束的標志字符。
三、總結
string的各種接口其實不用我們刻意去記,在用的時候慢慢熟悉就行了。 其中比較重要的接口就是增刪查改+遍歷:
增:operator+=/insert
刪:erase
查:find
改:operator[]/迭代器
以上就是小編分享的全部內容了,如果覺得不錯還請留下免費的贊和收藏
如果有建議歡迎通過評論區或私信留言,感謝您的大力支持。
一鍵三連好運連連哦~~