最全快读、快写模板「持续更新」

旧版快读、快写「模板」

快读

template<typename type>
inline void read(type &x)
{
    x=0;bool flag(0);char ch=getchar();
    while(!isdigit(ch)) flag=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    flag?x=-x:0;
}

用法:read(x)


快写

template<typename type>
inline void write(type x,bool mode=1)//0为空格,1为换行
{
    x<0?x=-x,putchar('-'):0;static short Stack[50],top(0);
    do Stack[++top]=x%10,x/=10; while(x);
    while(top) putchar(Stack[top--]|48);
    mode?putchar('\n'):putchar(' ');
}

用法:write(x,0)write(x,1)

0为空格,1为换行


整合

#include<iostream>
#include<cstdio>

using namespace std;

template<typename type>
inline void read(type &x)
{
    x=0;static bool flag(0);char ch=getchar();
    while(!isdigit(ch)) flag=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    flag?x=-x:0;
}

template<typename type>
inline void write(type x,bool mode=1)//0为空格,1为换行
{
    x<0?x=-x,putchar('-'):0;static short Stack[50],top(0);
    do Stack[++top]=x%10,x/=10; while(x);
    while(top) putchar(Stack[top--]|48);
    mode?putchar('\n'):putchar(' ');
}

signed main()
{
    int n;
    read(n);
    write(n,0);//不换行
    write(n);//换行
    return 0;
}

溢出情形

  • int a;read(a);write(a);
    • 当输入 2147483648 时,会输出:-?
    • 当输入 2147483649 时,会输出:-2147483647
    • 当输入 -2147483649 时,会输出:2147483647
    • 当输入 -2147483650 时,会输出:2147483646
  • int a;cin>>a;cout<<a<<endl;
    • 当输入 2147483648 时,会输出:2147483647
    • 当输入 2147483649 时,会输出:2147483647
    • 当输入 -2147483649 时,会输出:-2147483648
    • 当输入 -2147483650 时,会输出:-2147483648

更快的快读快写:freadfwrite

快读

只需要把上面快读模板中的 getchar() 函数修改一下即可。

需要的新变量:

char buf[1<<20],*p1=buf,*p2=buf;

其中:

  • buf 是缓冲区,用来缓存读入数据。
  • p1 指向当前读到的元素。
  • p2 指向缓冲区的末尾。

需要用到的函数:fread,其在头文件 <cstdio> 中的原型如下:

size_t fread( void *restrict buffer, size_t size, size_t count, FILE *restrict stream );

其中:

  • buffer 指向要读取的数组中首个对象的指针。「即“目的地”,直接指向 buf 数组即可」
  • size 为要读取的每个对象的大小(单位是字节)。「因为快读是一个字符一个字符的读入,因此 size 为1个 char 所占的字节大小,即为 1
  • count 为要读取的对象个数。「即“读多少”,和 buf 数组大小一样,直接设为 \(2^{20}\) 即可」
  • stream 为输入流。「即“从哪读”,设为标准输入 stdin 即可」
  • fread 的正常返回值为成功读取的对象个数,若出现错误或到达文件末尾,则可能小于 count。若 sizecount 为零,则 fread 返回零且不进行其他动作。

重新定义的 getchar() 函数:

inline char getchar()
{
    if(p1==p2)
    {
        p1=buf;
        p2=buf+fread(buf,1,1<<20,stdin);
        if(p1==p2) return EOF;
        else return *p1++;
    }
    return *p1++;
}
  1. p1!=p2,说明缓冲区还没读完,直接返回 *p1,然后 p1++ 即可。
  2. p1==p2,说明缓冲区已经读完。此时将 p1 重新指向 buf,将 p2 指向 buf+已读入的对象个数「此处 + 为指针运算」
    1. 若此时仍有 p1==p2,说明读入的字符个数为 0,读不进东西了,即到达文件末尾,返回 EOF「文件末尾标识符」
    2. 否则返回 *p1,然后 p1++ 即可。

这样写代码有点长,我们可以稍微简化一下代码。

