?hello,又見面了!
云邊有個稻草人-CSDN博客
C++_云邊有個稻草人的博客-CSDN博客——C++專欄(質量分高達97!)
菜鳥進化中。。。
目錄
一、為什么要學習string類?
1.1 C語言中的字符串
1.2 面試題(暫不做講解)
二、?標準庫中的string類
2.1 string類(了解)
2.2 auto和范圍for
2.3 string類的常用接口說明(注意下面我只講解最常用的接口)
2.3.1?string類對象的常見構造
2.3.2?string類對象的容量操作
2.3.3?string類對象的訪問及遍歷操作
【operator[ ]】?
【遍歷begin+end+迭代器第一彈】
【迭代器第二彈】
【const對象的迭代器和反向迭代器】?
2.4 string類對象的Element access(元素存取)
2.5 string類對象的修改操作
2.6?String類內部擴容細節
2.7 小試牛刀
? ? ? ? ?
??????????????——————————————《Rain》——————————————
正文開始——
一、為什么要學習string類?
1.1 C語言中的字符串
1.2 面試題(暫不做講解)
415. 字符串相加 - 力扣(LeetCode)
二、?標準庫中的string類
2.1 string類(了解)
https://cplusplus.com/reference/string/string/?kw=string,最好觀看第一手的資料,自己閱讀英文版
?C++標準庫都封裝在std這個命名空間里面的,在使用string類時,必須包含#include頭文件以及using namespace std;
#include<iostream>
#include<string>
#include<assert.h>using namespace std;//class string
//{
//public:
// s2[]的底層
// //為什么string類的對象能直接利用[]來訪問和修改元素呢?下面是[]運算符的重載,引用返回能直接修改元素
// char& operator[](size_t pos)
// {
// assert(pos < _size);
// return _str[pos];
// }
//private:
// char* _str;
// size_t _size;
// size_t _capacity;
//};int main()
{string s1;//空字符串string s2("1111122222");//將字符串拷貝s2進行賦值string s3("1111111111", 3);//拷貝字符串里面的前3個字符string s4(100, 'x');string s5(s2, 4, 3);//這里的4(pos)指的是下標string s6(s2,9);string s7(s2,4,90);cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;cout << s5 << endl;cout << s6 << endl;cout << s7 << endl;//s2.operator[](0) = 'x';相當于調用的函數s2[1] = 'x';//直接像數組一樣利用下標來修改內容cout << s2 << endl;//利用循環來遍歷s2for (int i = 0; i < s2.size(); i++){s2[i]++;}cout << s2 << endl;return 0;
}
2.2 auto和范圍for
auto關鍵字
- 在早期C/C++中auto的含義是:使用auto修飾的變量,是具有自動存儲器的局部變量,后來這個不重要了。C++11中,標準委員會變廢為寶賦予了auto全新的含義即:auto不再是一個存儲類型指示符,而是作為一個新的類型指示符來指示編譯器,auto聲明的變量必須由編譯器在編譯時期推導而得。
- 用auto聲明指針類型時,用auto和auto*沒有任何區別,但用auto聲明引用類型時則必須加&。
- 當在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,因為編譯器實際只對第一個類型進行推導,然后用推導出來的類型定義其他變量。
- auto不能作為函數的參數(C++20開始支持),可以做返回值但是建議謹慎使用,見下:
- auto不能直接用來聲明數組
int main()
{int i = 0;int j = 0;//自動推導類型,根據等號右邊的數據來自動推導左邊數據的類型auto z = i; // intauto m = 1.1; //doubleauto p = &i; //int*int& r1 = i; //r1的類型是int&,不是intauto r2 = r1; //int,不是int&auto& r3 = r1; //int&//auto r4; //報錯list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);//利用迭代器來遍歷鏈表list<int>::iterator it = lt.begin();auto it = lt.begin();//在這里使用迭代器就非常的爽!不用再寫那么長的代碼了while (it != lt.end()){cout << *it << " ";it++;}cout << endl;// 后面學了map用auto會更爽,我們先引入map的迭代器看看這里使用auto的感覺// auto語法糖 ---> 簡化代碼,替代寫起來長的類型std::map<std::string, std::string> dict;//std::map<std::string, std::string>::iterator dit = dict.begin();auto dit = dict.begin(); //很甜,超級甜~return 0;
}
范圍for
- 對于一個有范圍的集合而言,由程序員來說明循環的范圍是多余的,有時候還會容易犯錯誤。因此C++11中引入了基于范圍的for循環。for循環后的括號由冒號“ :”分為兩部分:第一部分是范圍內用于迭代的變量,第二部分則表示被迭代的范圍,自動迭代,自動取數據,自動判斷結束。
- 范圍for可以作用到數組和容器對象上進行遍歷。
- 范圍for的底層很簡單,容器遍歷實際就是替換為迭代器,這個從匯編層也可以看到。
int main()
{string s1("hello world");list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);// 范圍for // 適用于容器遍歷和數組遍歷// 自動取容器的數據賦值給左邊的對象,左邊的對象的名稱隨便取// 自動++,自動判斷結束// 原理:范圍for底層是迭代器//for (char ch : s1)for (auto ch : s1)//這里更喜歡用auto{cout << ch << " ";}cout << endl;//for (int n : lt)for (auto n : lt){cout << n << " ";}cout << endl;return 0;
}
?有一個問題,如果按下面的代碼數組里面的內容會不會被修改呢?
for (auto ch : s1)//這里更喜歡用auto{cout << ch << " ";}cout << endl;for (auto ch : s1)//這里更喜歡用auto{ch++;}for (auto ch : s1)//這里更喜歡用auto{cout << ch << " ";}cout << endl;
為什么呢?
前面講的只是單純的賦值,并不能改變里面的數據,加上&即可,真是嘎嘎嘎香
對于范圍for哪兩種情況下要用引用呢?
- ?修改容器里面的數據
- 如果容器里面存的是比較大的對象,那我們最好用引用以減少拷貝(如果我們用引用同時又不想修改里面的內容,就可以用const引用)
總結一下,對于string有三種遍歷方式,這三種遍歷方式都可讀可寫
- [下標 ],對于sring和vector會比較方便,不適用于物理存儲空間不連續的容器
- 迭代器,通用遍歷方式(可用于 list 這種物理空間不連續的)
- 范圍for
什么類都可以用范圍for嗎?
不是,像日期類,沒有這種用法。原理:范圍 for 的底層是迭代器
2.3 string類的常用接口說明(注意下面我只講解最常用的接口)
2.3.1?string類對象的常見構造
2.3.2?string類對象的容量操作
- size()與length()(都不包含最后結尾的\0)方法底層實現原理完全相同,引入size()的原因是為了與其他容器的接口保持一致,一般情況下基本都是用size()。
- clear()只是將string中有效字符清空,不改變底層空間大小。
- resize(size_t n) 與 resize(size_t n, char c)都是將字符串中有效字符個數改變到n個,不同的是當字符個數增多時:resize(n)用0來填充多出的元素空間,resize(size_t n, char?c)用字符c來填充多出的元素空間。注意:resize在改變元素個數時,如果是將元素個數增多,可能會改變底層容量的大小,如果是將元素個數減少,底層空間總大小不變。
- reserve(size_t res_arg=0):為string預留空間,不改變有效元素個數,當reserve的參數小于string的底層空間總大小時,reserver不會改變容量大小。
- max_size指最大能開到多少空間,不切實際沒有什么意義,不同平臺下返回的值不同。
【reserve 和 reverse】見下:
//reserve 保留 預留
//reverse 反轉 翻轉
int main()
{string s1;// 提前開空間,避免擴容(這是在知道大概需要多少空間的情況下比較好)s1.reserve(200);//編譯器會開的比200大,同樣滿足需求,但是不會是小cout << s1.capacity() << endl;return 0;
}
2.3.3?string類對象的訪問及遍歷操作
【operator[ ]】?
?上次我們學的日期類里面的取地址運算符的重載也是支持const對象和普通對象都可以調用。
【遍歷begin+end+迭代器第一彈】
#include<iostream>
#include<string>
using namespace std;int main()
{string s1("hello world");//[下標]的方式去遍歷for (int i = 0; i < s1.size(); i++){s1[i]++;//利用下標去修改數組的內容}for (int i = 0; i < s1.size(); i++){cout<<s1[i]<<" ";}cout << endl;// 迭代器---像指針一樣的對象(有可能是指針,有可能不是指針,要看底層的實現)// iterator是一個類型,是屬于string類里面的一個內部類string::iterator it1 = s1.begin();while (it1 != s1.end())//s1.end()指向的是最后一個數據的下一個位置{(*it1)--;++it1;//*it1就是這個位置,++來走到下一個位置}it1 = s1.begin();while (it1 != s1.end())//s1.end()指向的是最后一個數據的下一個位置{cout << (*it1) << " ";++it1;//*it1就是這個位置,++來走到下一個位置}cout << endl;return 0;
}
用下標的訪問方式來修改和讀取數據很香,為啥還要用迭代器呢?
用下標訪問的方式只適用于string和vector這樣的結構,string和vector底層是連續的物理空間,用[ ]來訪問就很方便,,但是我們后面學習鏈表和樹形結構再用[ ]來訪問就很不方便。[ ]不是通用的方式,而迭代器是通用的方式
看看下面舉例迭代器具體在哪里使用——list(記得加上頭文件#include<list>)
所有的容器都要定義迭代器,迭代器是所有容器通用的一種東西,都可以使用迭代器去遍歷,所使用的方式也都是很相似的
int main()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);//利用迭代器來遍歷鏈表list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;return 0;
}
【迭代器第二彈】
int main()
{string s1("hello world");string::reverse_iterator rit = s1.rbegin();while (rit != s1.rend()){cout << *rit << " ";++rit;}cout << endl;return 0;
}
【const對象的迭代器和反向迭代器】?
int main()
{const string s1("hello world");string::const_reverse_iterator rit = s1.rbegin();//不能修改容器里面的內容while (rit != s1.rend()){cout << *rit << " ";++rit;}cout << endl;const string s2(s1);string::const_iterator it = s2.begin();//不能修改容器里面的內容,這里可以用autowhile (it != s2.end()){cout << *it << " ";++it;}cout << endl;return 0;
}
上面介紹的就是我們常用的四種迭代器,iterator,reverse_iterator,const_iterator,const_reverse_iterator,其他的cbegin啥的我們不常用。
2.4 string類對象的Element access(元素存取)
對于operator[ ],見下:
?對于at,不同于operator[]直接assertion報錯at會有拋出異常機制,見下:
back,front 返回的是最后一個字符和第一個字符,我們用[ ]和at也可以實現,注意返回的是引用,可以用來修改數據。(具體自己來查看文檔)
int main()
{try{string s1("hello world1111");cout << s1.size() << endl;cout << s1.length() << endl;cout << s1.capacity() << endl;//空間大小,其實比本身容量還要再大一個cout << s1.max_size() << endl;cout << endl << endl;s1.clear();cout << s1.size() << endl;cout << s1.capacity() << endl;//s1[20];s1.at(20);}catch (const exception& e){cout << e.what() << endl;}return 0;
}
2.5 string類對象的修改操作
- 1. 在string尾部追加字符時,s.push_back(c) / s.append(1, c) / s += 'c'三種的實現方式差不多,一般情況下string類的+=操作用的比較多,+=操作不僅可以連接單個字符,還可以連接字符串。
- 對string操作時,如果能夠大概預估到放多少字符,可以先通過reserve把空間預留好。
int main()
{//其實string底層跟我們前面學的順序表很相似class String{public://...private:char buff[16];char* _str;size_t size;size_t _capacity;}//利用push_back只能插入單個字符string s1("hello");s1.push_back(',');s1.push_back('w');cout << s1 << endl;//利用append來插入字符串,具體更多用法查看文檔s1.append("orld");cout << s1 << endl;s1.append(10, '~');cout << s1 << endl;string s2("hello pig");//利用迭代器s1.append(s2.begin(), s2.end());//將一整串都直接插入到s1的后面cout << s1 << endl;s1.append(s2.begin()+6, s2.end());//可以定位s2的字符將其插入cout << s1 << endl;//但是我們更多是使用+=這個運算符重載,這個確實更香,不經常使用push_back和appendstring s3("hello");s3 += ',';s3 += "world";cout << s3 << endl;return 0;
}
2.6?String類內部擴容細節
int main()
{string s1;size_t old = s1.capacity();for (int i = 0; i < 200; i++){s1 += 'x';if (s1.capacity() != old){cout << "capacity:"<< s1.capacity() << endl;old = s1.capacity();}}cout << endl;string s2("11111111");string s3("111111111111111111111222222222222222222");cout << sizeof(s2) << endl;return 0;
}
2.7 小試牛刀
917. 僅僅反轉字母 - 力扣(LeetCode)
感慨一下,學了一些算法思想思路確實就是不一樣了哈,繼續學起來。對于本題,不難看出要使用雙指針算法,經典的雙指針算法。
值得一提的是,我們要單獨寫一個判斷是否是字母的函數,這樣在while循環里面的條件就比較好寫。這也是我第一次用C++來寫算法題,那就一個方便呀,以前過得是什么苦日子~
現在基本啥都有了,以前是啥也沒有純純自己造啊/woqu,還有,甘蕉力扣比洛谷刷題感覺好。
class Solution {
public:// 單獨寫一個函數——判斷是否是字母bool isLetter(char ch) {if (ch >= 'a' && ch <= 'z')return true;if (ch >= 'A' && ch <= 'Z')return true;return false;}string reverseOnlyLetters(string s) {int begin = 0;int end = s.size() - 1;while (begin < end) {while (begin < end && !isLetter(s[begin])) {begin++;}while (begin < end && !isLetter(s[end])) {end--;}// 交換swap(s[begin], s[end]);begin++;end--;}return s;}
};
387. 字符串中的第一個唯一字符 - 力扣(LeetCode)
387. 字符串中的第一個唯一字符 - 力扣(LeetCode)
字符串最后一個單詞的長度_牛客題霸_牛客網
125. 驗證回文串 - 力扣(LeetCode)
415. 字符串相加 - 力扣(LeetCode)
string,vestor,list 都想實現逆置,逆置的底層邏輯是一樣的,有點想實現逆置的函數模版,但是string,vector,list的底層結構不一樣就無法直接寫一個逆置的函數模版,但是!它們共同的一點都有迭代器,哈哈此時我們可以寫一個關于迭代器的逆置函數模版,只要給我你的迭代器我就可以實現這些容器的逆置。
class Solution {
public:string addStrings(string num1, string num2) {string str;str.reserve( max(num1.size(), num2.size()) + 1);int end1 = num1.size()-1,end2 = num2.size()-1;int next = 0;while(end1 >= 0 || end2 >= 0){ int x1 = end1 >= 0 ? num1[end1--]-'0' : 0 ;int x2 = end2 >= 0 ? num2[end2--]-'0' : 0 ;int ret = x1 + x2 + next;next = ret / 10;ret = ret % 10;str += ret+'0';}if(next == 1){str += '1';}reverse(str.begin(),str.end());return str;}
};
541. 反轉字符串 II - 力扣(LeetCode)
557. 反轉字符串中的單詞 III - 力扣(LeetCode)
43. 字符串相乘 - 力扣(LeetCode)
找出字符串中第一個只出現一次的字符_牛客題霸_牛客網
今天就先學到這里吧,剩下的明天繼續更!
完——
? ? ? ? ?
??????????????——————————————《Rain》——————————————
Rain_秦基博_高音質在線試聽_Rain歌詞|歌曲下載_酷狗音樂
至此結束——
我是云邊有個稻草人
期待與你的下一次相遇!
(有機會再看一遍怪奇物語全季)