0x01 前置准备

所有代码依赖以下头文件,建议统一包含:

  • <cstdio>:提供 getchar()putchar()fread()fwrite()
  • <iostream>:提供 cincout
  • <cctype>:提供 isspace()

0x02 基础 I/O 优化:基于 cincout

优化步骤

  1. 关闭流同步
  • 实现:通过 ios::sync_with_stdio(false) 关闭 C++ 和 C 输入输出流的同步;
  • 解释:为了确保混用 C++ 的 cin / cout 和 C 的 printf / scanf 不会产生 I/O 混乱,C++ 和 C 的两种流之间进行了同步。这提高了兼容性,但是产生了大常数。关闭流同步之后就不要同时使用 cinscanf,也不要同时使用 coutprintf,否则会造成 I/O 混乱。但可以同时使用 cinprintf,也可以同时使用 scanfcout
  1. 解除绑定
  • 实现:通过 cin.tie(nullptr) 解除 cincout 的绑定;
  • 解释:在 C++ 中,cin 默认绑定的是 &cout,这意味着每次读入都会调用 flush()。可以用 cin.tie(nullptr) 函数解除这种绑定。
  1. 针对 endl 的优化
  • 实现:用 '\n' 替换 endl
  • 解释:endl 的作用是换行并刷新缓冲区,相当于 cout<<'\n'<<flush。而刷新缓冲区会带来一定开销。

其中前两步一般合称「关流」。后文会沿用这个称呼。

代码实现

以下两种写法是等价的:

// 写法1:链式调用
cin.tie(0)->sync_with_stdio(0);
// 写法2:分步调用
ios::sync_with_stdio(0);
cin.tie(0);

可以这样将所有 endl 替换为 '\n'

// 注意:该宏需在包含<iostream>之后定义,避免与std::endl声明冲突
#define endl '\n'

0x03 进阶优化:快读

普通快读:基于 getchar()

通过 getchar() 函数逐字符读取,手动解析整数或字符串。

void read(int &x){  // 读整数(支持负数)
int c,f=1;
while((c=getchar())<'0'||c>'9') if(c=='-') f=-1;
for(x=c^48;(c=getchar())>='0'&&c<='9';x=(x<<3)+(x<<1)+(c^48));
x*=f;
}
void read(char &c){ // 读一个非空字符
while(isspace(c=getchar()));
}
int read(char s[]){ // 读一个字符串,到空格/EOF为止,返回长度
int len=0;
char c;
while(isspace(c=getchar()));
do s[len++]=c;
while(!isspace(c=getchar())&&c!=EOF);
s[len]='\0'; // 补字符串结束符
return len;
}
int getline(char s[]){ // 读一行字符串,返回长度
int len=0;
char c;
while((c=getchar())!='\n'&&c!=EOF) s[len++]=c;
s[len]='\0'; // 补字符串结束符
return len;
}

缓冲区快读:基于 fread()

getchar() 每次从系统读取 1 个字符,频繁调用系统接口,开销大。fread() 一次性读取一整块数据到自定义缓冲区,后续从缓冲区取字符。这样可以减少系统调用次数,速度通常可以提升 5~10 倍。

缓冲区一般大小设为 1MB 左右,即 \(2^{20}\) 字节,这样既不会占太大空间,不会刷新太多次缓冲区。

由于 fread 可以一次整块读入,因此速度比 getchar 快多了。

char in[1<<20],*p1,*p2;
inline char gc(){ // 从缓冲区读1个字符,空则补充
return p1==p2&&(p2=(p1=in)+fread(in,1,1<<20,stdin))==in?EOF:*p1++;
}

加上这段代码,然后用 gc() 替换掉所有 getchar() 就可以了。

0x04 进阶优化:快写

普通快写:基于 putchar()

通过 putchar() 函数逐字符输出。

void write(int x){  // 写整数(支持负数)
if(x<0) putchar('-'),x=-x;
x<10?putchar(x|48):(write(x/10),putchar(x%10|48));
}
void write(char s[],int len){ // 写字符串,指定长度
for(int i=0;i<len;++i) putchar(s[i]);
}
void write(char s[]){ // 写字符串,直到'\0'为止
for(int i=0;s[i];++i) putchar(s[i]);
}

缓冲区快写:基于 fwrite()

和缓冲区快读差不多,自定义一个缓冲区,每次写一个字符到缓冲区,满了就刷新缓冲区,通过 fwrite() 一次性将整个缓冲区里的内容输出。

加上如下代码,再用 pc() 替换掉所有 putchar() 就可以了。

char out[1<<20],*p3=out;
inline void pc(char c){ // 向缓冲区写1个字符,满则刷新
if(p3-out==1<<20) fwrite(out,1,1<<20,stdout),p3=out;
*p3++=c;
}