我们知道:

  • 赋值表达式的返回值为赋的值本身「int a,b;b=(a=1); 其中 b 的值为 1
  • 逗号表达式的返回值为最后一项的值「int a,b,c;c=(a=1,b=a+1); 其中 c 的值为 2

因此,我们可以将 p1=bufp2=buf+fread(buf,1,1<<20,stdin) 合并为 p2=(p1=buf)+fread(buf,1,1<<20,stdin),再利用逗号表达式和三目运算符,将第 5~8 行代码压缩为:(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++;

整个函数体也可以直接压缩为一行宏定义:

#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)

快写

快写与快读类似,只需要重新修改 putchar() 即可,模板不用改。

需要的新变量:

char buf[1<<20],*p3=buf;

同样用一个 buf 数组作为缓冲区存储要输出的内容,p3 指向缓冲区中读入的最后一个元素的下一个位置。

需要用到的函数:fwrite,其在头文件 <cstdio> 中的原型如下:

size_t fwrite(const void * buffer, size_t size, size_t count, FILE * stream);

其参数意义可以类比前面的 fread,几乎一模一样。

我们实现一个 flush 函数来输出缓冲区,方便以后多次调用:

inline void flush(){fwrite(p3=out,1,1<<20,stdout);}

可以将其写为宏定义的形式:

#define flush() (fwrite(p3=out,1,1<<20,stdout))

输出结束或缓冲区满时,调用 flush(),用 fwrite 输出缓冲区内容并清空缓冲区。

为了避免每次输出调用 flush() 造成的效率损失,可以只在缓冲区满和程序结束时调用 flush()

可以利用程序结束时调用析构函数的原理,定义一个类来在程序结束时调用 flush()

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

可以更简洁一些:

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

重新定义的 putchar 函数:

inline char putchar(char ch)
{
    if(p3==out+(1<<20)) flush();
    *p3=ch;
    return *p3++;
}

可以更简洁一些:

#define putchar(ch) (p3==out+SIZE&&flush(),*p3++=(ch))

整合

为了避免与 std 命名空间中的变量、函数名冲突,可以将上面所有操作放入自定义的命名空间。

「注:代码中的 inout 数组意义同前面的 buf,是快读、快写的缓冲区。」

#include<iostream>
#include<cstdio>

using namespace std;
namespace ly
{
    namespace IO
    {
        #define SIZE (1<<20)
        char in[SIZE],out[SIZE],*p1=in,*p2=in,*p3=out;
        #define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,SIZE,stdin),p1==p2)?EOF:*p1++)
        #define flush() (fwrite(p3=out,1,SIZE,stdout))
        #define putchar(ch) (p3==out+SIZE&&flush(),*p3++=(ch))
        class Flush{public:~Flush(){flush();}}_;
        template<typename type>
        inline void read(type &x)
        {
            x=0;bool flag(0);char ch=getchar();
            while(!isdigit(ch)) flag^=ch=='-',ch=getchar();
            while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
            flag?x=-x:0;
        }
        template<typename type>
        inline void write(type x,bool flag=1)
        {
            x<0?x=-x,putchar('-'):0;static short Stack[50],top(0);
            do Stack[++top]=x%10,x/=10;while(x);
            while(top) putchar(Stack[top--]|48);
            flag?putchar('\n'):putchar(' ');
        }
        #undef SIZE
        #undef getchar
        #undef putchar
        #undef flush
    }
}using namespace ly::IO;

signed main()
{
    int a,b;
    read(a),read(b);
    write(a,0),write(b);
    return 0;
}

可以看到,只是增加了寥寥十行代码,我们的快读快写又进入了一个新的境界。

但是这样就出现了一个问题:在终端里运行程序时,必须要按下 control+D 才能结束输入,当且仅当程序结束或输出缓冲区满时才能输出。

这个问题显然在用文件输入输出或真正提交代码时不是问题,但是降低了我们在本地调试代码的效率。

解决,必须解决!

我们可以用宏定义的方法,设置一个开关:

#define LOCAL
#include<iostream>
#include<cstdio>

using namespace std;
namespace ly
{
    namespace IO
    {
        #ifndef LOCAL
            #define SIZE (1<<20)
            char in[SIZE],out[SIZE],*p1=in,*p2=in,*p3=out;
            #define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,SIZE,stdin),p1==p2)?EOF:*p1++)
            #define flush() (fwrite(p3=out,1,SIZE,stdout))
            #define putchar(ch) (p3==out+SIZE&&flush(),*p3++=(ch))
            class Flush{public:~Flush(){flush();}}_;
        #endif
        template<typename type>
        inline void read(type &x)
        {
            x=0;bool flag(0);char ch=getchar();
            while(!isdigit(ch)) flag^=ch=='-',ch=getchar();
            while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
            flag?x=-x:0;
        }
        template<typename type>
        inline void write(type x,bool flag=1)
        {
            x<0?x=-x,putchar('-'):0;static short Stack[50],top(0);
            do Stack[++top]=x%10,x/=10;while(x);
            while(top) putchar(Stack[top--]|48);
            flag?putchar('\n'):putchar(' ');
        }
        #ifndef LOCAL
            #undef SIZE
            #undef getchar
            #undef putchar
            #undef flush
        #endif
    }
}using namespace ly::IO;

signed main()
{
    int a,b;
    read(a),read(b);
    write(a,0),write(b);
    return 0;
}

若要使用 freadfwrite 版快读快写,只需注释掉 #define LOCAL 即可;若要本地调试,开启 #define LOCAL 即可。

而且,无论开不开启 #define LOCAL,程序都能正常运行,结果不会出错。


多变量输入、输出的快读快写

可变参数模板

若有多个变量:

int a,b,c,d;
read(a),read(b),read(c),read(d);
write(a,0),write(b,0),write(c,0),write(d);

这样写可能会很麻烦。

为了偷懒,现在介绍一下 c++11 的一个新特性:可变参数模板

这种模板的声明也很简单:

template<typename... types>

发现没有?只是比普通模板多了个 ...

先介绍一下省略号的作用:

  • 位于参数左边:打包 type... args
  • 位于参数右边:解包 args...

... 可接纳的模板参数个数是 0 个及以上的任意数量,需要注意 包括 0 个

若不希望产生模板参数个数为 0 的变长参数模板,则可以采用以下的定义:

template<typename type, typename... types>

使用时,可以以递归的方法取出可用参数。「见下面的代码」


多变量快读

基于上面的介绍,我们只需要在原先的快读下面加两行代码即可:

template<typename type,typename ...T>
inline void read(type &x,T&...y){read(x),read(y...);}

这样,就可以一次读取多个变量啦,同时还支持一次读入不同的数据类型:

int a;long long b;short c;__int128 d;
read(a,b,c,d);

是不是很强大?


多变量快写

快写要更改的地方就多了一些,因为要考虑输出变量之间的空格、输出结束的换行。

首先我们把原先的快写模板更改一下,只输出变量,不输出空格、换行:

template<typename type>
inline void write(type x)
{
    x<0?x=-x,putchar('-'):0;
    static short Stack[50],top(0);
    do Stack[++top]=x%10,x/=10;while(x);
    while(top) putchar(Stack[top--]|48);
}

对了,顺手改一下,让我们的快读、快写可以输入输出字符:

inline char read(char &ch){return ch=getchar();}
inline char write(char ch){return putchar(ch);}

接下来准备实现多变量快写。

template<typename type,typename ...T>
inline void write(type x,T...y){write(x),putchar(' '),write(y...),putchar('\n');}

这样可以吗?

显然不行,因为每递归一次就输出一个换行,这不是我们想要的结果。

我们需要在 T...y 这一坨变量只剩下 1 个时再输出换行。「因为若 T...y 中变量数为 0,只会调用前面的单变量快写」

怎么判断 T...y 中的变量个数呢?可以用 sizeof...(y)。当且仅当 sizeof...(y)==1 时输出换行。

于是,最终的多变量快写如下:

template<typename type>
inline void write(type x)
{
    x<0?x=-x,putchar('-'):0;
    static short Stack[50],top(0);
    do Stack[++top]=x%10,x/=10;while(x);
    while(top) putchar(Stack[top--]|48);
}
inline char write(char ch){return putchar(ch);}
template<typename type,typename ...T>
inline void write(type x,T...y){write(x),putchar(' '),write(y...),sizeof...(y)^1?0:putchar('\n');}

这样,就可以一次输出多个变量,其中还可以有任意字符:

int a;long long b;short c;__int128 d;char e;
read(a,b,c,d,e);
write(a,b,c,d,e,'f');

整合

#include<iostream>
#include<cstdio>

using namespace std;

template<typename type>
inline void read(type &x)
{
    x=0;bool flag(0);char ch=getchar();
    while(!isdigit(ch)) flag^=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    flag?x=-x:0;
}
template<typename type>
inline void write(type x)
{
    x<0?x=-x,putchar('-'):0;
    static short Stack[50],top(0);
    do Stack[++top]=x%10,x/=10;while(x);
    while(top) putchar(Stack[top--]|48);
}
inline char read(char &ch){return ch=getchar();}
inline char write(const char &ch){return putchar(ch);}
template<typename type,typename ...T>
inline void read(type &x,T&...y){read(x),read(y...);}
template<typename type,typename ...T>
inline void write(type x,T...y){write(x),putchar(' '),write(y...),sizeof...(y)^1?0:putchar('\n');}

signed main()
{
    int a;long long b;short c;__int128 d;char e;
    read(a,b,c,d,e);
    write(a,b,c,d,e,'f');
    return 0;
}

快读、快写最新模板整合

最新模板「2022/11/17」

#define LOCAL
#include<iostream>
#include<cstdio>
#include<climits>
#include<cctype>
#define ll long long

using namespace std;
namespace ly
{
    namespace IO
    {
        #ifndef LOCAL
            constexpr auto maxn=1<<20;
            char in[maxn],out[maxn],*p1=in,*p2=in,*p3=out;
            #define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,maxn,stdin),p1==p2)?EOF:*p1++)
            #define flush() (fwrite(out,1,p3-out,stdout))
            #define putchar(x) (p3==out+maxn&&(flush(),p3=out),*p3++=(x))
            class Flush{public:~Flush(){flush();}}_;
        #endif
        namespace usr
        {
            template<typename type>
            inline type read(type &x)
            {
                x=0;bool flag(0);char ch=getchar();
                while(!isdigit(ch)) flag^=ch=='-',ch=getchar();
                while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
                return flag?x=-x:x;
            }
            template<typename type>
            inline void write(type x)
            {
                x<0?x=-x,putchar('-'):0;
                static short Stack[50],top(0);
                do Stack[++top]=x%10,x/=10;while(x);
                while(top) putchar(Stack[top--]|48);
            }
            inline char read(char &x){do x=getchar();while(isspace(x));return x;}
            inline char write(const char &x){return putchar(x);}
            inline void read(char *x){static char ch;read(ch);do *(x++)=ch;while(!isspace(ch=getchar())&&~ch);}
            template<typename type>inline void write(type *x){while(*x)putchar(*(x++));}
            inline void read(string &x){static char ch;read(ch),x.clear();do x+=ch;while(!isspace(ch=getchar())&&~ch);}
            inline void write(const string &x){for(int i=0,len=x.length();i<len;++i)putchar(x[i]);}
            template<typename type,typename...T>inline void read(type &x,T&...y){read(x),read(y...);}
            template<typename type,typename...T>
            inline void write(const type &x,const T&...y){write(x),putchar(' '),write(y...),sizeof...(y)^1?0:putchar('\n');}
            template<typename type>
            inline void put(const type &x,bool flag=1){write(x),flag?putchar('\n'):putchar(' ');}
        }
        #ifndef LOCAL
            #undef getchar
            #undef flush
            #undef putchar
        #endif
    }using namespace IO::usr;
}using namespace ly::IO::usr;

short a;int b;long long c;__int128 d;char e;char f[20];string g;

signed main()
{
    read(a,b,c,d,e,f,g);
    write(a,b,c,d,e,f,g);
    return 0;
}
/*
32767 2147483647 9223372036854775807 170141183460469231731687303715884105727 A 123abcABC!@#$%^ &*()[]{}:""<>''
*/

旧版本

更早的忘了存下来了……不过缺省源倒是还有,见【缺省源】。」

2022/11/1
#define LOCAL
#include<iostream>
#include<cstdio>
#include<climits>
#include<cctype>
#define ll long long

using namespace std;
namespace ly
{
    namespace IO
    {
        #ifndef LOCAL
            constexpr auto maxn=1<<20;
            char in[maxn],out[maxn],*p1=in,*p2=in,*p3=out;
            #define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,maxn,stdin),p1==p2)?EOF:*p1++)
            #define flush() (fwrite(out,1,p3-out,stdout))
            #define putchar(x) (p3==out+maxn&&(flush(),p3=out),*p3++=(x))
            class Flush{public:~Flush(){flush();}}_;
        #endif
        namespace usr
        {
            template<typename type>
            inline type read(type &x)
            {
                x=0;bool flag(0);char ch=getchar();
                while(!isdigit(ch)) flag^=ch=='-',ch=getchar();
                while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
                return flag?x=-x:x;
            }
            template<typename type>
            inline void write(type x)
            {
                x<0?x=-x,putchar('-'):0;
                static short Stack[50],top(0);
                do Stack[++top]=x%10,x/=10;while(x);
                while(top) putchar(Stack[top--]|48);
            }
            inline int read(double &x){return scanf("%lf",&x);}
            inline int read(long double &x){return scanf("%Lf",&x);}
            inline void dwrite(const double &x,int y=6,bool flag=1){printf("%.*lf",y,x),flag?putchar('\n'):putchar(' ');}
            inline void dwrite(const long double &x,int y=6,bool flag=1){printf("%.*Lf",y,x),flag?putchar('\n'):putchar(' ');}
            inline char read(char &x){do x=getchar();while(isspace(x));return x;}
            inline char write(const char &x){return putchar(x);}
            inline void read(char *x){static char ch;read(ch);do *(x++)=ch;while(!isspace(ch=getchar())&&~ch);}
            inline void write(const char *x){while(*x)putchar(*(x++));}
            inline void read(string &x){static char ch[50];read(ch),x=ch;}
            inline void write(const string &x){int len=x.length();for(int i=0;i<len;++i)putchar(x[i]);}
            template<typename type,typename...T>
            inline void read(type &x,T&...y){read(x),read(y...);}
            template<typename type,typename...T>
            inline void write(const type &x,const T&...y){write(x),putchar(' '),write(y...),sizeof...(y)^1?0:putchar('\n');}
            inline __int128 read(){static __int128 x;return read(x);}
            template<typename type>
            inline type put(type x,bool flag=1){write(x),flag?putchar('\n'):putchar(' ');return x;}
        }
        #ifndef LOCAL
            #undef getchar
            #undef flush
            #undef putchar
        #endif
    }using namespace IO::usr;
}using namespace ly::IO::usr;

short a;int b;long long c;__int128 d;char e;char f[20];string g;double h;long double i;

signed main()
{
    read(a,b,c,d,e,f,g,h,i);
    write(a,b,c,d,e,f,g),dwrite(h),dwrite(i,10);
    return 0;
}
/*
32767 2147483647 9223372036854775807 170141183460469231731687303715884105727 A 123abcABC!@#$%^ &*()[]{}:""<>'' 3.14 3.1415926535
*/

Updates

2022/10/18

修改 flush,见【下文】。

2022/10/27

新增字符串输入输出,支持 char*string 类型。新引入的 <cctype> 头文件是为了支持 isspace()

2022/10/28

新增 namespace ly::IO::usr 用户接口。先前为避免与其他命名空间变量重名统一采用下划线作为变量前缀,此次增加用户接口可以彻底避免命名冲突的情况出现,且大大增加代码可读性。

2022/10/29

新增浮点数输入输出,支持 doublelong double 类型。「不过由于实现浮点数输入输出太麻烦且优化效果甚微,因此直接用 scanfprintf 实现。。。」

支持控制输出小数位数,原理如下:

#include<cstdio>
signed main()
{
    double a;int b;
    scanf("%lf%d",&a,&b);
    printf("%.*lf",b,a);
    return 0;
}

上面的代码输入一个浮点数 a 和一个整数 b,输出 b 位的浮点数 a

使用方法:

double a;int b;
read(a,b),dwrite(a,b);

上面的代码同样输入一个浮点数 a 和一个整数 b,输出 b 位的浮点数 a。同时 double 可以改为 long doubleput 中可以只有一个参数 a,此时和 c++coutprintf 默认输出六位小数保持一致。

但要注意,由于使用了 printfscanf,因此若要使用小数输入输出,请 #define LOCAL 关闭 freadfwrite 版快读快写!否则二者缓冲区刷新不同步会导致输出有误!!!

2022/10/30

put 参数中的 const type &x 改为 type x,修复字符串输出问题。

2022/10/31

发现 bug:单独用 write 输出字符数组会编译错误,若同时有其他输出则正常运行。以后有时间修复。

2022/11/1

dwrite 函数增加输出空格/换行功能。

2022/11/17

针对即将到来的 noip 对模板略作优化和删减,以便考场使用。

  • 去掉 inline __int128 read(){static __int128 x;return read(x);},因为从来没用过,也没有必要。

  • 去掉浮点数输入输出,因为没有优化实现且有函数调用开销。同时也没有必要,直接用 scanfprintf 即可。

  • 去掉 put 的返回值,因为从来没用到过。

  • 选择性压行。

  • 修复 10月31日 发现的字符数组输出 bug,同时将模板的函数重载改为函数模板特化、偏特化,见【下文】。

  • 真正实现 string 类的读入,不再受到限制。

    过程中引出了一个问题:对单个字符来讲,string+=push_back() 哪个效率最高?

    string 好像没有 emplace_back()。」

    实践出真知。

    #include<iostream>
    using namespace std;
    
    int n=1000000000;
    string s;
    
    int main()
    {
        //for(int i=1;i<=n;++i) s+='a';
        //for(int i=1;i<=n;++i) s.push_back('a');
        return 0;
    }
    

    测试结果:

    • 不开 O2

      += push_back()
      第一组 3.68s 2.85s
      第二组 3.80s 2.86s
      第三组 3.52s 2.85s
    • 开 O2

      += push_back()
      第一组 2.48s 2.65s
      第二组 2.48s 2.64s
      第三组 2.49s 2.53s

    显然,不开 O2 时 push_back() 优于 +=,开 O2 时 += 更优。

    由于今年 CSP-S 的编译选项为 ‐O2 ‐std=c++14,noip 应该也会开 O2。

    因此最终采用 += 实现。

  • 最终模板经过【洛谷IDE】和【正睿oj】测试,暂未发现问题。


「debug:2022/10/18」模板修改:flush

用了这个模板快半个月,一直没有什么问题。直到在正睿比赛中,由于启用了 //#define LOCAL 而使得一道 100 分的题挂掉,才意识到该模板存在问题。

测试了一番:

signed main()
{
    int a,b;read(a,b),write(a,b);
    return 0;
}
  • #define LOCAL,即使用普通版快读快写:

    看起来一切正常,没有问题。

  • //#define LOCAL,即使用 freadfwrite 版快读快写:

    第二行输出了一堆 \0 ???

之前在洛谷交代码时未出现这样的问题,两种情况都能 AC,看来是洛谷自动忽略了文末换行,但正睿没有。

我们知道:

  • \0 是字符串的结束符,任何字符串之后都会自动加上 \0
  • 如果字符串末尾少了 \0 转义字符,则其在输出时可能会出现乱码问题。
  • \0 的 ASCII 码值为 0。

为啥输出这么多 \0 呢?

经过半个小时的反复调试,终于发现问题出来了 flush 上:

#define flush() (fwrite(out,1,SIZE,stdout))

而最后程序结束调用的析构函数:

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

直接调用 flush,输出长度为 SIZE

若输出缓冲区内容长度小于 SIZE,则剩下的会输出 \0!!!

由此,将 flush 略作修改,只输出 p3-out 个内容即可:

#define flush() (fwrite(out,1,p3-out,stdout))

「debug:2022/11/17」模板修改:输出字符数组——重载、特化与偏特化

2022/10/31

发现 bug:单独用 write 输出字符数组会编译错误,若同时有其他输出则正常运行。

修复 bug:将 inline void write(const char *x)const 去掉即可。但这样又会出现一个问题:使用可变参数模板的 write 时会报错,因为参数为 const 类型。

  • 解决方案一

    inline void write(char *x){while(*x)putchar(*(x++));}
    inline void write(const char *x){while(*x)putchar(*(x++));}
    
  • 解决方案二

    template<typename type>
    inline void write(type *x){while(*x)putchar(*(x++));}
    

显然解决方案二更简洁一些。修改后可以正常使用 writeput 输出字符数组。

两种方案的本质区别在于方案一是函数重载、方案二是函数模板偏特化

要搞清楚模板偏特化,首先要清楚什么是模板特化

什么是模板特化?举个简单的例子:

  • 函数重载

    #include<iostream>
    using std::cout;
    
    template<typename type>
    inline type min(type x,type y)
    {
        return x<y?x:y;
    }
    
    inline const char* min(const char* x,const char* y)
    {
        return (strcmp(x,y)<0)?x:y;
    }
    
    int main()
    {
        cout<<min("abc","bcd");
        return 0;
    }
    
  • 函数模板特化

    #include<iostream>
    using std::cout;
    
    template<typename type>
    inline type min(type x,type y)
    {
        return x<y?x:y;
    }
    
    typename<>
    inline const char* min(const char* x,const char* y)
    {
        return (strcmp(x,y)<0)?x:y;
    }
    
    int main()
    {
        cout<<min("abc","bcd");
        return 0;
    }
    
  • 区别

    1. 如果使用普通重载函数,那么不管是否发生实际的函数调用,都会在目标文件中生成该函数的二进制代码。而如果使用模板的特化版本,除非发生函数调用,否则不会在目标文件中包含特化模板函数的二进制代码。这符合函数模板的“惰性实例化”准则。
    2. 如果使用普通重载函数,那么在分离编译模式下,需要在各个源文件中包含重载函数的申明,否则在某些源文件中就会使用模板函数,而不是重载函数。

模板偏特化是模板特化的一种特殊情况,主要分为两种:

  • 一种是指对部分模板参数进行全特化

    #include<iostream>
    using std::cout;
    
    template<typename type>
    inline type min(type x,type y)
    {
        cout<<"test1\n";
        return x<y?x:y;
    }
    
    template<typename type>
    inline type min(int x,type y)
    {
        cout<<"test2\n";
        return x<y?x:y;
    }
    
    int main()
    {
        cout<<min(114514,1ll)<<'\n';
        return 0;
    }
    

    输出:

    test2
    1
    
  • 另一种是对模板参数特性进行特化,包括将模板参数特化为指针、引用或是另外一个模板类

    上面的解决方案二就是一个很好的例子。

    template<typename type>
    inline void write(type *x){while(*x)putchar(*(x++));}
    

    这里将模板参数特化为指针。

优先级:

对主版本模板类、全特化类、偏特化类的调用优先级从高到低进行排序是:全特化类 > 偏特化类 > 主版本模板类。

这样的优先级顺序对性能也是最好的。

更多细节,参见【C++ 模板特化与偏特化】。


参考资料

  1. C++快读快写模板
  2. 【C++基础技巧】5分钟学会竞赛中常用的快读方法,fread优化的终极快读
  3. C++11:模板(可变模板参数)
  4. 【C++】C++11可变参数模板(函数模板、类模板)
  5. C++ 模板特化与偏特化
posted @ 2021-08-30 09:42  凌云_void  阅读(4119)  评论(8编辑  收藏  举报