【记录】C/C++-关于I/O的坑与教训
吐槽
每每读取字符串时,倘若稍有灵活的操作,总会遇上诡异奇怪的事情。究其原因,就是没完全理解一些基本读写函数的机制。这次做Uva227就把I/O上的问题全暴露出来了。想来还是应该记录一些经验教训。
记录
操作系统的缓冲区管理机制,是一切奇怪事件的罪魁祸首!用户输入的数据将先进入缓冲区,待键入'\n'后,系统才会一次性将缓冲区的内容推给函相关数处理。如果不能准确把握缓冲区内容与变动,便无法精准高效的完成读取和存储。而缓冲区内容的变动,和每一个函数的机制有关。
1. scanf中的格式化字符串
scanf(<格式化字符串>[,地址表]);
格式化字符串中包含三类字符:格式说明符、空白字符(空格、\n、\t等)、非空白字符。问题往往产生于格式说明符和空白字符之间。
"%s": 读取缓冲区中从当前位置开始第一个非空白字符到下一个空白字符之间的内容,存入相应变量(会补上'\0',所以不用担心该字符串的初始化)。之前的空白字符将被读取并忽略,但之后的空白符不会被读取清理,仍保留在缓冲区中(这也表明空白字符不会存入字符串中,让我们省心不少)。
"%[]": 应用扫描字符集之后要注意,不符合要求的字符将仍会留在缓冲区中。举例而论,使用scanf("%[^\n]")读取之后,缓冲区中至少会剩下'\n',如果不注意的话,由于空白字符的诡异性会给之后的I/O造成障碍。因而最好的办法是之后紧跟一个%*c丢弃其后一个字符。scanf("%[^\n]%*c")而不使用scanf("%[^\n]\n"),是因为忌惮空白字符的怪异读取性质。
"\n"等空白字符: 除去%c,scanf中对缓冲区中空白字符都采取“一视同仁,消极忽略”的态度,任何一个空白字符都能代表其他所有的空白字符。scanf("\n")、scanf(" ")等都是一个含义:读取并忽略此后的空白字符(串)。

同时,对空白字符的读取操作未必是即时的:当缓冲区中只剩下类似'\n'的空白字符时,scanf并不会进行操作;等到下一个非空白字符出现,才会开始处理。例如:
char s[10];
scanf("%s\n",s);
printf("%s");
| 操作前缓冲区 | 操作后缓冲区剩余 | 完成操作解释 |
|---|---|---|
| "asd\n" | "\n" | %s将"asd"读取并存入s中,因为只有空白字符"\n"并未被匹配读取 |
| "\nd\n" | "d\n" | 由于缓冲区键入了"d",scanf开始运作,前一个"\n"被匹配读取。最后缓冲区残余"d\n" |

