【记录】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进 ...
随机推荐
- Qt编写的项目作品28-视频监控显示安卓版
一.功能特点 1.1 基础功能 支持各种音频视频文件格式,比如mp3.wav.mp4.asf.rm.rmvb.mkv等. 支持各种视频流格式,比如rtp.rtsp.rtmp.http等. 本地音视频文 ...
- 视频直播技术干货(十二):从入门到放弃,快速学习Android端直播技术
本文由陆业聪分享,原题"一文掌握直播技术:实时音视频采集.编码.传输与播放",本文进行了排版和内容优化. 1.引言 从游戏.教育.电商到娱乐,直播技术的应用场景无处不在.随着移动端 ...
- IM通讯协议专题学习(八):金蝶随手记团队的Protobuf应用实践(原理篇)
本文由金蝶随手记技术团队丁同舟分享. 1.引言 跟移动端IM中追求数据传输效率.网络流量消耗等需求一样,随手记客户端与服务端交互的过程中,对部分数据的传输大小和效率也有较高的要求,普通的数据格式如 J ...
- 使用 httputils + sbe (Simple Binary Encoding) 实现金融级 java rpc
1.认识 Simple Binary Encoding (sbe) 高性能Java库 Agrona 的主要目标是减少性能瓶颈,通过提供线程安全的直接和原子缓冲区.无装箱操作的原始类型列表.开散列映射和 ...
- Appium_WebDriverAgent设置
在使用真机调试的时候犯了一个错误,我把WebDriverAgent 下载到本地的A目录下,然后进行build安装,这样在模拟器上执行是无法发现问题的,但是使用appium 在真机上执行 ...
- Mybatis-Plus 多租户模式忽略某个方法
Mapper 类方法添加注解: @InterceptorIgnore(tenantLine = "true") 亲测有效.
- Solution -「NEERC 2016」Delight for a Cat 的一个尝试
\(\mathscr{Description}\) Link. 给定 \(n,k,m_s,m_e\) 和两个长为 \(n\) 的序列 \(\{s\},\{e\}\), 选择一个 \(S\sub ...
- Storm基本概念
storm简介 场景 伴随着信息科技日新月异的发展,信息呈现出爆发式的膨胀,人们获取信息的途径也更加多样.更加便捷,同时对于信息的时效性要求也越来越高.举个搜索场景中的例子,当一个卖家发布了一条宝 ...
- 项目PMP之十二项目采购管理
项目PMP之十二--项目采购管理 一.定义:存在法律义务,需要对采购过程了解,非法律专家,合同约束 项目经理无权签署对组织有约束力的法律文件 采购模式: 分散式采购:无采购管理部门,项目经理有采购 ...
- MongoDB 常用指令(详细)
# MongoDB 常用指令## 基础命令### 启动与连接```bash# 启动 MongoDB 服务mongod# 连接 MongoDB 客户端mongo```### 数据库操作```bash# ...