在預設情況下, std::cin/std::cout
是極為遲緩的讀入/輸出方式,而 scanf/printf
比 std::cin/std::cout
快得多。
可是為什麼會這樣呢?有沒有什麼辦法解決讀入輸出緩慢的問題呢?
關閉同步/解除繫結
std::ios::sync_with_stdio(false)
這個函式是一個“是否相容 stdio”的開關,C++ 為了相容 C,保證程式在使用了 printf
和 std::cout
的時候不發生混亂,將輸出流綁到了一起。
這其實是 C++ 為了相容而採取的保守措施。我們可以在進行 IO 操作之前將 stdio 解除繫結,但是在這樣做之後要注意不能同時使用 std::cin/std::cout
和 scanf/printf
。
tie
tie 是將兩個 stream 繫結的函式,空引數的話返回當前的輸出流指標。
在預設的情況下 std::cin
繫結的是 std::cout
,每次執行 <<
操作符的時候都要呼叫 flush()
,這樣會增加 IO 負擔。可以通過 std::cin.tie(0)
(0 表示 NULL)來解除 std::cin
與 std::cout
的繫結,進一步加快執行效率。
程式碼實現
std::ios::sync_with_stdio(false);
std::cin.tie(0);
// 如果編譯開啟了 C++11 或更高版本,建議使用 std::cin.tie(nullptr);
讀入優化
scanf
和 printf
依然有優化的空間,這就是本章所介紹的內容——讀入和輸出優化。
- 注意,本頁面中介紹的讀入和輸出優化均針對整型資料,若要支援其他型別的資料(如浮點數),可自行按照本頁面介紹的優化原理來編寫程式碼。
原理
眾所周知, getchar
是用來讀入 1 byte 的資料並將其轉換為 char
型別的函式,且速度很快,故可以用“讀入字元——轉換為整型”來代替緩慢的讀入
每個整數由兩部分組成——符號和數字
整數的 '+' 通常是省略的,且不會對後面數字所代表的值產生影響,而 '-' 不可省略,因此要進行判定
10 進位制整數中是不含空格或除 0~9 和正負號外的其他字元的,因此在讀入不應存在於整數中的字元(通常為空格)時,就可以判定已經讀入結束
C 和 C++ 語言分別在 ctype.h 和 cctype 標頭檔案中,提供了函式 isdigit
, 這個函式會檢查傳入的引數是否為十進位制數字字元,是則返回 true ,否則返回 false 。對應的,在下面的程式碼中,可以使用 isdigit(ch)
代替 ch >= '0' && ch <= '9'
,而可以使用 !isdigit(ch)
代替 ch <'0' || ch> '9'
程式碼實現
int read() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') { // ch 不是數字時
if (ch == '-') w = -1; // 判斷是否為負
ch = getchar(); // 繼續讀入
}
while (ch >= '0' && ch <= '9') { // ch 是數字時
x = x * 10 + (ch - '0'); // 將新讀入的數字’加’在 x 的後面
// x 是 int 型別,char 型別的 ch 和 ’0’ 會被自動轉為其對應的
// ASCII 碼,相當於將 ch 轉化為對應數字
// 此處也可以使用 (x<<3)+(x<<1) 的寫法來代替 x*10
ch = getchar(); // 繼續讀入
}
return x * w; // 數字 * 正負號 = 實際數值
}
- 舉例
讀入 num 可寫為 num=read();
輸出優化
原理
同樣是眾所周知, putchar
是用來輸出單個字元的函式
因此將數字的每一位轉化為字元輸出以加速
要注意的是,負號要單獨判斷輸出,並且每次 %(mod)取出的是數字末位,因此要倒序輸出
程式碼實現
int write(int x) {
if (x < 0) { // 判負 + 輸出負號 + 變原數為正數
x = -x;
putchar('-');
}
if (x > 9) write(x / 10); // 遞迴,將除最後一位外的其他部分放到遞迴中輸出
putchar(x % 10 + '0'); // 已經輸出(遞迴)完 x 末位前的所有數字,輸出末位
}
但是遞迴實現常數是較大的,我們可以寫一個棧來實現這個過程
inline void write(int x) {
static int sta[35];
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
while (top) putchar(sta[--top] + 48); // 48 是 '0'
}
- 舉例
輸出 num 可寫為 write(num);
更快的讀入/輸出優化
通過 fread
或者 mmap
可以實現更快的讀入。其本質為一次性將輸入檔案讀入一個巨大的快取區,如此比逐個字元讀入要快的多 ( getchar
, putchar
)。因為硬碟的多次讀寫速度是要慢於記憶體的,所以先一次性讀到快取區裡再從快取區讀入要快的多。
更通用的是 fread
,因為 mmap
不能在 Windows 環境下使用。
fread
類似於引數為 "%s"
的 scanf
,不過它更為快速,而且可以一次性讀入若干個字元(包括空格換行等製表符),如果快取區足夠大,甚至可以一次性讀入整個檔案。
對於輸出,我們還有對應的 fwrite
函式
std::size_t fread(void* buffer, std::size_t size, std::size_t count,
std::FILE* stream);
std::size_t fwrite(const void* buffer, std::size_t size, std::size_t count,
std::FILE* stream);
使用示例: fread(Buf, 1, SIZE, stdin)
,表示從 stdin 檔案流中讀入 SIZE 個大小為 1 byte 的資料塊到 Buf 中。
讀入之後的使用就跟普通的讀入優化相似了,只需要重定義一下 getchar。它原來是從檔案中讀入一個 char,現在變成從 Buf 中讀入一個 char,也就是頭指標向後移動一位。
char buf[1 << 20], *p1, *p2;
#define gc() \
(p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) \
? EOF \
: *p1++)
fwrite
也是類似的,先放入一個 OutBuf[MAXSIZE]
中,最後通過 fwrite
一次性將 OutBuf
輸出。
參考程式碼:
namespace IO {
const int MAXSIZE = 1 << 20;
char buf[MAXSIZE], *p1, *p2;
#define gc() \
(p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin), p1 == p2) \
? EOF \
: *p1++)
inline int rd() {
int x = 0, f = 1;
char c = gc();
while (!isdigit(c)) {
if (c == '-') f = -1;
c = gc();
}
while (isdigit(c)) x = x * 10 + (c ^ 48), c = gc();
return x * f;
}
char pbuf[1 << 20], *pp = pbuf;
inline void push(const char &c) {
if (pp - pbuf == 1 << 20) fwrite(pbuf, 1, 1 << 20, stdout), pp = pbuf;
*pp++ = c;
}
inline void write(int x) {
static int sta[35];
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
while (top) push(sta[--top] + '0');
}
} // namespace IO
輸入輸出的緩衝
printf
和 scanf
是有緩衝區的。這也就是為什麼,如果輸入函式緊跟在輸出函式之後/輸出函式緊跟在輸入函式之後可能導致錯誤。
重新整理緩衝區
- 程式結束
- 關閉檔案
printf
輸出\r
或者\n
到終端的時候(注:如果是輸出到檔案,則不會重新整理緩衝區)- 手動
fflush()
- 緩衝區滿自動重新整理
cout
輸出endl
使輸入輸出優化更為通用
如果你的程式使用多個型別的變數,那麼可能需要寫多個輸入輸出優化的函式。下面給出的程式碼使用 C++ 中的 template
實現了對於所有整數型別的輸入輸出優化。
template <typename T>
inline T
read() { //宣告 template 類,要求提供輸入的型別T,並以此型別定義行內函數 read()
T sum = 0, fl = 1; //將 sum,fl 和 ch 以輸入的型別定義
int ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') fl = -1;
for (; isdigit(ch); ch = getchar()) sum = sum * 10 + ch - '0';
return sum * fl;
}
如果要分別輸入 int
型別的變數 a, long long
型別的變數 b 和 __int128
型別的變數 c,那麼可以寫成
a = read<int>();
b = read<long long>();
c = read<__int128>();
完整帶除錯版
關閉除錯開關時使用 fread()
, fwrite()
,退出時自動析構執行 fwrite()
。
開啟除錯開關時使用 getchar()
, putchar()
,便於除錯。
若要開啟檔案讀寫時,請在所有讀寫之前加入 freopen()
。
// #define DEBUG 1 //除錯開關
struct IO {
#define MAXSIZE (1 << 20)
#define isdigit(x) (x >= '0' && x <= '9')
char buf[MAXSIZE], *p1, *p2;
char pbuf[MAXSIZE], *pp;
#if DEBUG
#else
IO() : p1(buf), p2(buf), pp(pbuf) {}
~IO() { fwrite(pbuf, 1, pp - pbuf, stdout); }
#endif
inline char gc() {
#if DEBUG //除錯,可顯示字元
return getchar();
#endif
if (p1 == p2) p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin);
return p1 == p2 ? ' ' : *p1++;
}
inline bool blank(char ch) {
return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
}
template <class T>
inline void read(T &x) {
register double tmp = 1;
register bool sign = 0;
x = 0;
register char ch = gc();
for (; !isdigit(ch); ch = gc())
if (ch == '-') sign = 1;
for (; isdigit(ch); ch = gc()) x = x * 10 + (ch - '0');
if (ch == '.')
for (ch = gc(); isdigit(ch); ch = gc())
tmp /= 10.0, x += tmp * (ch - '0');
if (sign) x = -x;
}
inline void read(char *s) {
register char ch = gc();
for (; blank(ch); ch = gc())
;
for (; !blank(ch); ch = gc()) *s++ = ch;
*s = 0;
}
inline void read(char &c) {
for (c = gc(); blank(c); c = gc())
;
}
inline void push(const char &c) {
#if DEBUG //除錯,可顯示字元
putchar(c);
#else
if (pp - pbuf == MAXSIZE) fwrite(pbuf, 1, MAXSIZE, stdout), pp = pbuf;
*pp++ = c;
#endif
}
template <class T>
inline void write(T x) {
if (x < 0) x = -x, push('-'); // 負數輸出
static T sta[35];
T top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
while (top) push(sta[--top] + '0');
}
template <class T>
inline void write(T x, char lastChar) {
write(x), push(lastChar);
}
} io;
參考
http://www.hankcs.com/program/cpp/cin-tie-with-sync_with_stdio-acceleration-input-and-output.html
http://meme.biology.tohoku.ac.jp/students/iwasaki/cxx/speed.html