"%c": 最单纯的一种,把一切当作平等的字符,既不多读也不少读——因此常用来清理缓冲区丢弃残余字符。%*c代替\n清理空白字符,因为后续若直接键入空格,使用\n可能会被当作空白字符直接丢弃,导致事故发生。
2. fgets中读取size
char str[SIZE];
fgets(str,SIZE,stdin);
当我们规定fgets只能读取SIZE个字符时,它会贴心的为'\0'预留出最后一个位置。因此,字符串str最多只能读取存入SIZE-1个实体字符。
同时,fgets并不会歧视所读取到的"\n",会将其存入目标字符串str中。简而言之,fgets后str中末尾的"\n"(小心Linux下读取Windows文件时的"\r\n",似乎和语言特性有关,先不管)来自于缓冲区而非函数自己添加。所以当缓冲区内容超过SIZE-1个时,其后的内容会残留,也包括最后的"\n"。
3.string类型行读取
istream& getline(istream& is,string& str[,char delim]);//delim默认为'\n'
会丢弃末尾的'\n'。按此机制,如遇空行,则丢弃换行符,str中内容为空。返回值:只有当读取失败(如EOF)才会为假,其余情况都会为真,while中都会继续循环
则忽视空行的写法
while( getline(cin,str),str.size()==0 );
4.cin,cin.getline()与cin.get()
1.两个函数从缓冲区中读取小于等于ASIZE个字符,遇到'\n'时终止。
cin.getline(arr,ASIZE):读取'\n',并将其替换为'\0';读取ASIZE个字符后强制结束,并将末位替换位'\0',同时设置failbit,阻断cin之后读取,可以通过cin.clear()清除;遇空行,不会设置failbit。
cin.get(arr,ASIZE):不读取'\n',会将其留在缓冲区,读入段后添加'\0';读取ASIZE个字符后强制结束,并将末尾替换为'\0',但不会设置failbit;遇空行,却会设置failbit。
cin.get():重载了函数,读取下一个字符,包括换行符,可以借此消除cin.get遗留的换行符
/*利用函数重载和类的特性*/
cin.get(arr,ASIZE).get();
cin.getline(arr1,S1).getline(arr2,S2);
2.cin >>类似scanf(%s),读取从当前位置开始第一个非空白字符到下一个空白字符之间的内容。
所以它会将换行符留在缓冲区,因而混合数据类型读取时需要注意,可以用前面的方法解决
(cin >> year).get();
令人火大的实例
调试了几乎一天的题目:UVa227 Puzzle。考的就是输入输出
改的千疮百孔,好些地方可以优化。
#include<stdio.h>
#include<string.h>
char map[5][6];
int x,y;
void findSpace();
int move(int i,int j);
int main(){
int cnt=0;
while(fgets(map[0],6,stdin)!=NULL && map[0][1] && map[0][1]!='\n')/* 注意有空格 */ /* 文件Z后是否有\n,也要考虑 */
{
if(map[0][4]!='\n') scanf("%*c"); /* 考虑到倘若第一行没空格读满,则"\n"会留在缓冲区,若不清理,则其后的扫描字符无法读取 */
scanf("%[^\n]%*c%[^\n]%*c%[^\n]%*c%[^\n]",map[1],map[2],map[3],map[4]);
/* 注意scanf("\n");的运作形式 */
for(int i=0;i<5;++i)
if(map[i][4]=='\n') map[i][4]=' ';
/* 处理空格在行末的情况,UVa样例输入里没有给出空格直接换行了;还打多了一个等号 */
if(cnt) puts(""); /* 非统一空行 */
printf("Puzzle #%d:\n",++cnt);
findSpace();
char cmd=0;
int isok=1; printMap();
while((cmd=getchar())!=EOF && cmd!='0')
{
if(isok==0 || cmd=='\n') continue;
switch(cmd){
case 'A':isok=move(-1,0);break;
case 'B':isok=move(1,0);break;
case 'L':isok=move(0,-1);break;
case 'R':isok=move(0,1);break;
default :isok=0; /* 根据Uva英文输入的描述,非法操作包括输入其他字符 */
}
// if(!isok){
// printf("This puzzle has no final configuration.\n");
// break; /* 如果这里就break的话,后续还有待输入的序列残留缓冲区,会影响后续IO */
// }
}
if(isok)
{
for(int i=0;i<5;++i){
for(int j=0;j<5;++j){
putchar(map[i][j]);
if(j!=4) putchar(' ');
}
puts("");
}
}
else printf("This puzzle has no final configuration.\n");
scanf("%*c");
}
return 0;
}
void findSpace(){
for(x=0;x<5;++x) for(y=0;y<5;++y)
if(map[x][y]==' ') return;
}
int move(int i,int j){
x+=i,y+=j;
if(0<=x && x<5 && 0<=y && y<5){
map[x-i][y-j]=map[x][y];
map[x][y]=' ';
return 1;
}
else return 0;
}
【记录】C/C++-关于I/O的坑与教训的更多相关文章
- 记录hyperic-hq搭建开发环境遇到的坑
这个星期接到一个新的任务:解决HQ(一个用JAVA开发的开源的运维监控平台)现在遇到的snmp升级到3.0后bug.公司用的HQ是4.6版本.于是,我把项目从gitlab上clone下来后,就开始了我 ...
- 记录android5.0更新踩过的坑
1. service的注册必须显示注册,不能隐式注册,相关链接http://www.eoeandroid.com/thread-568853-1-1.html 现象:Service Intent mu ...
- 记录一个 spring cloud 配置中心的坑,命令行端口参数无效,被覆盖,编码集问题无法读取文件等.
spring cloud 配置中心 结合GIT , 可以运行时更新配置文件.发送指令让应用重新读取配置文件. 最近在测试服务器实现了一套,结果CPU 实用率暴增,使用docker compose启动 ...
- 记录MYSQL中SQL语句的一个坑.
MYSQL5.7 假设我们有一个表 : h_member_cards_my (ID, WXOPEN_ID) 表中有一条记录如下: 理论上第二个SQL应当是可以查询得到一条数据的, 结果却为 Empt ...
- 记录使用git submodule时踩的坑
在使用git子模块的时候踩了一个坑 在使用git submodule updata --init --recursive命令,即递归更新子模块并初始化时碰到了一个问题: 经过一段不短时间的排查,发现问 ...
- 动归专题QAQ(两天创造的刷题记录哟!✿✿ヽ(°▽°)ノ✿✿)(未填坑)
1092 采药:由于没有限制开始时间和结束时间,01背包就好了 1095 开心的金明:01背包,无fuck说 1104 摆花:f[i][j]表示摆了i种花,第i种花摆了j种的方案数,乱转移0.0(感觉 ...
- 记录 Java 的 BlockingQueue 中的一些坑
最近学习了 BlockingQueue,发现 java 的 BlockingQueue 并不是每一个实现都按照 BlockingQueue 的语意来的,其中有不少坑. 直接上代码吧: 1.关于Prio ...
- 记录一次ABP下载模板的坑
1.拉取ABP官网的模板的最新代码,我的代码结构是这样的 https://aspnetboilerplate.com/Templates 环境安装的部分我就不说明了.node.js npm 等等部分 ...
- 需求:一个页面中需要用到多个字典数据。用于下拉选项,同时,需要将其保存为json格式。以便于key,value的相互转换。记录在实现过程中踩的坑
本文涉及到的知识: Promise,all()的使用 js处理机制 reduce的用法 map的用法 同步异步 需求: 一个页面中需要用到多个字典数据.用于下拉选项,同时,需要将其保存为json格式. ...
- Xshell记录Linux连接操作日志遇到的坑
1.问题描述: 在Windows上,以前一直使用Secure CRT连接Linux主机进行远程操作,使用CRT的日志功能记录连接过程中的所有操作以及输出. 最近(2019-8-17)使用Xshell进 ...
随机推荐
- [springboot] mvn编译实现代码混淆
pom配置 <project> <build> <plugins> <plugin> <groupId>org.spri ...
- 人工生命(AL:Artificial life)兰顿蚂蚁多版本代码html\go\php\python\java
背景介绍 人工生命(AL:Artificial life)这一概念由美国计算机科学家.人工生命领域创始人之一克里斯托弗・盖尔・兰顿(Christopher G. Langton)提出.1986 年,兰 ...
- Qt音视频开发10-ffmpeg内核硬解码
一.前言 为了极大的降低CPU的占用,实现硬解码(也叫硬件加速)非常有必要,一个视频文件或者一路视频流还好,如果增加到64路视频流呢,如果是4K.8K这种高分辨率的视频呢,必须安装上硬解码才是上上策. ...
- Qt音视频开发17-海康sdk解码
一.前言 在视频监控行业领域,海康当之无愧是老大,稳坐第一的宝座很多年了,近期需要将视频监控系统改成采用海康sdk的内核,于是特意去查阅了sdk的使用手册,sdk相关的文档和文件可以直接官网下载到,而 ...
- Halo博客+兰空图床搭建保姆级指南
1. 简介 1.1 依赖的相关软件 Docker.Docker-Compose底层运行环境 Minio底层的存储支持 Mysql关系型数据库 Redis缓存中间件 NginxProxyManager( ...
- vs 禁用c++编译警告提示的两种方式
1. 禁用单个cpp文件编译警告 #pragma warning(disable:警告号) 如:当前提示C4305警告; 加入禁用单个cpp文件编译警告; 结果:编译警告消失. 2. 全局禁用指定警告 ...
- CentOS安装tigerVNC Server
- 状压 DP 做题记录
1.普通状态压缩 DP oi-wiki I.P1896 [SCOI2005] 互不侵犯 \(f_{i,j,st}\) 表示前 \(i\) 行中放置了 \(j\) 个国王,当前行状态为 \(st\) 的 ...
- 快速上手jquery
优点 强大的选择器机制 优质的隐私迭代 链式编程 选择机制 选择器 标签名 $('div') id $('#id') class $('.clname') 属性 $('div:[name='66']' ...
- 《C++并发编程实战》读书笔记(4):原子变量
1.标准原子类型 标准原子类型的定义位于头文件<atomic>内.原子操作的关键用途是取代需要互斥的同步方式,但假设原子操作本身也在内部使用了互斥,就很可能无法达到期望的性能提升.有三种方 ...