浅谈c++的输入输出&输入输出优化

一般我们使用 C + + C++ C++ 输入时,会使用 s c a n f scanf scanf c i n cin cin,但其实他们是很慢的,有时候做题,即使算法优秀,但如果输入或输出时就几乎要超时,那就基本没救了。所以,对输入输出的优化是十分必要的。

还记得我们机房有一位卡常神仙,在一次比赛中,面对一道大难题,全机房都打了暴力。众人都是 20 20 20 分,唯独那位神仙凭借一手高超的卡常神技以及高人一等的输入输出优化,愣是卡到了 40 40 40 分……

如果这个例子还不能让你认识到输入输出优化的重要性,那么,我们来看看这几种输入方式的差距:

我们采用这几种输入方式,读入 10000000 10000000 10000000 个整型数据( 999999999 999999999 999999999),他们花费的平均时间如下:

输入方式时间/ms
cin7024
scanf3417
getchar650
fread521

可以发现, f r e a d fread fread 的速度约是 c i n cin cin 14 14 14 倍, s c a n f scanf scanf 7 7 7 倍。

这应该是最好的证据了。

进入正题

读入

scanf

这个东西是C语言的标准输入函数,使用它的时候,你得告诉它你需要读入什么类型,然后它会在缓冲区里面给你找对应的。它在缓冲区里面有一个指针,每次把指针右移一位,然后进行匹配,假如和约定的不一样,就停下来。

比如说,你写了这样一个东西:scanf("%d",&a);,然后给他的数据是123P,它读完 123 123 123 之后,发现还有个 P P P,并不是它要的,他要的是一个 %d,一个整型,所以这个 P P P 还会被留在缓冲区。

还有,scanf 里面一般约定的分隔符号是空格和回车。

由于有了这个复杂的过程,于是变得比较慢,但是很方便。

cin

这个东西是C++语言的标准输入函数,但是和scanf不兼容。

不兼容是指什么呢?他不能和scanf共用那个指针(天知道为啥要这么搞……反正用就是了)。这就导致了它每次读入时都要很无奈的去找scanf的那个指针,慢就慢在这里……(当然找到之后也是会拉着这个指针一起跑的)

但是它很方便,不需要你告诉他你需要读入什么类型,把变量丢给他就好了,他自有办法处理。

cin的读入用到>>这样一个东西,发现这和位运算的右移是一样的,那为什么能实现读入?!事实上,这个东西是被他重载过的。他甚至还支持你自己去重载这个东西,然后你就可以cin一个class啊、一个struct啊、一个map啊,都是没有问题的。

当然,C++也给你提供了一个很优秀的操作(不知道C有没有,没用过……)——关闭同步,也就是关闭与 stdin 指针的同步,自己读自己的。这样的话 cin 甚至比scanf还快!

关闭同步的代码:

std::ios::sync_with_stdio(false)

后面的 sync_with_stdio(false)的意思就是synchronization_with_standardIO(false),表示同步性_与_标准输入输出(关闭),也就是关闭与标准输入输出的同步。知道意思后应该好记很多。

getchar

getchar的作用是读取一个字符,实现起来是这个样子的。

void read(int &x)
{
	x=0;int f1=1;char ch=cn();
	while(ch<'0'||ch>'9'){if(ch=='-')f1=-1;ch=cn();}//不是0~9之内的字符都不要
	while(ch>='0'&&ch<='9')x=x*10+(ch-'0'),ch=cn();
	x*=f1;
}

(需要读入负数的话判断一下第五行有没有读入到一个负号即可)

烦是比scanf和cin烦了点,但是重要的是快呀~

因为它仅仅帮你读入了一个字符,并没有帮你弄成一个整形、浮点数之类的东西,需要你自己去搞,你自己来写的话就可以免去很多判断,因为你很清楚你需要读入什么类型,然而cin不知道,需要自己去判断;scanf也需要你去告诉他,然后它也需要繁琐的判断,所以你自己来当然要快很多。

而且!getchar这个东西还有个很优秀的操作——它会带着scanf的那个指针一起跑!这样的话就可以与scanf和cin混用了。

fread

