C语言深入标准输入输出( 六 )


输入abc efg,可以看到留在缓存中的efg以及空格和换行被清除了,按道理将两句话合成一句scanf(“%*[\n]%c")或scanf("%*[\n]%*c”)也行得通,但是有些编译器在丢弃到只剩一个换行符后就不再匹配后面的格式符,这会导致清缓存失败,为了保险将它们分为两句执行 。scanf()对正则表达式的支持相对较弱,需要多多测试 。
最后我们来想一想,为什么scanf()读取字符串时默认遇到空格会停止,可能是想让空格也作为间隔符来给多个字符串变量赋值吧,我们用代码来测试一下:
#includeint main(){char str1[20]="";char str2[20]="";scanf("%s%s",str1,str2);puts(str1);puts(str2);return 0;}
输入abc 123,从结果可以看到str1和str2被成功赋值,这说明设计初衷很可能如此 。这相当于如下代码:
#includeint main(){char commandStr[20];char par1[5]="";char par2[5]="";char par3[5]="";char par4[5]="";char par5[5]="";char *parStr[5]={par1,par2,par3,par4,par5};int i=0;scanf("%s%c", commandStr);while(stdin->_cnt) scanf("%s%c",parStr[i++]);puts(commandStr);for(i=0;i<5;i++) puts(parStr[i]);return 0;}
如果我们把使用空格分割的字符串看作输入的命令和它的参数,就可以通过上面方法解析出来,尝试输入一条linux命令ls –l –h –a,可以发现命令和参数都被正确解析,但我发现有些编译器如果参数中包含“:”有时会解析不正常,不知道是何原因 。
相比(),scanf()的返回值更加重要,它返回成功读取的变量个数,只有返回值与赋值的变量个数完全一致结果才会正确,如果一个变量都未赋值成功返回0 。
到这里所有的标准输入输出细节都讲清楚了,可能你会认为平时我们对输入要求并不高,花费这么多精力专研值得吗?可以明确的告诉你,值!因为文件的输入输出也有类似的()和()函数,它们和print(),scanf()用法几乎相同,只是操作的对象不一样,将这里学到的知识用于读写文件中的内容会大大提升文件读写的技巧 。
()和()
当从控制台输入输出单个字符时这两个函数的确比()和scanf()方便,因为不用编写格式控制符,特别是(),对变量赋值时能返回输入字符,不需要赋值时能清空缓存,或者干脆放到末尾放置程序退出 。
gets()和puts()
puts()最大的特点是自动在末尾添加换行符,虽然不能忠实反应输出内容,但是极大方便了行输出,对应的gets()会读取并自动丢弃缓存中换行符并且忽略字符串中间的空格符,极大方便了行输入 。需要注意的是gets()在方便之余带来了安全隐患,如果用来储存字符串的数组长度不够就会产生溢出,破坏代码运行甚至遭受黑客攻击 。C99承认这个漏洞但依然保留了这个函数,支持C99的很多编译器会给出警告,C11则完全废除了这个函数,但为了兼容过去的代码,支持C11很多编译器还是保留了这个函数 。C11新增()函数代替gets(),()需要指定输入长度,例如:
(words,len);
当输入内容超过指定长度时,()返回NULL,然后清除缓存中该行内容,直到遇到换行符或文件尾,最后调用错误处理函数,也就是说()认定该行无效同时自动清除该行剩余内容 。对于VS默认使用C11标准处理输入输出,使用gets()会报错,要使用gets()必须关闭SDL,如图:
VS还要求为()添加错误处理函数,如果不添加也会导致运行时报错,而其它支持C99的编译器则完全不支持() 。如果使用C99又想避免溢出,可以使用scanf()配合正则表达式,例如:
#include int main(){char str[5] = "";char str1[5] = "";scanf("%4[^\n]", str);scanf("%4[^\n]", str1);puts(str);puts(str1);return(0);}