【2024最新】C++读写优化超详细解析(cin优化+普通快读+fread)_输入输出优化_快读快写_算法竞赛

在算法竞赛中,读入速度和输出速度一直是卡常的重要手段。也有不少人经历过被题目卡 cin 的情况。今天我给大家介绍一下算法竞赛中常用的读写方法及其优化。

声明:大部分情况下,只用读入优化就行。对于输出量大的题目再考虑使用输出优化。

一、scanf/printf

这两个函数来源于传统的 C,在 <stdio.h><cstdio>(仅 C++)中。使用方法:scanf("%占位符",&var); printf("%占位符\n",var);。这样比传统的 cin/cout 快,但由于书写复杂而很少被人采用。

scanf/printf 用法参考文章:详解c++中scanf和printf用法

二、cin/cout 优化

cin 和 cout是 C++ 的标准输入输出流,在 <iostream> 头文件中,使用方法也十分简单:cin>>var; cout<<var<<endl; 所以深受人们的喜爱。
在时间要求不高的题目中,用它们既省事,又可以让代码更美观。

缺点:cin/cout 在不加优化的情况下速度慢于 scanf 和 printf。

原因:C++ 为了和 C 保持同步、在混用 printf&scanf 和 cin&cout时的时候不发生混乱,将它的输入/输出流绑到了一起。

解决方案:可以手动关闭同步,从而提高 cin 和 cout 的效率。std::ios::sync_with_stdio(false); 同时,还可以通过std::cin.tie(nullptr); 来解除 cin 和 cout之间的绑定,进一步减轻 cin 的负担。

cin/cout 用法:C++ 基础的输入输出介绍:掌握cin与cout的奥秘

三、endl 优化

有一个被人忽略的“大杀手”:endl。这一个语句和 \n 的效果相同,但更方便书写,因此被人们广泛采用。其实它的速度也特别慢。可能会导致某些题目超时。

原因:endl 输出时会清空缓冲区,这是为了让用户能及时看到输出,但会拖慢速度。并且由于它本身的功能定义,很难进行优化。

解决方案:打不过就不用呗 对于输出量大且需要换行的题目,使用 endl 有超时的风险,建议使用 cout<<'\n'。(平常刷题也建议用 \n,比较保险)

在实测中,C++14 的版本下,cin/cout 优化 + endl 优化的速度已经远快于 scanf/printf,建议在新评测机上使用上面两种方法优化

#include<iostream>
using namespace std;
int a[105];
int main(){
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
	    cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cout<<"a["<<i<<"]="<<a[i]<<"\n";
	}
    return 0;
}

四、传统快读、快写

原理:单个字符的输入输出速度要比读入数字快,因此我们以字符的形式先读入,然后处理计算转为数字,再用字符输出。

关于 isdigit()

在判断某个字符是不是整数时用的 isdigit() 函数,在不同编译器上的速度不一样。

这篇文章 中的测试结果:自己写比 VS 中的 isdigit() 快,但比 g++ 中的 isdigit() 慢。而且 g++ 中的 isdigit() 比 VS 中的 isdigit() 更快。

这篇文章 给我们展示了 Linux 内核中该函数王者级别的实现,大家可以学习。

自己写的话:

#define my_isdigit(c) ( (c)>='0'&&(c)<='9' ) // C语言
inline bool my_isdigit(char c){ return (c>='0'&&c<='9'); } // C++

快读

原理是先用单个字符读入所有零散的数字,再拼起来。拼数和判断字符见 这篇文章

template<typename T>
inline void read(T &x){
	T w=1; x=0; 
	char c=getchar(); 
	while(!isdigit(c)){ if(c=='-'){w=-1;} c=getchar();}  
	while(isdigit(c)){ x=x*10+(c-'0'); c=getchar();} 
	x=x*w;
}

快写

原理是把原来完整的数字拆成一个个字符输出。用递归的常数会略大,可以用模拟栈的方法时限。

递归代码:

template<typename T>
inline void write(T x){
    if(x<0){ putchar('-'); x=-x;}
    if(x>=10){ write(x/10);}
    write(x%10+'0'); //+'0'是为了让数字类型的x%10变成字符输出
}

模拟栈代码:(不能用模板了,要自己指定类型)