但是程序结束时,缓冲区里可能还有东西,因此我们必须在结束前清空缓冲区。这一步千万不要忘!

fwrite(out,1,p3-out,stdout);

0x05 工程化实现:I/O 类封装

封装的核心目的

  • 自动管理缓冲区:析构函数自动调用 fwrite() 刷新输出缓冲区,避免忘记刷新。
  • 统一接口:将快读和快写整合到同一个类中,使用时直接调用 io.函数名(参数) 即可,无需再关注底层实现,适合作为缺省源使用。

代码实现

class IO{
#define SIZE 1<<20
private:
char in[SIZE],out[SIZE],*p1,*p2,*p3;
public:
IO():p1(in),p2(in),p3(out){}
~IO(){fwrite(out,1,p3-out,stdout);}
inline char gc(){ // 从缓冲区读1个字符,空则补充
return p1==p2&&(p2=(p1=in)+fread(in,1,SIZE,stdin))==in?EOF:*p1++;
}
inline void pc(char c){ // 向缓冲区写1个字符,满则刷新
if(p3-out==SIZE) fwrite(out,1,SIZE,stdout),p3=out;
*p3++=c;
}
void read(int &x){ // 读整数(支持负数)
int c,f=1;
while((c=gc())<'0'||c>'9') if(c=='-') f=-1;
for(x=c^48;(c=gc())>='0'&&c<='9';x=(x<<3)+(x<<1)+(c^48));
x*=f;
}
void read(char &c){ // 读一个非空字符
while(isspace(c=gc()));
}
int read(char s[]){ // 读一个字符串,到空格/EOF为止,返回长度
int len=0;
char c;
while(isspace(c=gc()));
do s[len++]=c;
while(!isspace(c=gc())&&c!=EOF);
s[len]='\0'; // 补字符串结束符
return len;
}
int getline(char s[]){ // 读一行字符串,返回长度
int len=0;
char c;
while((c=gc())!='\n'&&c!=EOF) s[len++]=c;
s[len]='\0'; // 补字符串结束符
return len;
}
void write(int x){ // 写整数(支持负数)
if(x<0) pc('-'),x=-x;
x<10?pc(x|48):(write(x/10),pc(x%10|48));
}
void write(char s[],int len){ // 写字符串,指定长度
for(int i=0;i<len;++i) pc(s[i]);
}
void write(char s[]){ // 写字符串,直到'\0'为止
for(int i=0;s[i];++i) pc(s[i]);
}
#undef SIZE
}io; // 全局实例化,无需重复创建对象

0x06 关键避坑指南

  1. 混用不同 I/O 方式

    • 关流后不能混用 cinscanf,也不能混用 coutprintf
    • 自定义缓冲区快读与 getchar() 不可混用,缓冲区快写和 putchar() 也不能混用;
  2. 缓冲区快写忘记刷新:非封装版本需在程序结束前调用 fwrite(out,1,p3-out,stdout),否则缓冲区剩余数据不会输出;
  3. 整数快读快写处理边界值:处理 INT_MIN 即 \(-2147483648\) 时会溢出,若需支持要开 long long

0x07 性能对比与选型建议

I/O 方式 速度排序 优点 缺点 适用场景
缓冲区快读快写 1 速度极致,适合大数据 代码长,需封装,不支持复杂类型(如浮点数) 输入超大,高频 I/O 的题目
普通快读快写 2 代码短,速度快 不支持复杂类型(如浮点数) 输入较大,需要卡常的题目
关流 cin / cout 3 代码超短 兼容性差 一般题目
scanf / printf 4 适合输出格式串 格式控制符复杂,速度较慢 一般题目
不关流 cin / cout 5 兼容性好 速度极慢 极小数据量的题目,调试输出

注:若需支持浮点数(如 double),需扩展快读快写函数,手动解析小数点前后数字。因浮点数 I/O 场景较少,通常含有浮点数的题目数据量也不会太大,本文暂不展开,可根据需求自行扩展。

