【记录】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进 ...
随机推荐
- MyBatisPlus中updateById与updateAllColumnById方法区别
实现 updateById方法在插入时,会根据实体类的每个属性进行非空判断,只有非空的属性所对应的字段才会出现在SQL语句中. updateAllColumnById方法在插入时,不管属性是否为空,属 ...
- Qt开源作品15-视频监控画面
一.前言 视频监控系统在整个安防领域,已经做到了烂大街的程序,全国起码几百家公司做过类似的系统,当然这一方面的需求量也是非常旺盛的,各种定制化的需求越来越多,尤其是这几年借着人脸识别的东风,发展更加迅 ...
- DVWA靶场Command Injection(命令注入) 漏洞low(低),medium(中等),high(高)所有级别通关教程及源码审计
命令注入 命令注入漏洞是一种安全漏洞,攻击者可以通过向应用程序输入恶意命令,诱使系统执行这些命令,从而达到未授权访问.数据篡改.系统控制等目的.该漏洞通常出现在应用程序未对用户输入进行充分验证和清理时 ...
- Pytorch的主要组成模块
Pytorch的主要组成模块 一.基本配置 对于一个PyTorch项目,我们需要导入一些Python常用的包来帮助我们快速实现功能.常见的包有os.numpy等,此外还需要调用PyTorch自身一些模 ...
- python基础应用
pip的使用 升级pip python3 -m pip install --upgrade pip 镜像源设置 查看镜像源 pip config list 指定镜像源更新依赖 pip3 install ...
- [源码阅读]-Redis核心事件流程
Redis核心流程 本文分析基于Redis-1.0源码,核心流程代码主要分布在redis.c,ae.c两个文件中. Notion版本 1.Redis核心流程中的重要数据结构 struct redisS ...
- 《C++并发编程实战》读书笔记(3):并发操作的同步
1.条件变量 当线程需要等待特定事件发生.或是某个条件成立时,可以使用条件变量std::condition_variable,它在标准库头文件<condition_variable>内声明 ...
- Ellyn-Golang调用级覆盖率&方法调用链插桩采集方案
词语解释 Ellyn要解决什么问题? 在应用程序并行执行的情况下,精确获取单个用例.流量.单元测试走过的方法链(有向图).出入参数.行覆盖等运行时数据,经过一定的加工之后,应用在覆盖率.影响面评估.流 ...
- RocketMQ的架构设计、关键特性、与应用场景详解
内容大纲: 1.RocketMQ的简介与演进 2. RocketMQ的架构设计 3.RocketMQ的关键特性 4.RocketMQ的应用场景 RocketMQ的简介 RocketMQ一个纯java. ...
- Billyboss pg walkthough Intermediate window
nmap ┌──(root㉿kali)-[/home/ftpuserr/nc.exe] └─# nmap -p- -A -sS 192.168.219.61 Starting Nmap 7.94SVN ...