--- GA: G-RZYLL0RZGV --- ###### tags: `大一程設-下` `東華大學` `東華大學資管系` `基本程式概念` `資管經驗分享` 字串String === [toc] 在這篇筆記會介紹 C-string(char array)、string 的應用,同時也會介紹常用到的 function,還有在使用 char array 跟 string 時須注意的細節。 # cstring ## C-string and null character 請務必記得引入函式庫 ```cpp= #include <cstring> ``` ### null character `'\0'` 和一般的陣列相同,C-string的index初始都是0,不同的是,C-string並非使用整數的變數來追蹤目前有多少陣列被使用,而是使用 `'\0'`值放在所有值的最尾端。當電腦在逐一讀取C-string value時,只要它讀到陣列裡的值是`'\0'` ,電腦便會停下來,也因此在使用C-string時,陣列的最後一定是null character。 假設程式碼如下,宣告一個名為c的Cstring,長度為5,然而這個程式碼在執行後電腦會報錯。 **報錯資訊:** 初始化給的值對陣列而言太長了。 `c[0]='1'` `c[1]='2'` `c[2]='3'` `c[3]='4'` `c[4]='5'` ``` cpp= #include <iostream> #include <cstring> using namespace std; int main(){ char c[5]="12345"; cout<<c; } ``` 會發生這樣的問題,就要回到這一節所介紹的null character,由於我們cstring的長度一開始就設好為5,值剛好index是0~4,長度也同樣是5,而電腦在宣告cstring時會自動幫我們補上一個`'\0'`,然而這個範例的陣列並沒有為null character預留長度1空間,因此系統才會報錯。 所以當我們將陣列長度改為6,就可以正常執行。 `c[0]='1'` `c[1]='2'` `c[2]='3'` `c[3]='4'` `c[4]='5'` `c[5]='\0'` ``` cpp= #include <iostream> #include <cstring> using namespace std; int main(){ char c[6]="12345"; cout<<c; if(c[5]=='\0') cout<<endl<<"null character 有在這!"; return 0; } ``` ==**宣告cstring時,長度總是要比需要的長度多1,來存放`'\0'`**== 不只是宣告cstring時需要注意,在更改值時也要特別留意,若我們不小心將上題`c[5]='a'`原本存放null character的位置改成別的字符,雖然程式依舊能執行,但如果有使用到其他的function,而那些function在處理cstring仍需要讀到null character才停止,使用上就會有問題。 ### C-string如何使用 在宣告cstring時就和宣告其他型態的陣列差不多 - 宣告陣列 ```cpp= char a[10]; ``` - 宣告陣列時直接給初始值 - 第一種:有給長度 ```cpp= char a[10]="abcde"; ``` - 第二種:沒給初始長度 ```cpp= char a[]="abcde"; ``` 程式跑起來後,電腦會自動幫a這個cstring配置空間,並給予長度6的cstring陣列,長度5給`"abcde"`,而最後一個長度給`'\0'`。 `c[0]='a'` `c[1]='b'` `c[2]='c'` `c[3]='d'` `c[4]='e'` `c[5]='\0'` - 宣告陣列後才給值(需 `#include<cstring>` ) ```cpp= #include<cstring> int main(){ char a[10]; strcpy(a,"abcde"); //將後面的abcde指派給a[10] } return 0; ``` `c[0]='a'` `c[1]='b'` `c[2]='c'` `c[3]='d'` `c[4]='e'` `c[5]='\0'` `c[6]=?` `c[7]=?` `c[8]=?` `c[9]=?` ## cstring function 以下介紹的幾個function皆需使用<span style="color:red;"><font size="3">**cstring**</font></span>函式庫,為cstring提供不同的功能。 | Function名稱 | 說明 | | ------------ | ---- | | strcpy(cs1, cs2) | 把cs2的字串指派給cs1 | | strcat(cs1, cs2) | 把cs2的字串接在cs1後方 | | strlen(cs1) | **(int型態)** 回傳cs1長度 | | strcmp(cs1, cs2) | 回傳int型態,等於0代表兩字串相同,小於0代表cs1小於cs2,大於0則代表cs1大於cs2| <span style="color:red">**若你是使用 visual studio,請注意以下事項!!!礙於編輯器的關係,有些 function 的名字有些不同。**</span> * strcpy(cs1, cs2) 在 visual studio 為 <span style="color:red">strcpy_s(cs1, cs2)</span> * strcat(cs1, cs2) 在 visual studio 為 <span style="color:red">strcat_s(cs1, cs2)</span> 原因是因為微軟的編輯器對於字串處理比較嚴格,所以在設計上有額外的處理,但 function 的意義跟用法並沒有改變。 --- 由於上面這幾個function都可能存在一些問題,像是無法確認變數是否足夠大可以儲存複製過來的變數,因此根據上面這些function又有了下面的這幾個function,功能差不多,只是下面的function更安全。 | Function名稱 | 說明 | | ------------ | ---- | | strncpy(cs1, cs2 , Length_Limit )| 與strcpy(cs1, cs2)相同, cs2複製給cs1最多Length_Limit個 | ```cpp= char c[]="abcdef",C[]="ABCDEF"; strncpy(c, C , 3); cout<<c; ``` ``` ABCdef ``` --- | Function名稱 | 說明 | | ------------ | ---- | | strncat(cs1, cs2 , Length_Limit )|與strcat(cs1, cs2)相同, 取cs2字串Length_Limit個來串接在cs1後方 | ```cpp= char c[]="abcdef",C[]="ABCDEF"; strncat(c, C , 3); cout<<c; ``` ``` abcdefABC ``` --- | Function名稱 | 說明 | | ------------ | ---- | | strncmp(cs1, cs2 , Length_Limit )|與strncmp(cs1, cs2)相同, 取cs2字串Length_Limit個 跟 取cs1字串Length_Limit個來比較| ```cpp= char c[]="abcdef",C[]="abcDEF"; cout<<strncmp(c, C , 3); ``` ``` 0 //比較的結果為相同 ``` --- ## cstring的輸入法 : cin、cin.getline()、get() 想輸入值到cstring變數時,有多種不同的輸入方法。 ### cin 在輸入cstring變數時可以直接寫cin輸入即可,不需要像輸入陣列那般,使用for迴圈來一一輸入值,當然,cin要注意的是,**只要輸入的字串中間有空格(包括`\n`、`\t`),就會停下**。 --- ```cpp= char b3[]=""; cin>>b3; cout<<b3; ``` **Input:** ``` abc ``` **Output:** ``` abc ``` --- cin輸入的字串,會停在空格之前。 ```cpp= char b[]=""; cin>>b; cout<<b; ``` **Input:** ``` Hello! everyone ``` **Output:** ``` Hello! ``` --- ### cin.getline 可以在輸入cstring時,輸入包含空格符號的字串,遇到換行符號會停下。 - cin.getline(cstring_variable,限制輸入的長度) 當然你也可以宣告長度後,在給陣列值,不過如果出現以下程式碼的情況,就需要留意一下。 ```cpp= char b[3]=""; cin.getline(b,3); //限制只能輸入三位 cout<<b; ``` **Input:** ``` 123 ``` 雖然我們長度設為3,但別忘了電腦在處理cstring時,是去尋找`'\0'`的位置在哪,只要找到那個位置,即是知道這裡是陣列的尾端。因此在輸入cstring時需預留一個空位給電腦,它會自動幫你加在值的最尾端。這也是為什麼我們輸入了三個值`123`,但輸出的結果卻只有`12`。 實際上為`12`+`\0`。 **Output:** ``` 12 ``` --- ### cin.get() 會一個一個符號讀使用者輸入的值,可接受空格、`\n`、`\t`。如果讀取成功會回傳非0的值,失敗則回傳0。 - cin.get(cstring_variable) ```cpp= char c; cin.get(c); cout<<c; ``` **Input:** ``` A ``` **Output:** ``` A ``` --- - cin.get(cstring_variable,長度) 限制讀幾個字符 ```cpp= char c[20]; cin.get(c,5); cout<<c; ``` **Input:** ``` 1234567890 ``` 第五個讀入的為'\0' **Output:** ``` 1234 ``` - cin.get(cstring_variable,長度,終止符號) 讀20個字符,但一讀到字符5就停下,字符5並不會被讀入。 ```cpp= char c[20]; cin.get(c,20,'5'); cout<<c; ``` **Input:** ``` 1234567890 ``` **Output:** ``` 1234 ``` --- ## 將cstring轉換成數字 需使用<span style="color:red;"><font size="4">cstdlib </font></span>函式庫,若我們想將cstring的變數轉換成數字,會用到以下三種作法,差別在資料型態的不同: - atoi cstring-to-**int** ```cpp= char c[20]="123456"; int num = atoi(c); cout<<num; ``` - atol cstring-to-**long** 如果數字太大難以用int來儲存,那你可以考慮使用long。 - atof cstring-to-**double** ```cpp= double y = atof("12.37"); ``` # ------------------------------------------- # String string可直接透過+來串接,+號左方的字串會在前面,+號右方的字串會在後面。 ```cpp= string s,hi="hi",everyone="everyone"; s=hi+" "+everyone; cout<<s; ``` **Output:** ``` hi everyone ``` 但如果將+號左右變數的位置對調 ```cpp= string s,hi="hi",everyone="everyone"; s=everyone+" "+hi; cout<<s; ``` **Output:** ``` everyone hi ``` --- 我們可以把string想成是由多個char array組成的,因此string可以像陣列那般用index來取值。 **範例** ```cpp= string s="Hi"; cout<<s[0]<<endl<<s[1]; ``` **Output:** ``` H i ``` ## string輸入法 ### cin 在string用cin大家應該很熟悉,cin會在遇到空格、換行、tab時停下 ### getline 可以讀空格,遇到換行跟tab會停下。 **要注意的是,如果是string就要使用getline,而上面所介紹的cin.getline就專門用在cstring。** - getline(istream& ,string_variable) ```cpp= string s; getline(cin,s); cout<<s; ``` - getline(istream& ,string_variable,停止符號) 終止符號不會被讀進去。 以下範例為輸入一字串遇到字符1就停下,且字符1不會被讀進去。 ```cpp= string s; getline(cin,s,'1'); cout<<s; ``` **Input:** ``` 97531 ``` **Output:** ``` 9753 ``` ## string才可使用的函示庫 需要使用到<span style="color:red;"><font size="4">string </font></span>函式庫。該函式庫的function不能給cstring使用喔! 請務必記得引入函式庫: ```cpp= #include<string> ``` --- | Function名稱 | 說明| | ------------ | ----------------| | str[i] | 取str字串的第i個值,類似陣列取值 | | `str.at(i)` | 如同陣列取值str[i],取str陣列的第i個位置,at()會先檢查index是否合理,較str[i]直接取值更安全。 | |str.substr(position,length)|擷取position後的length個str字串。position跟index一樣,從0開始| ### `str[i]` 與 `str.at(i)` 的差異 `str[i]` 不會做 out_of_range 的確認,`str.at(i)` 會,可以試著把以下兩段程式碼分別執行,觀看結果。 #### `str.at[i]` ```cpp= // 我們都知道 a 的索引值不可能到 8 string a = "hello"; cout << a.at(8); ``` ![](https://i.imgur.com/ITzHtRH.png) #### `str[i]` ```cpp= // 我們都知道 a 的索引值不可能到 8 string a = "hello"; cout << a[8]; ``` ![](https://i.imgur.com/ZGmJ1tC.png) ### str.substr() ```cpp= string s="123456789"; cout<<s.substr(0,3); ``` **Output:** ``` 123 ``` --- | Function名稱 | 說明 | | ------------ | ---------------------------------------------------------------------------------------------- | | str.length() | str字串的長度 | | str.empty() | **回傳bool值**。檢查str這個字串是否為空字串,是空字串回傳**true**,不是空字串則回傳**false**。 | | str.insert(pos,str2)|插入str2字串在str的第pos個位置| ```cpp= string num="1234",s="abcd"; cout<<num.insert(0,s); ``` **Output:** ``` abcd1234 ``` --- | Function名稱 | 說明 | | ------------ | ---- | | str.erase(pos,length)| 從str字串的第pos個位置移除length個| ```cpp= string num="1234"; cout<<num.erase(0,2); ``` **Output:** ``` 34 ``` --- | Function名稱 | 說明 | | -------------- | --------------------------------------------------------------------------------------------------------------- | | str.find(str1) | 在str字串找str1這個字串的位置,如果有找到則會回傳位置,位置的初始為0。如果沒有找到,則會回傳最大整數string::npos | | str.find(str1,pos)|和str.find(str1)的功能相同,差在是從第pos個位置開始尋找| - 找到的情況:point_down: ```cpp= string s="hello"; cout<<s.find("h"); ``` **Output:** ``` 0 ``` - 沒找到的情況:point_down: ```cpp= string s="hello"; cout<<s.find("0"); ``` **Output:** ``` 18446744073709551615 ``` * 重複的字一律以前面的優先 ```cpp= string s="hello"; cout<<s.find("l"); ``` **Output:** ``` 2 ``` 索引值(index) 為 2 的位置是第一個 `l` 所在的位置,那如果想要找第二個 `l` 怎麼辦?,**必須從第一個 `l` 的索引值加 1 開始往下搜尋** `l` 這個字。像下面這樣。 ```cpp= int main(){ string s="hello"; int pos = 0; pos = s.find("l"); cout << pos << endl; // the first `l` pos = s.find("l", pos+1); cout << pos; // the second 'l' return 0; } ``` **Output:** ``` 2 3 ``` --- | Function名稱| 說明| | --- | ---- | |str.find_first_of(str1)|str1最先出現在str字串的位置| | str.find_first_of(str1,pos) | 從第pos個位置開始找,str1最先出現在str字串的位置 | 雖然`l`有兩個,但只會回傳最先找到的位置。 `s[0]=h` `s[1]=e` `s[2]=l` `s[3]=l` ```cpp= string s="hello"; cout << s.find_first_of("l"); // 印出 2 cout << s.find_first_of("l", 1); // 印出 2 cout << s.find_first_of("l", 2); // 印出 2 cout << s.find_first_of("l", 3); // 印出 3 cout << s.find_first_of("l", 4); // 印出 string::npos ``` --- | Function名稱| 說明| | --- | ---- | |str.find_first_not_of(str1)|最先出現不是str1字串在str字串的位置| | str.find_first_not_of(str1,pos) | 從第pos個位置開始找,回傳最先出現不是str1字串在str字串的位置 | 最先出現不是a的位置是最左邊的b,位置是2。 `s[0]=a` `s[1]=a` `s[2]=b` ```cpp= string s="aabbbcc"; cout<<s.find_first_not_of("a"); ``` **Output:** ``` 2 ``` --- ## 如何將string 轉換成cstring - `string_variable.c_str();` ``` s.c_str() ``` 可將s這個string型態的變數轉成cstring **以下範例為:** 先將s這個string字串轉換成cstring後再複製(等同於assign功能)給cs這個cstring,最後輸出cs。 ```cpp= #include <iostream> #include <string> #include <cstring> using namespace std; int main(){ string s="aabbbcc"; char cs[]="123456"; strcpy(cs,s.c_str()); cout<<cs; return 0; } ``` **Output:** ``` aabbbcc ``` --- # [<span style="color:red">超級重要!</span>]cstring跟string差異 | 作法 | cstring | String | |:----------------:|:-----------------------------:|:----------------------:| | assign | strcpy() | = | | 輸入法 | cin、cin.getline()、cin.get() | cin、getline()、gets() | | 串接 | strcat() | + | | 比較字串是否相等 | strcmp() | == | | 回傳長度 | strlen() | string.length() | |轉換型態|**cstring-to-整數:** atoi、atof、atol| **string-to-cstring:** string.c_str() | --- # [<span style="color:red">期中/期末考必考觀念!</span>]字串產生的緩衝區問題 ## cin 與 getline 交互使用產生的緩衝區問題 學到字串這一章必須和大家討論的問題之一就是非常有名的「緩衝區」問題。在了解緩衝區之前我們先看一個例子。 ```cpp= int main(){ string a = "", b = ""; cin >> a; // 假設我們「想」輸入 hello 後按下 enter cout << "string a: " << a << endl; getline(cin, b); // 假設我們「想」輸入 world 後按下 enter cout << "string b: " << b << endl; return 0; } ``` 閱讀程式碼,我們「應該」會印出如下內容。 ``` string a: hello string b: world ``` 但很難過的,程式碼輸出如下。 ![](https://i.imgur.com/s3joQdx.png) 相信非常令人納悶,這是為甚麼呢? 原因就是**因為緩衝區內的內容讓我們沒有輸入字串進字串 b**。 ## 解釋緩衝區 在講到問題的關鍵之前,請仔細閱讀程式碼,第 3 行使用 cin 來做字串輸入,第 5 行使用 getline() 來做字串輸入。 而透過上面的筆記我們都知道 cin 沒有辦法讀入空白、換行等符號,所以當我們第一次輸入 hello 後按下 enter,真的被存進字串 a 的內容是 hello,而 enter 我們都知道它代表 `\n` 換行字串,他其實也是字串,因為 cin 不吃,所以 `\n` 就被丟到了所謂的緩衝區去了。 意思就是說,對於沒有讀到的字串,他不會不見,而是會被丟去緩衝區佔存起來,直到有人來把它拿走。 接著第 5 行又要執行輸入了,而 getline 可以接收空白字串,這個時候按照一般邏輯,我們應該可以打字輸入字串 b,但其實在輸入前程式會先去看緩衝區內有沒有東西,此時的緩衝區有一個 \n 符號,所以這個換行符號會直接被 getline 先做讀入,因此我們就沒辦法輸入字串了,因為已經執行 \n。 照常理我們都知道,做字串輸入的時候一旦按下 enter 表示輸入結束,所以這個緩衝區的換行符號直接做了 enter,所以字串 b 我們根本沒有輸入。 根據上面的說明,此時的字串 b 裡面存了甚麼呢? **答案是甚麼都沒有存。** ## 解決緩衝區問題 那針對這個問題怎麼解決呢?我們必須使用 cin.ignore() 這個 function 來清空緩衝區,對於上面的例子,我們只需在 cin 結束後下一行,先清空緩衝區即可。 ```cpp= int main(){ string a = "", b = ""; cin >> a; // 我們輸入 hello 後按下 enter cout << "string a: " << a << endl; cin.ignore(); // 清空緩衝區內的 \n 符號 getline(cin, b); // 我們輸入 world 後按下 enter cout << "string b: " << b << endl; return 0; } ``` ![](https://i.imgur.com/SudBh2g.png) 所以針對緩衝區的問題,只要 cin 與 getline 交互使用(一定是 cin 先,再 getline),就一定要來清空緩衝區,否則會有輸入上的問題。 ## 比較奇特處理緩衝區字串的方法 ### 利用 cin.get() ```cpp= int main(){ string a = "", b = ""; cin >> a; // 我們輸入 hello 後按下 enter cout << "string a: " << a << endl; cin.get(); // cin.get() 會取一個字元,此時緩衝區是一個 \n // 所以會把 \n 取走 getline(cin, b); // 我們輸入 world 後按下 enter cout << "string b: " << b << endl; return 0; } ``` ### 多 getline 一次 ```cpp= int main(){ string a = "", b = ""; cin >> a; // 我們輸入 hello 後按下 enter cout << "string a: " << a << endl; getline(cin, b); // 因為 getline 可以取走緩衝區的 \n getline(cin, b); // 我們輸入 world 後按下 enter cout << "string b: " << b << endl; return 0; } ``` ## 更加認識 getline() 想必 \n 造成的緩衝區問題還是困擾著你們,想必有人會問,那如果連續 getline 兩次,第二個 getline 不是仍舊會吃到第一個字串輸入時的 \n 嗎? 答案是不會,原因是因為 getline 預設是以 `\n` 作為終止字符,而 getline 的終止字符是不會被放進緩衝區的,他會直接被捨棄,但是 cin 會後按下 enter 的 \n 會被放進緩衝區。 所以 getline 其實有一個 overloading function `getline(輸入類型, 輸入字串變數, 終止字符)` 預設的 getline 他的終止字符是 `\n`**(字元型別)**,所以我們每次作如下的輸入時, `getline(cin, a)` 其實他都是 `getline(cin , a, '\n')`。 那至於你問為什麼 getline 的終止字符不會被放進緩衝區,但 cin 會呢? 這就跟 C++ 背後的運作邏輯有關,我們就不討論。 --- # Reference * [C++ 中的 cin/cin.get()/cin.getline()/getline/getchar()](https://www.itread01.com/content/1543422437.html)