C/C++ IO 基本優化介紹(IO同步取消方法與進階函數使用) #0 | seansie blog
在競程解題時,有些題目會刻意設計大量資料,這時 I/O(輸入輸出)優化就變得格外重要。雖然 C/C++ 相比其他直譯式語言,I/O 效率已經相當不錯,但仍有進一步提升的空間。本文將介紹各種 I/O 優化技巧,從入門的安全方法到進階但可能帶有風險的技巧,讓您在競程中更上一層樓。
基本版本(標配)
在 C++ 中,我們同時擁有來自 C 語言的 scanf
和 printf
,以及 C++ 標準庫提供的 cin
和 cout
。由於 C++ 是 C 的超集,也就是說 C++ 完全兼容 C 的語法和功能,這使得我們可以在 C++ 中自由使用這兩套輸入輸出函式。然而,這樣的便利性也帶來了潛在的衝突。
衝突的根源
這些衝突主要源自於它們對於輸入緩衝區(input buffer)的處理方式不同。scanf
和 cin
在讀取輸入時,都會從輸入緩衝區中獲取資料。如果我們在程式中混合使用這兩者,可能會導致輸入緩衝區的狀態混亂,進而產生非預期的結果。
常見的衝突情況
- 殘留的換行符號:當我們使用
scanf
讀取數字後,按下 Enter 鍵會在輸入緩衝區中留下一個換行符號(’\n’)。如果接下來使用cin
讀取字串,cin
會直接讀取這個換行符號,導致讀取的字串為空。
確實,在多數情況下,開發者會傾向於統一使用 cin
、cout
或 scanf
、printf
,避免混用的麻煩。如果您追求程式的輸出效率,取消輸入輸出流同步是個值得考慮的優化策略。
取消輸入輸出流同步
C++ 預設會將 cin
、cout
與 C 語言的 stdio
(也就是 scanf
、printf
所使用的標準輸入輸出庫)同步。這意味著每次進行 cin
、cout
操作時,都會強制刷新底層的輸入輸出緩衝區,確保與 stdio
的操作保持一致。雖然這增加了安全性,但也可能導致一些效能損耗,尤其是在頻繁進行輸入輸出操作的情況下。
取消同步的方法
您可以透過以下程式碼取消 cin
、cout
與 stdio
的同步:
std::ios_base::sync_with_stdio(false);
這行程式碼會告知 C++ 標準庫,cin
、cout
不需要再與 stdio
同步。如此一來,cin
、cout
可以更自由地管理自己的緩衝區,從而提升輸入輸出的效率。
注意事項
- 取消同步後,請勿混用:一旦取消同步,就絕對不要再混用
cin
、cout
與scanf
、printf
,否則可能導致未定義行為(undefined behavior),造成程式崩潰或產生錯誤的結果。
cin.tie(0) 的作用
在 C++ 的輸入輸出流程 (iostream) 中,cin.tie(0)
的作用是解除標準輸入 cin
與標準輸出 cout
之間的綁定關係。
預設行為
一般情況下,cin
和 cout
之間存在一種稱為「綁定 (tie)」的關係。這意味著在從 cin
接收輸入之前,會自動清空 (flush) cout
的緩衝區 (也就是將緩衝區的內容輸出)。這種行為在與使用者互動的程式中很自然,但在涉及大量輸入輸出的場景 (如競賽程式設計) 中,可能會成為效能瓶頸。
cin.tie(0) 的效果
執行 cin.tie(0)
後,這種綁定關係會被解除,cin
的操作不再強制清空 cout
的緩衝區。這可以提高輸入輸出處理的效率,尤其是在讀取大量資料時,可能會顯著提升執行速度。
經常搭配使用的操作
cin.tie(0)
通常會與 std::ios::sync_with_stdio(false)
一起使用。std::ios::sync_with_stdio(false)
用於解除 C++ 的輸入輸出流與 C 語言標準輸入輸出庫 (stdio) 之間的同步。這使得 C++ 的輸入輸出流獨立於 stdio,從而實現更快的操作。
取消綁定關係的方法
在main
函數加上加上此段程式碼即可。
cin.tie(0)
注意事項
- 使用
std::ios::sync_with_stdio(false)
和cin.tie(0)
後,不應混用 C++ 的輸入輸出流和 C 語言的標準輸入輸出庫 (例如:不要同時使用cout
和printf
)。 - 在競程中,一個輸入通常對應一個輸出,輸出順序不重要,因此使用
cin.tie(0)
後可能出現的輸出順序問題,並不會影響結果。 - 基本上這樣優化下來已經與C的
printf
與scanf
速度差不多了,因此如果用習慣cin
cout
的人可以以此法優化有不失方便性。
進階用法
-
puts()
-
功能:將一個字串 (以 null 字元 ‘\0’ 結尾的字元陣列) 輸出到標準輸出 (通常是螢幕),並自動在結尾加上一個換行字元 ‘\n’。
-
用法
#include <stdio.h> int main() { puts("Hello, world!"); // 輸出 "Hello, world!" 並換行 return 0; }
putchar()
-
功能:將一個單個字元輸出到標準輸出。
-
用法
#include <stdio.h> int main() { putchar('A'); // 輸出字元 'A' return 0; }
gets()
-
功能:從標準輸入 (通常是鍵盤) 讀取一行字串,直到遇到換行字元 ‘\n’ 為止,並將換行字元替換為 null 字元 ‘\0’,然後將字串儲存到指定的字元陣列中。
-
重要提醒:由於
gets()
函式不檢查輸入字串的長度是否超過指定的字元陣列大小,可能導致緩衝區溢位 (buffer overflow) 的安全問題。因此,強烈建議使用更安全的fgets()
函式來取代gets()
。 -
用法
#include <stdio.h> int main() { char name[50]; printf("請輸入您的名字:"); gets(name); // 從鍵盤讀取名字,並儲存到 name 陣列中 printf("您好,%s!\n", name); return 0; }
getchar()
-
功能:從標準輸入讀取一個單個字元,並返回該字元的 ASCII 碼值 (整數)。如果遇到檔案結尾 (end-of-file, EOF),則返回 EOF。
-
用法
#include <stdio.h> int main() { int ch; printf("請輸入一個字元:"); ch = getchar(); printf("您輸入的字元是:%c\n", ch); return 0; }
總結
puts()
和putchar()
用於輸出,分別輸出字串和單個字元。gets()
和getchar()
用於輸入,分別讀取一行字串和單個字元。- 為了安全起見,請避免使用
gets()
,改用fgets()
-
總結
總而言之,在競程的賽場上,當資料量龐大到足以影響程式運行速度時,輸入輸出(I/O)優化便成為決定勝負的關鍵因素。儘管 C/C++ 本身具備良好的 I/O 效率,但仍有優化的空間。透過本文介紹的技巧,從基礎的 cin/cout 與 scanf/printf 衝突排除、取消同步與綁定,到進階的 C 語言 I/O 函式運用,程式設計師能更有效率地處理大量資料,讓程式在競程中表現更出色。