淺談超快讀與超快寫

Torrentolf發表於2024-11-21

使用原因

有些題目會比較毒瘤,出現很大的輸入輸出量,透過超快讀,我們可以十分有效地縮短輸入用時,為程式後面的操作預留出更多的時間,而對於超快寫,可以防止自己的程式在輸出量極大時超時。

普通快讀與快寫

考慮C++中的\(getchar\)函式的讀入效率比較高,所以我們可以使用其將數字一位一位地讀入,而對應的輸出則使用\(puchar\)函式,但是要注意輸出一個數時應逆序輸出(即先輸出最高位),而我們在一位一位輸出時是優先算出第一位(當然可以先算最高位,但是會比較麻煩),所以需要一個類似於棧的結構儲存,最後再一起輸出。

普通快讀快寫模板程式碼

template<typename T>
void read(T& w)
{
    w = 0 ;
    short f = 1 ;
    char ch = getchar() ;
    while(!isdigit(ch))
    {
        if(ch == '-')
            f = -1 ;
        ch = getchar() ;
    }
    while(isdigit(ch))
        w = (w << 1) + (w << 3) + (ch ^ 48) , ch = getchar() ;
    w *= f ;
}
template<typename T>
void write(T x)
{
    char st[50] ;
    int len = 0 ;
    if(x < 0)
        putchar('-') , x = -x ;
    do
    {
        st[++len] = x % 10 + '0' ;
        x /= 10 ;
    } while(x) ;	//注意這裡應使用do while迴圈,不然單獨一個0時不會有任何輸出
    while(len)
        putchar(st[len--]) ;
}

超快讀與超快寫

考慮我們像這樣一位一位讀入字元與輸出字元的效率太低了(普通快讀快寫甚至不如關掉流同步後的\(cin\)\(cout\)),所以我們可以建立一個緩衝區,先將內容讀入(輸出)至緩衝區,再用快讀快寫進行操作,而C++中剛好就有\(fread\)\(fwrite\)兩個函式,支援整段讀入(輸出)文字,而且實際效率取決於硬碟的讀寫速度,所以我們先講一下這兩個函式的使用。

\(fread\)有四個引數,分別是目標陣列(即緩衝區),單位大小,讀入數量與讀入檔案指標;

\(fwrite\)也有四個與\(fread\)相同的引數。(偷個懶)

注意\(fread\)的返回值為實際讀入單位大小的個數(單位大小一般當作\(1\),所以一般將返回值當作實際讀入位元組數),\(fwrite\)的輸出則十分抽象,它會將目標緩衝陣列中的元素按照你給定的引數全部輸出,因此如果第三個引數填入了緩衝區大小會將緩衝區內容後面的不知道哪來的亂碼全部輸出,而且新版的C++編譯器似乎將這個問題最佳化掉了,而這就是問題所在,因為眾所周知比賽時的編譯指令是-std=c+++14,而C++14並未將其最佳化掉,所以第三個引數一定要填緩衝陣列尾指標與緩衝陣列頭指標的差(我曾經因為這個問題在一場考試中爆了零),務必牢記!!!

超快讀已經基本沒有問題了,內容讀入至緩衝陣列後直接從其中提取即可,可以重寫一個\(getchar\)函式:

char getchar()
{
    if(_now == _end)
    {
        _now = _end = buf ;
        _end += fread(buf , 1 , SIZE , stdin) ;
        if(_now == _end)
            return EOF ;
    }
    return *(_now++) ;
}

然而對於超快寫,我們將內容寫入緩衝陣列後應將其輸出,但是又不能寫一個就輸出一個,這樣就與普通快寫沒有區別了,因此我們要在緩衝區滿了時將其清空輸出,並將緩衝區尾指標同時指向緩衝區頭指標,寫一個\(flush\)函式即可:

void flush()
{
    fwrite(buf , 1 , p - buf , stdout) ;
    p = buf ;
}

為了方便,我們在重寫的\(putchar\)函式中判一下緩衝區有沒有滿,並在滿了時呼叫\(flush\)函式:

void putchar(char ch)
{
    if(p == buf + SIZE)
        flush() ;
    *p = ch ;
    ++p ;
}

但是,我們還忘了一件事,就是在程式結束時,我們的緩衝區中的內容應全輸出至檔案中,因此每次在程式結束前還需要呼叫一下\(flush\)函式,非常麻煩,因此我們考慮能不能方便一點,這時,我們可以想到一個很逆天的操作:使用C++類中的解構函式來實現在程式結束執行前自動呼叫\(flush\)函式(不瞭解解構函式的請點此處):

