前言
我最早接触的刷题平台是力扣,这导致我第一次在PTA平台上写题的时候在数据的输入和输出上感到非常折磨。这篇文章是我对各种输入输出的总结,准备开始刷题的同学看这篇文章能帮你节约不少的时间。
缓存区 : 书上的定义是,缓冲区是一块物理内存中的存储区,当数据进行转移时用来临时存放数据。在这里的作用我就把他理解成,在输入和输出时数据时,这些数据都要在缓存区走一遍再去完成他们的功能。
一、普通读写操作
在不考虑读写运行速度的情况下,基本的读入函数就能完成我们所有的读入功能。下面主要介绍不同的读入函数,
1、cin
作为操作起来最方便的读入函数,cin不需要记住任何的多余符号,新手只要知道他在空格和换行符会自动停止读入就行,且不会清空缓存区,对刚刚刷题的小白非常的友好。但我个人是非常不建议用的,速度慢是其次,主要是他在读入一些比较特殊的词时会有些不方便(这个题目写多了肯定会遇到的),最基础的操作相比来说的话我更推荐scanf。
2、scanf
我是非常建议把scanf当成不需要考虑读入效率时的默认输入方式的。一般的scanf使用方法我就不多说了,这里介绍几种在刷题时曾经困扰过我的几种输入。
题目要求读入n个整形(n不确定),存放在arr数组里,输入时每个整形之间会以空格分开,具体样子如下。
当时我不了解scanf遇到空格会结束的特性,这个输入没有完成,其实这种类型的输入通过一句话就可以解决。
while(~scanf("%d",&arr[i++]));//i就是个从零开始的变量
这里用到了scanf的返回值特性,scanf其实是有返回值的(可能就我不知道吧),一次scanf读入几个字符他就会返回几,碰到空格或者TAB等会返回0,而碰到输入结束标志会返回EOF(即-1),所以这里用的是~符号,让-1的时候得到0退出while循环,就是要让他在检测接收到数据和空格时都继续接收下一个,而接收到输入结束标志时退出while即不继续输入。
大部分不确定输入个数的输入通过这个都能解决,但有一个例子不可以:要求将n(n不确定)个字符串放到二维数组arr里,具体如下。
我们先直接看解决代码
while(~scanf("%d",&arr[i++]) && getchar() == ' ');//i就是个从零开始的变量
这里我先解释上面的那一串字符为什么不能用之前的方法解决,在最开始我也不明白为什么这样的字符串输入进来会有问题,后面我看了录进去的值之后我发现其实不管是让我输入字符串还是让我输入整形,我的输入都是有问题的,只不过在字符串输入的时候更容易发现错误。对于第一个输入整形的题目我们写一下就会发现当我们运行完时,如果我们输入3个数,n的值会变成4而不是3,这是因为scanf在读入的时候在把空格,换行等读入到缓存区的时候会结束此次读入,但是这时候缓存区里的空格,换行等不会消失,这就导致我读完最后一个数时其实缓存区里还有\n而不是结束标志,这时候如果下一位是文章结束标志scanf会将\n转换成空格存到我的数组里,当然这时候n++于是导致n比我们想要的值还多一。那为什么加了getchar就好了呢,这我们就要知道getchar的处理字符方式,当缓存区有字符时,getchar会从缓存区读取并清空一个字符,而这里就是用到了getchar的这个特性,不仅可以清空scanf之后缓存区残留的符号还能,让循环在正确的时候退出。
3、gets/getline
这两个分别是c/c++的输入函数,这里要注意的是gets在c++里是不能用的,在这里讲一下这两个函数和scanf有什么区别,前面我们已经说过了scanf遇到空格和回车会停止,但是这里的gets只有遇到回车才会停止,并且会自动把\n转化成\0(gets只会能把数据读到char*里)。还有一点与scanf不同的是scanf会把空格换行等留在缓存区里,只能通过getchar来清除,但gets不会在缓存区里留东西。getline就是c++版的gets。那什么时候会用到这个呢?当我想输入一段字符串但字符串里面包含空格时,我就只能用gets去实现这个功能。
4、printf
所有的输出我都是建议printf的,虽然cout非常的方便,但是学会printf可以满足所有不需要效率时的输出操作。下面主要讲两个我之前不知道的操作。
固定小数位数的输出,这里举个例子1:比如我要输出一个小数后两位的浮点数。
1. double a =2.127; 2. printf("%.2lf\n",a);//控制double类型到两位小数
这里有一点要注意,最后一位显示出来的小数是会自动四舍五入的。
例子2:在整形前面自动补0直到达到指定位数,多用于有补0格式要求的整形输出。
1. int i=2; 2. printf("%02d",i);//不够两位在左边补零,补空格也行
二、快读快写
在有些题目里读写要求的数据量特别大时甚至会要求我们不能使用语言本身提供的函数,而是要求我们对一些不方便但是效率高的函数操作来达到输入输出的功能,这就是快读快写。
快读快写的本质就是用更快的读入函数去实现读写操作,读入操作时间 cin > scanf > getchar > fread.这就衍生出了以下两种快读(getchar版本和fread版本)。以下按照整型的输入来解释。
1. inline int read1()//这里加inline是为了解决一些频繁调用的函数大量消耗栈空间(栈内存)的问题 2. { 3. int x = 0, f = 1; 4. char ch = getchar(); 5. while (ch < '0' || ch >'9') 6. { 7. if (ch == '-') f = -1; 8. ch = getchar(); 9. } 10. while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); 11. return x * f; 12. }
上面根据getchar读入字符的功能去实现读入一个整形,但是这样的函数没法像scanf一样放在while里读入,这就导致如果不告诉你数据个数你就没法很好的读入,于是改进后函数为下面这样,输入检测到\n会让while退出。
1. inline int read2(int* a,int i)//这里加inline是为了解决一些频繁调用的函数大量消耗栈空间(栈内存)的问题 2. { 3. int x = 0, f = 1; 4. char ch = getchar(); 5. while (ch < '0' || ch > '9') 6. { 7. if (ch == '-') f = -1; 8. ch = getchar(); 9. } 10. while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); 11. a[i] = x * f; 12. if (ch == '\n') return 0; 13. return 1; 14. }
从之前所说的我们知道还有一个比getchar更快的函数,接下来我们再用fread去实现getchar,实现一个更快的快读。
1. char buf[100000], * p1 = buf, * p2 = buf; 2. 3. inline char nc() { 4. if(p1 == p2) p2 = (p1 = buf) + fread(buf, 1, 1, stdin); 5. return *p1++; 6. } 7. 8. inline int read3(int* a, int i) { 9. int x = 0, f = 1; 10. char ch = nc(); 11. while (ch < '0' || ch > '9') 12. { 13. if (ch == '-') f = -1; 14. ch = nc(); 15. } 16. while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = nc(); 17. a[i] = x * f; 18. if (ch == '\n') return 0; 19. return 1; 20. }
fwrite(buffer,size,count,fp) buf表示存放读入一个数据的地址指针;size表示读出的字节数;count表示读出数据项的个数;fp表示文件型指针。
同样的我们这时候看快写运行时间 cout > printf > putchar ,下面就是用putchar来实现printf。
1. void write(int x) 2. { 3. if (x < 0) putchar('-'), x = -x; 4. if (x > 9) write(x / 10); 5. putchar(x % 10 + '0'); 6. return; 7. }
1、整形快读快写
1. char buf[100000], * p1 = buf, * p2 = buf; 2. 3. inline char nc() { 4. if(p1 == p2) p2 = (p1 = buf) + fread(buf, 1, 1, stdin); 5. return *p1++; 6. } 7. 8. inline int read3(int* a, int i) { 9. int x = 0, f = 1; 10. char ch = nc(); 11. while (ch < '0' || ch > '9') 12. { 13. if (ch == '-') f = -1; 14. ch = nc(); 15. } 16. while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = nc(); 17. a[i] = x * f; 18. if (ch == '\n') return 0; 19. return 1; 20. } 21. 22. void write(int x) 23. { 24. if (x < 0) 25. putchar('-'), x = -x; 26. if (x > 9) 27. write(x / 10); 28. putchar(x % 10 + '0'); 29. return; 30. }
2、浮点型的快读快写
这方面用到的雀食没见过,而且如果把浮点型写成快读快写也需要很多的函数,效果没那么明显,这里就不写了。
3、字符串快读快写
这里其实也起不到多大的优化效果,看个人意愿是否使用,这里就不用fread了。
1. inline string readstr() { 2. string s = ""; 3. char ch; 4. while ((ch = getchar()) != '\n') s += ch; 5. return s; 6. } 7. 8. inline void writestr(string s) { 9. for (int i = 0; i < s.size(); i++) putchar(s[i]); 10. }
三、O2优化
读入数据量大的时候O2优化能减少很多运行时间,具体原理不需要了解,这里就直接放O2开关。
把下面这句话写到程序前面就好啦。
#pragma GCC optimize(2)
总结
很多的题目都是可以不要快读快写的,很多时候怎么方便怎么来,但是这不意味这就不重要,快读快写在关键时刻说不定真的能救你一命呢。