C++ I/O 终极加速指南,全网最全整理的更多相关文章

  1. WordPress SEO ☞ WordPress网站终极优化指南

    原文地址:http://www.eastdesign.net/wordpress-seo/ 最新消息,东方设计学院 WordPress SEO 系列视频教程正在持续更新中,目前为了不至于让视频传播过于 ...

  2. Python实现代码统计工具——终极加速篇

    Python实现代码统计工具--终极加速篇 声明 本文对于先前系列文章中实现的C/Python代码统计工具(CPLineCounter),通过C扩展接口重写核心算法加以优化,并与网上常见的统计工具做对 ...

  3. (zhuan) 深度学习全网最全学习资料汇总之模型介绍篇

    This blog from : http://weibo.com/ttarticle/p/show?id=2309351000224077630868614681&u=5070353058& ...

  4. 全网最全ASP.NET MVC 教程汇总

    全网最全ASP.NET MVC 教程汇总 MVC架构已深得人心,微软也不甘落后,推出了Asp.net MVC.小编特意整理博客园乃至整个网络最具价值的MVC技术原创文章,为想要学习ASP.NET MV ...

  5. 自学MVC看这里——全网最全ASP.NET MVC 教程汇总(转)

    自学MVC看这里——全网最全ASP.NET MVC 教程汇总   MVC架构已深得人心,微软也不甘落后,推出了Asp.net MVC.小编特意整理博客园乃至整个网络最具价值的MVC技术原创文章,为想要 ...

  6. 自学MVC看这里——全网最全ASP.NET MVC 教程汇总【转】

    自学MVC看这里——全网最全ASP.NET MVC 教程汇总 http://www.cnblogs.com/powertoolsteam/archive/2015/08/13/4667892.html ...

  7. Mybatis系列全解(四):全网最全!Mybatis配置文件XML全貌详解

    封面:洛小汐 作者:潘潘 做大事和做小事的难度是一样的.两者都会消耗你的时间和精力,所以如果决心做事,就要做大事,要确保你的梦想值得追求,未来的收获可以配得上你的努力. 前言 上一篇文章 <My ...

  8. 【全网最全的博客美化系列教程】08.自定义地址栏Logo

    全网最全的博客美化系列教程相关文章目录 [全网最全的博客美化系列教程]01.添加Github项目链接 [全网最全的博客美化系列教程]02.添加QQ交谈链接 [全网最全的博客美化系列教程]03.给博客添 ...

  9. 全网最全的Windows下Anaconda2 / Anaconda3里正确下载安装爬虫框架Scrapy(离线方式和在线方式)(图文详解)

    不多说,直接上干货! 参考博客 全网最全的Windows下Anaconda2 / Anaconda3里正确下载安装OpenCV(离线方式和在线方式)(图文详解) 第一步:首先,提示升级下pip 第二步 ...

  10. 全网最全的Windows下Anaconda2 / Anaconda3里正确下载安装OpenCV(离线方式和在线方式)(图文详解)

    不多说,直接上干货! 说明: Anaconda2-5.0.0-Windows-x86_64.exe安装下来,默认的Python2.7 Anaconda3-4.2.0-Windows-x86_64.ex ...

随机推荐

  1. stylus - 新生代CSS预处理框架

    stylus是什么 Stylus 是一种 CSS 预处理器,它扩展了 CSS 的功能,使得编写样式变得更简洁和高效.Stylus 允许使用嵌套.变量.混入等编程功能,这些功能可以极大地提高开发效率和代 ...

  2. 雷池 7.x 主从节点分钟级自动同步 + 手动切换实战教程

    雷池7.x版本新增配置同步功能,可以设置主节点和从节点,可以自动每分钟将主节点的配置同步到从节点,在主节点异常情况下,使用者手动切换流量后,实现从节点马上承接业务流量. 准备环境 ● 检查主从节点机器 ...

  3. Python之multiprocessing.Pool(创建多个子进程)

    当需要创建的子进程数量不多的时候可以直接利用multiprocessing中的Process动态生成多个进程.但是,如果是成百上千个任务,手动创建显然不合适,此时就可以用multiprocessing ...

  4. Vertx 接入Redis (八)

    项目github地址:https://github.com/fotocj007/VertxWebApi web服务器经典的mysql+redis模式,这里介绍redis的接入. 一:导入gradle ...

  5. docker入门基操~~~

    # Docker基本使用方式 - [Window 使用 Docker 创建lnmp环境 ](#introduction) - [常用docker命令 ](#command) ### [安装详情](ht ...

  6. css3 position定位—— sticky 定位

    一.概念sticky 定位sticky 英文字面意思是粘,粘贴,所以可以把它称之为粘性定位. position: sticky; 基于用户的滚动位置来定位. 粘性定位的元素是依赖于用户的滚动,在 po ...

  7. SQL Server 配置管理器打不开提示错误

    ---------------------------SQL Server 配置管理器--------------------------- 无法连接到 WMI 提供程序.您没有权限或者该服务器无法访 ...

  8. NVelocity模版引擎

    NVelocity模版引擎 需要引用外部的NVelocity.dll文件 /// ///Person类 的摘要说明 /// public class Person { public Person() ...

  9. centos8 yum替换阿里源

    解决centos7使用yum安装mysql 下载速度慢的问题 挺好用的,之前用腾讯云安装了半天,太慢了,改过之后速度快多了. 1.首先备份系统自带yum源配置文件/etc/yum.repos.d/Ce ...

  10. vue被部署到子(二级)目录

    需求有的时候,你的域名很珍贵,除了二级域名外.你还可以将你的项目部署在服务器二级目录下,这样的话,就可以部署多个项目了.比如说,我有一个域名为dshvv.com的服务器,我想部署两个项目:12306项 ...