浅谈超快读与超快写
使用原因
有些题目会比较毒瘤,出现很大的输入输出量,通过超快读,我们可以十分有效地缩短输入用时,为程序后面的操作预留出更多的时间,而对于超快写,可以防止自己的程序在输出量极大时超时。
普通快读与快写
考虑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>
#include<cmath>
using namespace std ;
namespace IO
{
#define isdigit(ch) (ch >= '0' && ch <= '9')
#define SIZE (1 << 16)
namespace Read
{
char buf[SIZE] , *_now = buf , *_end = buf ;
inline char getchar()
{
if(_now == _end)
{
_now = _end = buf ;
_end += fread(buf , 1 , SIZE , stdin) ;
if(_now == _end)
return EOF ;
}
return *(_now++) ;
}
template<typename T>
inline 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 ;
}
#define sb(ch) (ch == ' ' || ch == '\n' || ch == '\r')
inline void read(char& c)
{
char tmp = getchar() ;
while(sb(tmp))
tmp = getchar() ;
c = tmp ;
}
inline void read(char* s)
{
char ch = getchar() ;
while(sb(ch) && ch != EOF)
ch = getchar() ;
int len = 0 ;
while(!sb(ch) && ch != EOF)
s[len++] = ch , ch = getchar() ;
s[len] = '\0' ;
}
inline void read(string& s)
{
s.clear() ;
char ch = getchar() ;
while(sb(ch) && ch != EOF)
ch = getchar() ;
while(!sb(ch) && ch != EOF)
s.push_back(ch) , ch = getchar() ;
}
inline 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 ;
}
inline 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 ;
}
inline 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>
inline qistream& operator>>(T& a)
{
read(a) ;
return *this ;
}
inline qistream& operator>>(char* s)
{
read(s) ;
return *this ;
}
} qcin ;
}
namespace Write
{
#define SIZE (1 << 16)
char buf[SIZE] , *p = buf ;
template<typename T>
struct pres
{
T num ;
int len ;
} ;
template<typename T>
inline pres<T> make_lf(T num , int len)
{
pres<T> ret ;
ret.num = num , ret.len = len ;
return ret ;
}
inline void flush()
{
fwrite(buf , 1 , p - buf , stdout) ;
p = buf ;
}
inline void putchar(char ch)
{
if(p == buf + SIZE)
flush() ;
*p = ch ;
++p ;
}
class Flush{public:~Flush(){flush() ;};}_;
template<typename T>
inline 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--]) ;
}
inline void write(char c)
{
putchar(c) ;
}
inline void write(const char* s)
{
int siz = strlen(s) ;
for(int i = 0 ; i < siz ; ++i)
putchar(s[i]) ;
}
inline void write(char* s)
{
int siz = strlen(s) ;
for(int i = 0 ; i < siz ; ++i)
putchar(s[i]) ;
}
inline void write(string& s)
{
int siz = s.size() ;
for(int i = 0 ; i < siz ; ++i)
putchar(s[i]) ;
}
inline void write(double x , int len = 6)
{
double tmp = x ;
if(x < 0)
putchar('-') , x = -x , tmp = -tmp ;
double y = 5 * powl(10 , -len - 1) ;
x += y , tmp += y ;
int lennow = 0 ;
char st[50] ;
if(x < 1)
putchar('0') ;
else
for( ; x >= 1 ; x /= 10)
st[++lennow] = floor(x) - floor(x / 10) * 10 + '0' ;
while(lennow)
putchar(st[lennow--]) ;
putchar('.') ;
for(tmp *= 10 ; len ; --len , tmp *= 10)
putchar(floor(tmp) - floor(tmp / 10) * 10 + '0') ;
}
inline void write(float x , int len = 6)
{
float tmp = x ;
if(x < 0)
putchar('-') , x = -x , tmp = -tmp ;
float y = 5 * powl(10 , -len - 1) ;
x += y , tmp += y ;
int lennow = 0 ;
char st[50] ;
if(x < 1)
putchar('0') ;
else
for( ; x >= 1 ; x /= 10)
st[++lennow] = floor(x) - floor(x / 10) * 10 + '0' ;
while(lennow)
putchar(st[lennow--]) ;
putchar('.') ;
for(tmp *= 10 ; len ; --len , tmp *= 10)
putchar(floor(tmp) - floor(tmp / 10) * 10 + '0') ;
}
inline void write(long double x , int len = 6)
{
long double tmp = x ;
if(x < 0)
putchar('-') , x = -x , tmp = -tmp ;
long double y = 5 * powl(10 , -len - 1) ;
x += y , tmp += y ;
int lennow = 0 ;
char st[50] ;
if(x < 1)
putchar('0') ;
else
for( ; x >= 1 ; x /= 10)
st[++lennow] = floor(x) - floor(x / 10) * 10 + '0' ;
while(lennow)
putchar(st[lennow--]) ;
putchar('.') ;
for(tmp *= 10 ; len ; --len , tmp *= 10)
putchar(floor(tmp) - floor(tmp / 10) * 10 + '0') ;
}
class qostream
{
public:
template<typename T>
inline qostream& operator<<(T x)
{
write(x) ;
return *this ;
}
inline qostream& operator<<(const char* s)
{
write(s) ;
return *this ;
}
template<typename T>
inline qostream& operator<<(const pres<T> x)
{
write(x.num , x.len) ;
return *this ;
}
} qcout ;
}
using Read::qcin ;
using Write::qcout ;
using Write::make_lf ;
}
(updated on 2025/4/3 修复浮点数输出处问题)
(updated on 2025/7/31 修复Linux下的字符串输入时的"\r\n"问题)
(updated on 2025/8/13 修复Linux下的字符输入时的问题(其实是上一次忘改正过来了,也是"\r\n"问题))

浙公网安备 33010602011771号