---
# System prepended metadata

title: 淺談 CRLF 對程式競賽的影響
tags: [tutorial]

---

# 淺談 CRLF 對程式競賽的影響

## 前言

在某些程式競賽中 (~~像是學科區賽~~)，你可能會遇到一些需要使用 `std::getline` 才能解的題目。此時，了解 `\r\n` (CRLF) 這個換行符號是如何影響程式，並導致錯誤答案 (Wrong Answer, WA) 是非常重要的。

## Windows 與 Unix 換行符號的差異

在文字檔中，換行並不是由一個單一的字元表示，而是由一組特定的字元組合而成。這些字元的由來與早期的打字機有關：

* **CR (Carriage Return)**: 將游標移回行首 (`\r`)
* **LF (Line Feed)**: 將游標往下移動一行 (`\n`)

這種設計導致了不同作業系統在處理換行時，使用了不同的組合：

* **Windows 系統**：延續了打字機的傳統，使用 **CRLF (`\r\n`)** 組合來表示換行。
* **Unix/Linux/macOS 系統**：為了檔案大小和簡潔性，只使用 **LF (`\n`)** 來表示換行。

這種差異是導致跨平台檔案格式問題的根源。

## std::getline 函式

根據 `cppreference` 的文件，`std::getline` 函式有以下幾個參數：

1.  `input`：要讀取的輸入流（通常為 `std::cin`）。
2.  `str`：用於存放讀取資料的字串。
3.  `delim`：分隔字元（通常省略，預設為換行符號 `'\n'`）。

`std::getline` 的運作機制是持續讀取輸入流中的字元，直到遇到檔案結尾 (EOF) 或指定的分隔字元 (`delim`) 為止。當它讀到 `delim` 時，會將該字元從輸入流中取出並丟棄，**但不會將它存入字串中**。

```cpp=
// getline 使用範例
string s;
getline(cin, s);
```

## getline + CRLF = ~~NCUE~~

> 當測試資料使用 Windows 的 CRLF 格式時，`getline` 會產生一些常見的陷阱。

### 陷阱 1：`cin >> ...` 後的緩衝區問題

在使用 `cin >>` 讀取非字串資料（如整數）後，輸入緩衝區中會留下換行符號。如果緊接著使用 `getline`，它會讀到這個換行符號，並直接回傳一個空字串，導致輸入錯誤。

有些人會使用 `cin.get()` 來解決這個問題，但它每次只會讀取一個字元。當面對 **CRLF (`\r\n`)** 換行時，`cin.get()` 只會清掉 `\r`，而 `\n` 仍會留在緩衝區中，導致同樣的問題。

正確的解決方法是使用**有設定引數的** `cin.ignore` 或者用兩次 getlien 來清除緩衝區，確保所有殘留的換行字元都被移除。

```cpp
// cin.ignore() 預設只會清一個字元，需要改引數 (這裡直接改成無上限)
cin.ignore(numeric_limits<streamsize>::max(), '\n');

// 或者直接用 getline 將髒東西存在一個暫時的字串
string temp;
getline(cin, temp);
````

:::spoiler 錯誤範例

```cpp=
int n;
cin >> n;
string s;
cin.ignore(); // or cin.get()
getline(cin, s);
```

:::

### 陷阱 2：字串末尾多出的 `\r`
> 這是筆者在學科區賽踩到的大坑。

`getline` 預設只將 `\n` 視為換行符號。當它處理 CRLF 格式的檔案時，會將 `\n` 丟棄，但**會把 `\r` 讀進字串中**。這會導致你讀取到的字串末尾多出一個 `\r` 字元。

例如，如果測試資料的內容是 `12345\r\n`，`getline` 讀取後得到的字串實際上是 `"12345\r"`。如果你沒有妥善處理，程式在進行字串操作或數值轉換時，很可能因此產生錯誤並導致 WA。

為了解決這個問題，你需要在讀取字串後，手動將結尾的 `\r` 移除。

```cpp=
// 推薦的通用寫法
string s;
getline(cin, s);
while (!s.empty() && s.back() == '\r') {
    s.pop_back();
}
```

## 出題者如何避免這些問題
> 如果你是出題者，則可以採取以下措施來避免噁心到選手。

### 不要出需要用 getline 的題目
一個好的題目應該專注於演算法的考察，而不是輸入輸出的處理細節。

### 統一換行符號格式

在撰寫或生成測試資料時，請確保所有檔案都使用 LF 結尾。這可以透過以下方式實現：

  * 使用專業的文字編輯器（如 VS Code、Notepad++），它們允許你將檔案儲存為 LF 格式。
  * 在 Linux 環境下生成測試資料，因為 Linux 預設使用 LF 換行。

若是使用 python 生測資時，可以透過設定 `newline=''` 來關閉 CRLF 的轉換。
```python=
with open("output.txt", "w", newline='') as f:
    f.write("Hello, world!\n")
    f.write("This is a new line.\n")
```

### 使用 Codeforces Polygon

`polygon` 是一個很好用的出題平台，它運行在 Linux 環境中，並內建了 Tests well-formed 的功能。即使你上傳了 CRLF 格式的檔案，`polygon` 也會自動將其轉換為 LF，從而確保了測試資料不會有髒東西。

## 參考資料

  * [換行字元 CR LF - hackmd](https://hackmd.io/@mike-liu/By32a6lyi)
  * [cppreference](https://en.cppreference.com/index.html)