【C++】競程筆記:字串處理

本筆記僅個人學習用途,斟酌參考。

額…我發現我的字串處理實在是太爛了,所以做這篇來挽救一下我的字串題目。

String 宣告方式

有四種:

1
2
3
4
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(),可以讀所有字元,包含換行字元。

基本操作

取得長度

1
2
3
string s = "Hello";
int len = s.length(); // 或 s.size(),兩者通用
// len 為 5

字串串接(+

1
2
3
4
string a = "Happy";
string b = "Coding";
string c = a + " " + b + "!";
// c 變成 "Happy Coding!"

存取與修改字元

像陣列一樣使用 []

1
s[0] = 'h'; // 變成 "hello" (大小寫轉換常用, 只要更改第一個字元即可)

比較

直接使用 ==, !=, <, > 等運算子(依照字典序比較)。

1
2
if (s == "hello") { ... }
if ("apple" < "banana") { ... } // true

核心功能

尋找 find()

找子字串或字元的位置。如果找不到,會回傳特殊常數 string::npos。

1
2
3
4
5
6
7
8
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(開始位置, 長度)

需注意:第二個參數是「長度」,不是結束位置。

1
2
3
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(開始位置, 長度, "新字串");
1
2
3
4
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()

1
2
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
1
2
3
4
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():排序
1
2
3
string s = "dbca";
reverse(s.begin(), s.end()); // 反轉 -> "acbd"
sort(s.begin(), s.end()); // 排序 -> "abcd"

字串切割(stringstream)

需要引入 #include <sstream>

當題目輸入是一整行字串,包含未知的空白數量,要把單字一個個切出來時,就可以用 sstream

1
2
3
4
5
6
7
8
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) 分別擷取「序號」跟「實數」。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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)不需要轉換,直接輸出。

範例程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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() 來反轉字串。

範例程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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(),有可能會有負號。

範例程式碼:

1
2
3
4
5
6
7
8
9
10
11
#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 判斷目前是不是一個單字,如果碰到非字母字元,則表示已經跳到下個字去了。

範例程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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 字串變數串接單字。

範例程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
};