---
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);
```

#### `str[i]`
```cpp=
// 我們都知道 a 的索引值不可能到 8
string a = "hello";
cout << a[8];
```

### 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
```
但很難過的,程式碼輸出如下。

相信非常令人納悶,這是為甚麼呢?
原因就是**因為緩衝區內的內容讓我們沒有輸入字串進字串 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;
}
```

所以針對緩衝區的問題,只要 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)