吐槽

每每读取字符串时,倘若稍有灵活的操作,总会遇上诡异奇怪的事情。究其原因,就是没完全理解一些基本读写函数的机制。这次做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的坑与教训的更多相关文章

  1. 记录hyperic-hq搭建开发环境遇到的坑

    这个星期接到一个新的任务:解决HQ(一个用JAVA开发的开源的运维监控平台)现在遇到的snmp升级到3.0后bug.公司用的HQ是4.6版本.于是,我把项目从gitlab上clone下来后,就开始了我 ...

  2. 记录android5.0更新踩过的坑

    1. service的注册必须显示注册,不能隐式注册,相关链接http://www.eoeandroid.com/thread-568853-1-1.html 现象:Service Intent mu ...

  3. 记录一个 spring cloud 配置中心的坑,命令行端口参数无效,被覆盖,编码集问题无法读取文件等.

    spring cloud 配置中心 结合GIT , 可以运行时更新配置文件.发送指令让应用重新读取配置文件. 最近在测试服务器实现了一套,结果CPU 实用率暴增,使用docker compose启动 ...

  4. 记录MYSQL中SQL语句的一个坑.

    MYSQL5.7 假设我们有一个表 : h_member_cards_my  (ID, WXOPEN_ID) 表中有一条记录如下: 理论上第二个SQL应当是可以查询得到一条数据的, 结果却为 Empt ...

  5. 记录使用git submodule时踩的坑

    在使用git子模块的时候踩了一个坑 在使用git submodule updata --init --recursive命令,即递归更新子模块并初始化时碰到了一个问题: 经过一段不短时间的排查,发现问 ...

  6. 动归专题QAQ(两天创造的刷题记录哟!✿✿ヽ(°▽°)ノ✿✿)(未填坑)

    1092 采药:由于没有限制开始时间和结束时间,01背包就好了 1095 开心的金明:01背包,无fuck说 1104 摆花:f[i][j]表示摆了i种花,第i种花摆了j种的方案数,乱转移0.0(感觉 ...

  7. 记录 Java 的 BlockingQueue 中的一些坑

    最近学习了 BlockingQueue,发现 java 的 BlockingQueue 并不是每一个实现都按照 BlockingQueue 的语意来的,其中有不少坑. 直接上代码吧: 1.关于Prio ...

  8. 记录一次ABP下载模板的坑

    1.拉取ABP官网的模板的最新代码,我的代码结构是这样的 https://aspnetboilerplate.com/Templates 环境安装的部分我就不说明了.node.js  npm 等等部分 ...

  9. 需求:一个页面中需要用到多个字典数据。用于下拉选项,同时,需要将其保存为json格式。以便于key,value的相互转换。记录在实现过程中踩的坑

    本文涉及到的知识: Promise,all()的使用 js处理机制 reduce的用法 map的用法 同步异步 需求: 一个页面中需要用到多个字典数据.用于下拉选项,同时,需要将其保存为json格式. ...

  10. Xshell记录Linux连接操作日志遇到的坑

    1.问题描述: 在Windows上,以前一直使用Secure CRT连接Linux主机进行远程操作,使用CRT的日志功能记录连接过程中的所有操作以及输出. 最近(2019-8-17)使用Xshell进 ...

随机推荐

  1. Eureka 缓存机制详细配置

    https://blog.csdn.net/qwe86314/article/details/94963865 上节为大家介绍了 Eureka 的工作原理,其中提到了 Eureka Server 内部 ...

  2. Qt开发经验小技巧101-110

    如果需要在尺寸改变的时候不重绘窗体,则设置属性即可 this->setAttribute(Qt::WA_StaticContents, true); 这样可以避免可以避免对已经显示区域的重新绘制 ...

  3. Windows上使用CMake GUI编译开源代码时,提示:cmake Could NOT find ZLIB (missing:ZLIB_LIBRARY)和Could NOT find PNG (missing: PNG_LIBRARY PNG_PNG_INCLUDE_DIR)的处理办法

    有的时候就算在CMake GUI中配置完ZLIB_LIBRARY和PNG_LIBRARY和PNG_PNG_INCLUDE_DIR等相关路径,还是提示上述错误.原因还是由于编译某源码时遗漏了对第三方开源 ...

  4. Python中导入模块的import命令的语法

  5. OpenMMLab AI实战营 第七课笔记

    OpenMMLab AI实战营 第七课笔记 目录 OpenMMLab AI实战营 第七课笔记 import os import numpy as np from PIL import Image im ...

  6. Redis 学习笔记之基础

    一.Redis 是什么 Redis 是一个使用 C 语言写成的,开源的.key-value 结构的.非关系型数据库.它支持存储的 value 类型相对更多,包括 String(字符串).List(列表 ...

  7. CDS标准视图:测量点数据 I_MeasuringPointData

    视图名称:测量点数据 I_MeasuringPointData 视图类型:基础视图 视图代码: 点击查看代码 @AbapCatalog.sqlViewName: 'IMEASPOINTDATA' @A ...

  8. RESTful 架构详解-copy

    1. 什么是REST REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移. 它首次出现在2000年Roy Fielding的 ...

  9. MAC安装redis的简单方法

    part 1:安装redis1.官网下载压缩包https://redis.io/download or brew install redis(太慢了-)我此处选的法一,先去官网上下载包,在解压使用. ...

  10. 工作流调度器-Azkaban

    1.工作流调度器 1.为什么需要工作流调度系统 一个完整的数据分析系统通常都是由大量任务单元组成: shell脚本程序,java程序,mapreduce程序.hive脚本等 各任务单元之间存在时间先后 ...