# 淺談 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)