【C++ 筆記】C++ 與 C style string
===
[TOC]
C-style string
---
先從老祖宗 C 語言講起好了。
C-style string 就是字元陣列所組成的,最後結尾用 `null`(`\0`)表示字串結束。
如:
```cpp=
char a[] = "Hello"; // 等於 char a[] = {'H', 'e', 'l', 'l', 'o', '\0'};
```
這種字元陣列需要搭配 `<cstring>` 標頭檔引入 C 語言版本的字串函式庫。
裡面常見的操作如下:
| 函式名 | 功能 |
| ------------------ | --------------- |
| `strlen(s)` | 回傳字串長度(不含 `\0`) |
| `strcpy(dst, src)` | 字串複製 |
| `strcmp(s1, s2)` | 字串比較(回傳 0 相同,非 0 不同) |
| `strcat(dst, src)` | 字串串接 |
dst 就是 destination,意為目的地;src 就是 source,意為來源。
`strcpy(dst, src)` 就是將 src 複製到 dst。
`strcat(dst, src)` 就是將 src 串接於 dst 上。
範例:
```cpp=
#include <iostream>
#include <cstring>
using namespace std;
int main() {
char str1[50];
char str2[] = "World";
// 1. strcpy() 複製字串
strcpy(str1, "Hello");
cout << "str1 after strcpy: " << str1 << endl;
// 2. strcat() 串接字串
strcat(str1, " "); // 加空格
strcat(str1, str2); // 將 str2 加到 str1 後面
cout << "str1 after strcat: " << str1 << endl;
// 3. strlen() 測量字串長度(不含 \0)
cout << "Length of str1: " << strlen(str1) << endl;
// 4. strcmp() 比較兩個字串
if (strcmp(str1, "Hello World") == 0) {
cout << "str1 與 \"Hello World\" 相同" << endl;
} else {
cout << "str1 與 \"Hello World\" 不相同" << endl;
}
return 0;
}
```
Output:
```
str1 after strcpy: Hello
str1 after strcat: Hello World
Length of str1: 11
str1 與 "Hello World" 相同
```
### Strength and weakness
---
優點:
* 效能高,無額外抽象(Abstract)。
* 與低階記憶體操作無縫整合。
* 在嵌入式系統、C 接口中常見。
缺點:
* 安全性低,常因未妥善管理記憶體導致緩衝區溢位(buffer overflow)。
* 很麻煩,沒有 C++ style string 來的方便。
* 無法支援動態擴充長度,一個字串一旦寫好就固定長度了。
C++ style string
---
C++ style string 是由類別所構成的,使用前須引入標頭檔 `<string>`。
要建立一個字串,如下:
```cpp=
#include <string>
string name = "LukeTseng";
```
可以用中括號存取某個字元,索引(位置)起始值一樣從 0 開始,如上範例的 `name[0]` 就會取得 `'L'` 字元。
```cpp=
#include <iostream>
#include <string>
using namespace std;
int main(){
string name = "LukeTseng";
cout << name[0];
return 0;
}
```
Output:
```
L
```
另外 C++ style string 是可以更新字串的,不像 C-style string 那麼死板:
```cpp=
#include <iostream>
#include <string>
using namespace std;
int main(){
string name = "LukeTseng";
name = "王奕翔醜男"; // 這絕對不是在說誰
cout << name;
return 0;
}
```
Output:
```
王奕翔醜男
```
也可以更改特定字元:
```cpp=
#include <iostream>
#include <string>
using namespace std;
int main(){
string name = "LukeTseng";
name[1] = 'i';
cout << name;
return 0;
}
```
Output:
```
LikeTseng
```
### 相關函式
---
| Function | Description |
|---------------|-------------|
| `length()` | 回傳字串長度。 |
| `swap(a, b)` | 交換兩個字串。 |
| `size()` | 查找字串的大小。 |
| `resize()` | 將字串長度調整為給定的字元數。 |
| `find()` | 尋找傳入參數的字串。 |
| `push_back(c)` | 把字元 c 推送到字串的結尾。 |
| `pop_back(c)` | 移除字串中最後一個字元 c。|
| `clear()` | 清空字串。 |
| `strncmp(const char *str1, const char *str2, size_t count)` | 最多比較兩個字串的前 num 個位元組。 |
| `strncpy(char *dest, const char *src, size_t n)` | 該函式與 `strcpy()` 函式類似,不同之處在於最多複製 src 的 n 個位元組。 |
| `strrchr(char* str, int chr)` | 定位字串中某個字元的最後出現的位置。 |
| `strcat(dest, src)` | 把來源字串 src 的副本附加到目標字串 dest 的結尾。 |
| `replace()` | 把區間 `[first,last)` 中每個等於舊值的元素替換為新值。 |
| `substr()` | 從給定字串中建立子字串。 |
| `compare()` | 比較兩字串並以整數形式回傳結果。 |
| `erase()` | 刪除字串的某個部分。 |
| `rfind()` | 查找字串最後一次出現的位置。 |
表格來源:https://www.geeksforgeeks.org/strings-in-cpp/
find() 有多種語法:
```cpp=
s.find(sub, pos); // For substring 用於子字串
s.find(sub, pos, n); // For n character of sub 用於字串的 n 個字元
s.find(c, pos); // For character 用於字元
```
以上的第二種語法僅適用於 C-style string。
`find()` 範例:
```cpp=
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "The quick brown fox jumps over the lazy dog.";
// 尋找子字串 "fox"
size_t pos = str.find("fox");
if (pos != string::npos) {
cout << "\"fox\" found at position: " << pos << endl;
} else {
cout << "\"fox\" not found." << endl;
}
// 尋找字元 'z'
pos = str.find('z');
if (pos != string::npos) {
cout << "'z' found at position: " << pos << endl;
} else {
cout << "'z' not found." << endl;
}
// 尋找不存在的子字串
pos = str.find("cat");
if (pos != string::npos) {
cout << "\"cat\" found at position: " << pos << endl;
} else {
cout << "\"cat\" not found." << endl;
}
return 0;
}
```
Output:
```
"fox" found at position: 16
'z' found at position: 37
"cat" not found.
```
`string::npos` 代表找不到字串,回傳值的型態為 `size_t`,通常為其型態的最大值 `4294967295`。
接下來是有關 `strncmp()`, `strncpy()`, `strrchr()`, `strcat()` 的範例:
1. `strncmp()`
`strncmp()` 回傳值:`0`(相等)、`<0`(str1 < str2)、`>0`(str1 > str2)
```cpp=
#include <iostream>
#include <cstring> // strncmp()
using namespace std;
int main() {
const char* str1 = "abcdef";
const char* str2 = "abcxyz";
// 比較前 3 個字元
int result = strncmp(str1, str2, 3);
if (result == 0) {
cout << "前 3 個字元相同" << endl;
} else if (result < 0) {
cout << "str1 小於 str2" << endl;
} else {
cout << "str1 大於 str2" << endl;
}
return 0;
}
```
Output:
```
前 3 個字元相同
```
2. `strncpy(dest, src, n)`
若 `src` 長度小於 `n`,`dest` 會補上 `\0`。
若 `src` 長度大於或等於 `n`,可能不會自動補 `\0`,需手動處理。
```cpp=
#include <iostream>
#include <cstring> // strncpy()
using namespace std;
int main() {
const char* src = "Hello";
char dest[10];
// 複製前 5 個字元到 dest,後面不足的會補 \0
strncpy(dest, src, 10);
cout << "複製後的 dest: " << dest << endl;
return 0;
}
```
Output:
```
複製後的 dest: Hello
```
3. `strrchr(str, ch)`
* `strrchr(str, ch)` 回傳指向 `ch` 最後一次出現位置的指標,找不到回傳 `nullptr`。
* 可用指標運算計算該位置。
```cpp=
#include <iostream>
#include <cstring> // strrchr()
using namespace std;
int main() {
const char* str = "This is a sample sentence.";
// 找出字元 's' 最後一次出現的位置
const char* result = strrchr(str, 's');
if (result != nullptr) {
cout << "最後一個 's' 出現在位置: " << (result - str) << endl;
cout << "從該位置開始的字串為: " << result << endl;
} else {
cout << "找不到字元 's'" << endl;
}
return 0;
}
```
Output:
```
最後一個 's' 出現在位置: 17
從該位置開始的字串為: sentence.
```
4. `strcat(dest, src)`
* `strcat(dest, src)`:將 `src` 字串加到 `dest` 字串尾端。
* `dest` 必須有足夠的空間存放合併後的結果,否則會產生記憶體區段(Segmentation fault)錯誤。
```cpp=
#include <iostream>
#include <cstring> // strcat()
using namespace std;
int main() {
char dest[50] = "Hello, ";
const char* src = "world!";
strcat(dest, src);
cout << "連接後的字串: " << dest << endl;
return 0;
}
```
Output:
```
連接後的字串: Hello, world!
```
以下是有關函式 `replace()`, `substr()`, `compare()`, `erase()`, `rfind()` 的範例:
1. `replace(pos, len, new_str)`
* `str.replace(pos, len, new_str)`:從位置 `pos` 開始,取代長度為 `len` 的部分為 `new_str`。
```cpp=
#include <iostream>
#include <string> // 注意為 <string>
using namespace std;
int main() {
string str = "I love apples";
// 從位置 7 開始,替換 6 個字元為 "oranges"
str.replace(7, 6, "oranges");
cout << "替換後字串: " << str << endl;
return 0;
}
```
Output:
```
替換後字串: I love oranges
```
2. `substr(pos, len)`
* `substr(pos, len)`:從 `pos` 開始擷取長度為 `len` 的子字串。
* 若省略 `len`,則取到字串尾端。
```cpp=
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello, world!";
// 從位置 7 開始擷取 5 個字元
string sub = str.substr(7, 5);
cout << "擷取的子字串: " << sub << endl;
return 0;
}
```
Output:
```
擷取的子字串: world
```
3. `a.compare(b)`
* `a.compare(b)`:
* 回傳 0:a 與 b 相同
* 回傳 <0:a 小於 b(字典序)
* 回傳 >0:a 大於 b。
* 可用來實作排序(自訂排序)或搜尋。
```cpp=
#include <iostream>
#include <string>
using namespace std;
int main() {
string a = "apple";
string b = "banana";
int result = a.compare(b);
if (result == 0) {
cout << "字串相同" << endl;
} else if (result < 0) {
cout << "a < b" << endl;
} else {
cout << "a > b" << endl;
}
return 0;
}
```
Output:
```
a < b
```
4. `erase(pos, len)`
* `erase(pos, len)`:從 `pos` 開始,刪除 `len` 個字元。
* 也可用 `erase(iterator)` 或 `erase(iterator_first, iterator_last)`。
```cpp=
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "0123456789";
// 從位置 3 開始,刪除 4 個字元
str.erase(3, 4);
cout << "刪除後字串: " << str << endl;
return 0;
}
```
Output:
```
刪除後字串: 012789
```
5. `rfind(substring)`
* rfind(substring):回傳子字串最後一次出現的位置。
* 若找不到,則回傳 `string::npos`。
```cpp=
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "one two three two one";
// 搜尋 "two" 最後一次出現的位置
size_t pos = str.rfind("two");
if (pos != string::npos) {
cout << "\"two\" 最後一次出現於位置: " << pos << endl;
} else {
cout << "找不到子字串" << endl;
}
return 0;
}
```
Output:
```
"two" 最後一次出現於位置: 14
```
總結
---
### C-style string
C-style string 是由 C 語言時期所沿用至今的字串表示方式,形式為字元陣列,並以特殊終止字元 `'\0'`(null character)表示字串結尾。
```cpp=
char a[] = "Hello"; // 等價於 {'H', 'e', 'l', 'l', 'o', '\0'}
```
#### 常見的函式
在 C++ 使用時用 `<cstring>` 標頭檔引入。
| 函式 | 說明 |
| ------------------ | ---------------- |
| `strlen(s)` | 傳回字串長度(不包含 `\0`) |
| `strcpy(dst, src)` | 將 src 字串複製到 dst |
| `strcmp(s1, s2)` | 比較兩個字串(相同回傳 0) |
| `strcat(dst, src)` | 將 src 串接於 dst 之後 |
* dst(destination):目標字串(目的地)。
* src(source):來源字串。
#### 優點:
* 效能高,無額外抽象(Abstract)。
* 與低階記憶體操作無縫整合。
* 在嵌入式系統、C 接口中常見。
#### 缺點:
* 安全性低,常因未妥善管理記憶體導致緩衝區溢位(buffer overflow)。
* 很麻煩,沒有 C++ style string 來的方便。
* 無法支援動態擴充長度,一個字串一旦寫好就固定長度了。
---
### C++ style string
C++ style string 是以 `std::string` 類別為核心的字串型態,需引入 `<string>` 標頭檔。
用起來比 C-style string 更方便,也更符合現代化。
範例:
```cpp=
#include <string>
using namespace std;
string name = "LukeTseng";
cout << name[0]; // Output : L
```
也可以更新字串:`name = "abc";`。
也能更新特定字元:`name[1] = 'i';`。
#### 常見函式表
| Function | Description |
|---------------|-------------|
| `length()` | 回傳字串長度。 |
| `swap(a, b)` | 交換兩個字串。 |
| `size()` | 查找字串的大小。 |
| `resize()` | 將字串長度調整為給定的字元數。 |
| `find()` | 尋找傳入參數的字串。 |
| `push_back(c)` | 把字元 c 推送到字串的結尾。 |
| `pop_back(c)` | 移除字串中最後一個字元 c。|
| `clear()` | 清空字串。 |
| `strncmp(const char *str1, const char *str2, size_t count)` | 最多比較兩個字串的前 num 個位元組。 |
| `strncpy(char *dest, const char *src, size_t n)` | 該函式與 `strcpy()` 函式類似,不同之處在於最多複製 src 的 n 個位元組。 |
| `strrchr(char* str, int chr)` | 定位字串中某個字元的最後出現的位置。 |
| `strcat(dest, src)` | 把來源字串 src 的副本附加到目標字串 dest 的結尾。 |
| `replace()` | 把區間 `[first,last)` 中每個等於舊值的元素替換為新值。 |
| `substr()` | 從給定字串中建立子字串。 |
| `compare()` | 比較兩字串並以整數形式回傳結果。 |
| `erase()` | 刪除字串的某個部分。 |
| `rfind()` | 查找字串最後一次出現的位置。 |
表格來源:https://www.geeksforgeeks.org/strings-in-cpp/
find() 有多種語法:
```cpp=
s.find(sub, pos); // For substring 用於子字串
s.find(sub, pos, n); // For n character of sub 用於字串的 n 個字元
s.find(c, pos); // For character 用於字元
```
以上的第二種語法僅適用於 C-style string。
#### `strncmp()`, `strncpy()`, `strrchr()`, `strcat()` 總表
| 函式名稱 | 功能說明 |
| ----------- | ---------------- |
| `strncmp()` | 比較兩字串前 n 個字元 |
| `strncpy()` | 複製字串前 n 個字元到另一字串 |
| `strrchr()` | 找到某字元最後一次出現位置 |
| `strcat()` | 將一字串接到另一字串之後 |
#### `replace()`, `substr()`, `compare()`, `erase()`, `rfind()` 總表
| 函式 | 功能說明 |
| ----------- | --------------- |
| `replace()` | 替換字串中的某段子字串 |
| `substr()` | 擷取子字串 |
| `compare()` | 比較兩字串字典順序 |
| `erase()` | 刪除某段字串 |
| `rfind()` | 搜尋某子字串最後一次出現的位置 |
參考資料
---
[Strings in C++ | GeeksforGeeks](https://www.geeksforgeeks.org/strings-in-cpp/)
[C-style string的處理 - HackMD](https://hackmd.io/@HsuChiChen/c-style-string)
[使用 string](https://openhome.cc/Gossip/CppGossip/string2.html)
[Neutrino's Blog: 入門看完這一篇就夠了,讓你融會貫通使用 C++ 的 std::string 初級篇!](https://tigercosmos.xyz/post/2023/06/c++/std-string-beginner/)