inline void write(int x){
    static int st[35] = {0}; // int 最多32位
    int top = 0; // 指向栈顶的指针
    do{
        st[top++]=x%10;
        x/=10;
    }while(x>0);
    while(top>0){ putchar(st[--top]+'0');}
}

这里有一个细节,把 st 数组声明为 static 静态类型,可以在申请空间时只申请一次,同时降低常数。static 关键字详细用法见 这篇文章

五、位运算优化快读、快写

原理:通过位运算加速拼数、取数及转换数字的过程。

实现如下:

template<typename T>
inline void read(T &x){
	T w=1; x=0; 
	char c=getchar(); 
	while(!isdigit(c)){ w|=(c=='-'); c=getchar();}  
	while(isdigit(c)){ x=(x<<1)+(x<<3)+(c^48); c=getchar();} 
	x=x*w;
}

template<typename T>
inline void write1(T x){
    if(x<0){ putchar('-'); x=(~x)+1;}
    if(x>=10){ write(x/10);}
    write(x%10^48); //+'0'是为了让数字类型的x%10变成字符输出
}

inline void write2(int x){
    static int st[35] = {0}; // int 最多32位
    int top = 0; // 指向栈顶的指针
    do{
        st[top++]=x%10;
        x/=10;
    }while(x>0);
    while(top>0){ putchar(st[--top]^48);}
}

上面一些位运算优化的解释参见:这篇文章

六、优化快读、快写 plus

原理:使用 C 中的 fread()fwrite() 函数,把数据当作字符串 直接从文件中读入/向文件中输出。加快 getchar() 函数。
注:文件也可以是标准输入输出流 stdinstdout

fread、fwrite 用法

fread 函数:从一个文件流中读取数据
函数原型如下:

size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
  -- buffer:指向数据块的指针,读取的数据存储在此
  -- size:每个数据的大小,单位为Byte 例如:sizeof(int)就是4
  -- count:要读入的数据个数
  -- stream:文件指针,指向要读入的数据

fwrite 函数:将一块内存区域中的数据写入到本地文本
函数原型如下:

size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
   -- buffer:指向数据块的指针,待写入的数据存储在此
   -- size:每个数据的大小,单位为Byte 例如:sizeof(int)就是4
   -- count:要写入数据个数
   -- stream:文件指针,指向待写入的数据

拓展:两个函数随着传入参数不同,返回值的意义也不同。

  1. fread(buf,sizeof(buf),1,file):读取数据量正好是 sizeof(buf) 个 Byte 时,返回值为 1(为 count)。
  2. fread(buf,1,sizeof(buf),file):读取成功返回值为实际读入的数据个数(单位为Byte)。
  3. fwrite(buf,sizeof(buf),1,file):成功写入返回值为 1(为 count)。
  4. fwrite(buf,1,sizeof(buf),file):成功写入则返回实际写入的数据个数(单位为Byte)。

更具体的详细用法参见 这篇文章

优化快读

char buf[1<<20],*p1,*p2;
#define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf,1,1<<20,stdin), p1 == p2) ? 0 : *p1++)
template <typename T>
inline void read(T &x){
	T w=1; x=0;
	char c=gc();
	while(!isdigit(c)){ w|=(c=='-'); c=gc();}
	while(isdigit(c)){ x=(x<<1)+(x<<3)+(c^48); c=gc();}  
	x=x*w; 
}

优化快写

const int bsiz = 1000000;
int sta[30];
char buf[1<<20], pbuf[1<<20], *p = pbuf, *s = buf, *t = buf;

#define put1(ch) (p - pbuf == bsiz ? fwrite(pbuf, 1, bsiz, stdout), p = pbuf, *p++ = ch : *p++ = ch)

inline void putint(int x){
    register int top = 0;
    if (x<0) put1('-'), x = -x;
    do{
       sta[top++]=x%10;
       x/=10;
    }while(x>0);
    while (top) put1(sta[--top]^48);
}
// ...
int main(){
	// ...
	fwrite(pbuf, 1, p - pbuf, stdout); // 程序最后一定要加上这一句,让存储到输出流里的内容输出
	return 0;
}

End

本文就到这里啦~ 有不完善的地方日后会加以完善的(咕了几篇文章

这里是 YLCHUP,拜拜ヾ(•ω•`)o

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值