# 【C++】競程筆記:字串處理 [TOC] 本筆記僅個人學習用途,斟酌參考。 額...我發現我的字串處理實在是太爛了,所以做這篇來挽救一下我的字串題目。 ## String 宣告方式 有四種: ```cpp= string s1; // 空字串 "" string s2 = "Hello"; // 初始化為 "Hello" string s3(s2); // 複製 s2 -> "Hello" string s4(5, 'A'); // 重複字元 -> "AAAAA" ``` s1 什麼都沒寫,僅宣告的話就是空字串。 s2 賦值 `"Hello"` 字串,則表示 s2 初始化為 `"Hello"`。 `s3(s2)` 括號 `s2` 寫法就是 s3 複製 s2 的 `"Hello"`。 `s4(5, 'A')`,前面是次數,後面是字元,表示重複 5 次 `'A'`。 ## 輸入方式 輸入字串的方式有一行一行讀跟整行讀。 一行一行讀就是 `cin`,整行讀就是 `getline(cin, line)`。 使用 `getline(cin, line)` 有個陷阱: 「如果前面已經有寫 `cin >> s` 的話,接下來又用 `getline(cin, s)`,那麼 `cin` 留下的換行符號會被 `getline` 讀去,導致看起來像是『跳過了一次輸入』。」 這個解法就是在兩者之間加一行 `cin.ignore()` 來吃掉那個換行符號。 最後還有一個就是 `cin.get()`,可以讀所有字元,包含換行字元。 ## 基本操作 ### 取得長度 ```cpp= string s = "Hello"; int len = s.length(); // 或 s.size(),兩者通用 // len 為 5 ``` ### 字串串接(`+`) ```cpp= string a = "Happy"; string b = "Coding"; string c = a + " " + b + "!"; // c 變成 "Happy Coding!" ``` ### 存取與修改字元 像陣列一樣使用 `[]`。 ```cpp= s[0] = 'h'; // 變成 "hello" (大小寫轉換常用, 只要更改第一個字元即可) ``` ### 比較 直接使用 ==, !=, <, > 等運算子(依照字典序比較)。 ```cpp= if (s == "hello") { ... } if ("apple" < "banana") { ... } // true ``` ## 核心功能 ### 尋找 `find()` 找子字串或字元的位置。如果找不到,會回傳特殊常數 string::npos。 ```cpp= string s = "Hello World"; size_t pos = s.find("World"); // 回傳 W 的索引值 (6) if (pos != string::npos) { cout << "Found at: " << pos << endl; } else { cout << "Not found" << endl; } ``` ### 擷取子字串 `substr()` 語法:`s.substr(開始位置, 長度)` 需注意:第二個參數是「長度」,不是結束位置。 ```cpp= string s = "2023-12-25"; string year = s.substr(0, 4); // 從索引 0 開始,抓 4 個字 -> "2023" string month = s.substr(5, 2); // 從索引 5 開始,抓 2 個字 -> "12" ``` ### 插入、刪除、取代 * 插入:`s.insert(位置, "字串")`; * 刪除:`s.erase(開始位置, 長度)`; * 取代:`s.replace(開始位置, 長度, "新字串")`; ```cpp= string s = "I like C++"; s.insert(7, "modern "); // "I like modern C++" s.erase(2, 5); // 從索引 2 開始刪 5 個字 -> "I modern C++" s.replace(9, 3, "Python"); // 從索引 9 開始取代 3 個字 -> "I modern Python" ``` ## 型態轉換 ### 數字轉字串 用 `to_string()`。 ```cpp= int num = 123; string s = to_string(num); // "123" ``` ### 字串轉數字 有四種方法: * stoi():String to Integer * stoll():String to Long Long * stof():String to Float * stod():String to Double ```cpp= string a = "123"; int aa = stoi(a); string b = "123.123"; double bb = stod(b); ``` 二進位字串轉整數:`stoi(line, nullptr, 2)`,當中最後面的 2 參數是 line 的進位制。 ## 搭配 `<algorithm>` 跟 `<sstream>` ### 字串反轉與排序 使用這些方法前需要引入 `#include <algorithm>`,而且要用 iterator: - reverse():反轉元素。 - sort():排序 ``` string s = "dbca"; reverse(s.begin(), s.end()); // 反轉 -> "acbd" sort(s.begin(), s.end()); // 排序 -> "abcd" ``` ### 字串切割(stringstream) 需要引入 `#include <sstream>`。 當題目輸入是一整行字串,包含未知的空白數量,要把單字一個個切出來時,就可以用 `sstream`。 ```cpp= string line = " This is a pen "; stringstream ss(line); // 建立一個 string stream string word; while (ss >> word) { // 像 cin 一樣讀取,會自動跳過空白 cout << "[" << word << "]"; } // 輸出:[This][is][a][pen] ``` ## `<cctype>` `#include <cctype>` 引入這個標頭檔。 這邊有幾個好用的函式可以用: - `isalnum()`:判斷是否為英文字母跟數字。 - `isalpha()`:判斷是否為英文字母。 - `isspace()`:判斷是否有空白。 - `isdigit()`:判斷是否為字母。 - `islower()`:判斷是否為小寫字母。 - `isupper()`:判斷是否為大寫字母。 - `tolower()`:將字元轉成小寫字母。 - `toupper()`:將字元轉成大寫字母。 ## 練習題 ### ZJ d018. 字串讀取練習 Problem Source:https://zerojudge.tw/ShowProblem?problemid=d018 用 `stringstream` 讀取,至於有 `:` 的部分,可以先用 `int pos = s.find(':')` 找到字元的位置,然後再用 `s.substr(pos + 1)` 跟 `s.substr(pos - 1)` 分別擷取「序號」跟「實數」。 ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ string line; while (getline(cin, line)){ stringstream ss(line); string s; double n; double sum = 0; while (ss >> s){ int pos = s.find(':'); int number = stoi(s.substr(pos - 1)); n = stod(s.substr(pos + 1)); if (number % 2 == 1){ sum += n; } else{ sum -= n; } } cout << sum << '\n'; } } ``` ### Uva 10082 - WERTYU PDF Source:https://onlinejudge.org/external/100/10082.pdf Uva Online Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=1023 Zerojudge:https://zerojudge.tw/ShowProblem?problemid=c054 題目的意思是輸入的字元都是在 QWERTY 鍵盤上往右錯了一位的結果,然後需要將它往左移回一位。 解題思路: - 建立對照表:鍵盤上的所有字元都存進一個字串。 - 尋找、替換:讀取輸入的每一個字元,在對照表中找到該字元的位置,然後輸出它左邊那一個字元。 - 特殊字元處理:空白字元(Space)不需要轉換,直接輸出。 範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ string keyboard = "`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./"; char c; while (cin.get(c)){ int pos = keyboard.find(c); if (pos != string::npos){ cout << keyboard[pos-1]; } else{ cout << c; } } } ``` ### LeetCode 125. Valid Palindrome Problem Source:https://leetcode.com/problems/valid-palindrome/description/ 迴文判斷。 解題思路: 觀察題目,會發現要將字元改小寫,再判斷迴文才可以。 建立 a 跟 b 字串,a 作為原字串,b 是反轉後的字串,要比較兩者是否相同。 針對字母字元跟數字字元這兩個加進去 a 裡面,之後可以用 `reverse()` 來反轉字串。 範例程式碼: ```cpp= class Solution { public: bool isPalindrome(string s) { string a; for (const char& c : s){ if (isalpha(c) || isdigit(c)){ a += tolower(c); } } string b = a; reverse(b.begin(), b.end()); return a == b; } }; ``` ### ZJ a065. 提款卡密碼 Problem Source:https://zerojudge.tw/ShowProblem?problemid=a065 懂 ASCII Code 就知道怎麼做。基本上就是字元 - 字元,會得到之間的差值,記得用 `abs()`,有可能會有負號。 範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ string line; getline(cin, line); for (int i = 0; i < line.size() - 1; ++i){ cout << abs(line[i + 1] - line[i]); } } ``` ### Uva 494 - Kindergarten Counting Game PDF Source:https://onlinejudge.org/external/4/494.pdf Uva Online Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=435 Zerojudge:https://zerojudge.tw/ShowProblem?problemid=a011 解題思路:運用 `isalpha()` 判斷是否為字母,並建立一個 `bool inWord = false` 判斷目前是不是一個單字,如果碰到非字母字元,則表示已經跳到下個字去了。 範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ string line; while (getline(cin, line)){ int count = 0; bool inWord = false; for (const char& c : line){ if (isalpha(c)){ if (!inWord) inWord = true; } else{ if (inWord){ count++; inWord = false; } } } if (inWord) count++; cout << count << '\n'; } } ``` ### LeetCode 151. Reverse Words in a String Problem Source:https://leetcode.com/problems/reverse-words-in-a-string/description/ 解題思路:運用 stringstream,將每個單字推到 vector 裡面,之後用 for 從 vector 最右邊開始存取,並用 result 字串變數串接單字。 範例程式碼: ```cpp= class Solution { public: string reverseWords(string s) { vector <string> lines; stringstream ss(s); string line, result; while (ss >> line) lines.push_back(line); if (lines.empty()) return ""; result = lines.back(); for (int i = lines.size() - 2; i >= 0; --i){ result += " " + lines[i]; } return result; } }; ```