class Flush{public:~Flush(){flush() ;};}_;

然後,我們就圓滿解決了超快寫的所有問題。

注意!

但是,我們其實還有最後一個問題:超快讀與超快寫不能與其他的\(IO\)方式混用,因為它使用單獨的緩衝陣列,混用包會出錯的,那怎麼辦?

解決方案

非常簡單粗暴:使用它實現幾乎所有型別的輸入輸出

最後最佳化

每次都寫\(read(x)\)\(write(x)\)著實很累人,就不能像\(cin、cout\)一樣方便地使用嗎?

當然可以,\(cin、cout\)本質上都是基於\(istream(ostream)\)類的過載運算子實現的,所以我們可以考慮模仿一下,定義自己的的\(qistream(qostream)\)類:

class qistream
{
    public:
    template<typename T>
    qistream& operator>>(T& a)
    {
        read(a) ;
        return *this ;
    }
    qistream& operator>>(char* s)
    {
        read(s) ;
        return *this ;
    }
} qcin ;
class qostream
{
    public:
    template<typename T>
    qostream& operator<<(T x)
    {
        write(x) ;
        return *this ;
    }
    qostream& operator<<(const char* s)
    {
        write(s) ;
        return *this ;
    }
} qcout ;

附最終完整程式碼:

#include<iostream>
#include<cstring>
using namespace std ;
namespace IO
{
	namespace Read
	{
		#define isdigit(ch) (ch >= '0' && ch <= '9')
		#define SIZE (1 << 16)
		char buf[SIZE] , *_now = buf , *_end = buf ;
		char getchar()
		{
			if(_now == _end)
			{
				_now = _end = buf ;
				_end += fread(buf , 1 , SIZE , stdin) ;
				if(_now == _end)
					return EOF ;
			}
			return *(_now++) ;
		}
		template<typename T>
		void read(T& w)
		{
			w = 0 ;
			short f = 1 ;
			char ch = getchar() ;
			while(!isdigit(ch))
			{
				if(ch == '-')
					f = -1 ;
				ch = getchar() ;
			}
			while(isdigit(ch))
				w = (w << 1) + (w << 3) + (ch ^ 48) , ch = getchar() ;
			w *= f ;
		}
		void read(char& c)
		{
			char tmp = getchar() ;
			while(tmp == ' ' || tmp == '\n')
				tmp = getchar() ;
			c = tmp ;
		}
		#define sb(ch) (ch == ' ' || ch == '\n')
		void read(char* s)
		{
			char ch = getchar() ;
			while(sb(ch))
				ch = getchar() ;
			int len = 0 ;
			while(!sb(ch))
				s[len++] = ch , ch = getchar() ;
			s[len] = '\0' ;
		}
		void read(string& s)
		{
			s.clear() ;
			char ch = getchar() ;
			while(sb(ch))
				ch = getchar() ;
			while(!sb(ch))
				s.push_back(ch) , ch = getchar() ;
		}
		void read(double& w)
		{
			w = 0 ;
			short f = 1 ;
			char ch = getchar() ;
			while(!isdigit(ch))
			{
				if(ch == '-')
					f = -1 ;
				ch = getchar() ;
			}
			while(isdigit(ch))
				w = w * 10 + (ch ^ 48) , ch = getchar() ;
			if(ch == '.')
			{
				ch = getchar() ;
				int len = 0 ;
				while(isdigit(ch))
					w = w * 10 + (ch ^ 48) , ch = getchar() , ++len ;
				while(len)
					w /= 10 , --len ;
			}
			w *= f ;
		}
		void read(float& w)
		{
			w = 0 ;
			short f = 1 ;
			char ch = getchar() ;
			while(!isdigit(ch))
			{
				if(ch == '-')
					f = -1 ;
				ch = getchar() ;
			}
			while(isdigit(ch))
				w = w * 10 + (ch ^ 48) , ch = getchar() ;
			if(ch == '.')
			{
				ch = getchar() ;
				int len = 0 ;
				while(isdigit(ch))
					w = w * 10 + (ch ^ 48) , ch = getchar() , ++len ;
				while(len)
					w /= 10 , --len ;
			}
			w *= f ;
		}
		void read(long double& w)
		{
			w = 0 ;
			short f = 1 ;
			char ch = getchar() ;
			while(!isdigit(ch))
			{
				if(ch == '-')
					f = -1 ;
				ch = getchar() ;
			}
			while(isdigit(ch))
				w = w * 10 + (ch ^ 48) , ch = getchar() ;
			if(ch == '.')
			{
				ch = getchar() ;
				int len = 0 ;
				while(isdigit(ch))
					w = w * 10 + (ch ^ 48) , ch = getchar() , ++len ;
				while(len)
					w /= 10 , --len ;
			}
			w *= f ;
		}
		class qistream
		{
			public:
			template<typename T>
			qistream& operator>>(T& a)
			{
				read(a) ;
				return *this ;
			}
			qistream& operator>>(char* s)
			{
				read(s) ;
				return *this ;
			}
		} qcin ;
	}
	namespace Write
	{
		#define SIZE (1 << 16)
		char buf[SIZE] , *p = buf ;
		void flush()
		{
			fwrite(buf , 1 , p - buf , stdout) ;
			p = buf ;
		}
		void putchar(char ch)
		{
			if(p == buf + SIZE)
				flush() ;
			*p = ch ;
			++p ;
		}
		class Flush{public:~Flush(){flush() ;};}_;
		template<typename T>
		void write(T x)
		{
			char st[50] ;
			int len = 0 ;
			if(x < 0)
				putchar('-') , x = -x ;
			do
			{
				st[++len] = x % 10 + '0' ;
				x /= 10 ;
			} while(x) ;
			while(len)
				putchar(st[len--]) ;
		}
		void write(char c)
		{
			putchar(c) ;
		}
		void write(const char* s)
		{
			int siz = strlen(s) ;
			for(int i = 0 ; i < siz ; ++i)
				putchar(s[i]) ;
		}
		void write(char* s)
		{
			int siz = strlen(s) ;
			for(int i = 0 ; i < siz ; ++i)
				putchar(s[i]) ;
		}
		void write(string& s)
		{
			int siz = s.size() ;
			for(int i = 0 ; i < siz ; ++i)
				putchar(s[i]) ;
		}
		void write(double x , int len = 6)
		{
			if(x < 0)
				write('-') , x = -x ;
			long long sb = (long long)(x) ;
			x -= sb ;
			int spar = 0 ;
			for(int i = 0 ; i <= len ; ++i)
			{
				x *= 10 ;
				if(int(x) == 0)
					++spar ;
			}
			long long ssb = (long long)(x) ;
			if(ssb % 10 >= 5)
				ssb += 10 ;
			ssb /= 10 ;
			write(sb) , write('.') ;
			for(int i = 1 ; i <= spar ; ++i)
				write('0') ;
			write(ssb) ;
		}
		void write(float x , int len = 6)
		{
			if(x < 0)
				write('-') , x = -x ;
			int sb = int(x) ;
			x -= sb ;
			int spar = 0 ;
			for(int i = 0 ; i <= len ; ++i)
			{
				x *= 10 ;
				if(int(x) == 0)
					++spar ;
			}
			int ssb = int(x) ;
			if(ssb % 10 >= 5)
				ssb += 10 ;
			ssb /= 10 ;
			write(sb) , write('.') ;
			for(int i = 1 ; i <= spar ; ++i)
				write('0') ;
			write(ssb) ;
		}
		void write(long double x , int len = 6)
		{
			if(x < 0)
				write('-') , x = -x ;
			__int128 sb = __int128(x) ;
			x -= sb ;
			int spar = 0 ;
			for(int i = 0 ; i <= len ; ++i)
			{
				x *= 10 ;
				if(int(x) == 0)
					++spar ;
			}
			__int128 ssb = __int128(x) ;
			if(ssb % 10 >= 5)
				ssb += 10 ;
			ssb /= 10 ;
			write(sb) , write('.') ;
			for(int i = 1 ; i <= spar ; ++i)
				write('0') ;
			write(ssb) ;
		}
		class qostream
		{
			public:
			template<typename T>
			qostream& operator<<(T x)
			{
				write(x) ;
				return *this ;
			}
			qostream& operator<<(const char* s)
			{
				write(s) ;
				return *this ;
			}
		} qcout ;
	}
	using Read::qcin ;
	using Write::qcout ;
}
using namespace IO ;

完結撒花!!!

相關文章