每一次的读入都是需要准备的,用多少次getchar,就需要准备多少次,然而getchar每次准备好后,只读取一个字符,如果每次准备好后,读取多个字符,不就少了很多次准备的时间吗!

于是 fread 应运而生。

fread 函数中有4个参数,分别设它们为 a , b , c , d a,b,c,d a,b,c,d,就是这样的:

fread(a,b,c,d);

它们的意义分别是:
a: 这是一个指针类型,指向一个 c h a r char char 类型, f r e a d fread fread 会将读入读到的字符逐个逐个地塞到 a 、 a + 1 、 a + 2 、 … a、a+1、a+2、… aa+1a+2中,这里的 + 1 +1 +1 是指指针向后移一位,移的字节数根据其指针类型而定(实际上指针并不能 + 1 +1 +1,但是能 + + ++ ++)。
b: 表示读入的数据的字节数,如果我需要将读入的数据弄成 c h a r char char 来存,那么 b b b 就等于1,但是不可以为了方便,直接将 b b b 弄成 4 4 4,试图直接将整数读进来。因为 f r e a d fread fread 读入的时候是将1234这样的数字统一当做字符读进来的,如果你强行将他们的二进制编码拼凑在一起的话,是不可能得到想要的数的。
c: 如果说 b b b 表示读入的块的大小的话,那么 c c c 就表示要读多少个块。显然,我们一般不知道要读入的数据的长度,所以,这个 c c c 一般设大一点,当没有东西读入时,它自然会停下,所以不用担心出什么问题。
d: 这是也是一个指针类型,但他指向一个文件,表示我们的数据从哪里读取。一般使用 stdin 这个系统帮我们搞好的指针,他会在exe文件所在目录下寻找输入文件。(用就完事了~,原理什么的不用管)

另外,因为fread这个函数是有返回值的,它会返回成功读入的块数,于是,我们可以利用它乱搞。


代码示例:

inline char cn()//这个cn函数的用处类似于getchar,但是用fread实现,他先读取一堆字符
{//然后一个一个返回给你用
	static char buf[10000010],*t1=buf,*t2=buf;
	//t1和t2表示buf中有用数据所在区间的起点和终点,注意这里一定要有static
	return t1==t2&&(t2=(t1=buf)+fread(buf,1,1000000,stdin),t1==t2)?EOF:*t1++;
}
void read(int &x)
{
	x=0;int f1=1;char ch=cn();
	while(ch<'0'||ch>'9'){if(ch=='-')f1=-1;ch=cn();}
	while(ch>='0'&&ch<='9')x=x*10+(ch-'0'),ch=cn();
	x*=f1;
}

如果没看懂上面那个 return 的话,这里有解释:
如果 t1 不等于 t2,那么就会进入

*t1++;

这个语句,它会返回 t1 这个指针所指向的那个 char 类型的变量的值。
但如果满足了

t1==t2

这个条件,那么就是说 buf 中已经没有有用的数据了,那么我们就尝试读入新的数据。也就是

t2=(t1=buf)+fread(buf,1,1000000,stdin)

这一句,首先要知道,=运算符是有返回值的,例如a=b,那么它的返回值就是a也就是说,t2=(t1=buf)这一句就相当于实现了将 t1 和 t2 的值都变成 buf 的操作,但是还没完,t2 还要加上新读取的数据的数量,这样,t1和t2就形成了一个区间。

然后到了很重要的部分

,t1==t2

还是要讲一个重要的前置芝士,我们要隆重地请出——逗号运算符!没错,,也是个运算符,例如a,b,c,d,e,它的返回值就是e,也就是最后一项,完全跟前面几项没有关系(所以它也被用来连接多个语句,如a=b,k++;)。所以

(t2=(t1=buf)+fread(buf,1,1000000,stdin),t1==t2)

这一大串的返回值就是t1==t2,如果尝试读取数据后,t1 依然等于 t2,说明已经没有数据了,那么这个时候,cn()这个函数就会返回一个 EOF,表示没有东西了。(EOF的全称是 End Of File)


所以 fread 一般配合 freopen 来使用,不能手动在窗口输入,因为 fread 会用 stdin 来寻找输入文件,除了使用 freopen,你几乎不能实现在程序运行前完成输入。

