---
# System prepended metadata

title: 【C++】競程筆記：字串處理
tags: [程式設計, C++, 競程, 程式語言, CP]

---

# 【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;
    }
};
```