以及,他也是会带着 scanf 的指针一起跑的,因为他也是用stdin这个指针,但是他却不能与 scanf 混用,为什么呢?

因为 fread 的用法是先读取一大堆字符,存起来,慢慢用。而你想用 scanf 读取的内容很可能早就给 fread 拐跑了,那还读个球嘛。

输出

学完输入后,就发现输出是和输入十分类似的。

printf

他和 scanf 类似,需要你告诉它你需要输出什么,然后他就对着那个字符串一个一个找你给他的变量,然后输出。

看这一段代码,你觉得会输出什么呢:

#include <cstdio>

int a=0;

int main()
{
	printf("%d %d %d\n",++a,++a,++a);
}

不明白 printf 的工作方式的读者应该都猜错了,正确的结果是:
在这里插入图片描述
为什么呢?

原因是:printf 是从右往左读取的,它会把读取到的东西存到一个栈里面,然后再一个一个弹出来。

所以,在 printf 里面不要随便用 + + ++ ++ − − -- 这类东西,一不小心就完蛋了。

cout

这个东西用到<<,也是重载过的,他也是一个方便的输出。

但是与 scanf 和 cin 的关系类似,它和 printf 也有一个奇妙的联系:他们也会同步。输出时一般有一个缓冲区,因为电脑的 cpu 比输出设备(如显示屏)是要快很多的,假如每次输出时都直接丢给输出设备,那么输出设备会跟不上,可能导致输出设备难以响应别的输出要求,所以一般输出时有一个缓冲区,先存一大堆,然后一起给输出设备一起输出,这样就快了很多(也就是少了很多准备,类似getchar与fread的区别)。

但是,printf 的缓冲区似乎和 cout 的缓冲区不是一样的,这就导致了,假如你把 printf 和 cout 混用,那么这两个家伙的缓冲区里面的内容就需要有一个先后输出顺序,然而我们知道这两个家伙不兼容,而且 printf 出现的要早,那么他自然会傲娇的不管 cout,自顾自的输出,所以只能委屈 cout 去与他同步了,这就导致了 cout 略慢于 printf。

当然,cout 和 cin 一样,他们的方便性是毋庸置疑的。

这里举个栗子:假如关闭他们的同步,混用起来的效果是这样的:

#include <cstdio>
#include <iostream>
using std::cout;

int main()
{
	std::ios::sync_with_stdio(false);
	cout<<"A";
	printf("B");
}

会输出:
在这里插入图片描述

putchar

这个函数的功能是输出一个字符,跟 getchar 大概是对应的。

这个东西跟 printf 应该用的是同一个缓冲区,因为你看这个代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

char A='A',B='B',C='C';

int main()
{
	std::ios::sync_with_stdio(false);
	cout<<A;
	putchar(B);
	printf("%c",C);
}

然后输出是这样的:
在这里插入图片描述
这个东西用起来和 getchar 差不多的,输出的时候把数拆成一位一位再 putchar 出去即可,像这样:

void print(int x,int type=0)
{
	static int op[20],t;t=0;
	if(x==0)op[++t]=0;//注意要判断0的情况
	while(x)op[++t]=x%10,x/=10;
	while(t)putchar(op[t--]+'0');
	if(type)putchar(' ');else putchar('\n');
}
fwrite

fwrite 与 putchar 的区别类似与 fread 与 getchar 的区别,就是把字符先存起来,存的差不多了再一起输出。

而 fwrite 的四个参数也可以类比 fread,所以可以这样写:

char OP[1000030],Zhan[30];int op=0,Top;
void fcheck(int type=0){if(type||op>=1000000)fwrite(OP,1,op,stdout),op=0;}
template<class TY>inline void write(TY x)
{
	if(x==0){OP[op++]='0';fcheck();return;}
	Top=0;while(x)Zhan[++Top]=x%10+'0',x/=10;
	while(Top)OP[op++]=Zhan[Top--];fcheck();
}
#define K_G OP[op++]=' '

int main()
{
	//...
	fcheck(1);return 0;
}

这篇咕了一年的博客终于补完了qwq

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值