杨玉磊 · 更新于 2018-06-18 06:01:22

流是什么?形象的比喻——水流,文件程序之间连接一个管道,水流就在之间形成了,自然也就出现了方向:可以流进,也可以流出。
便于理解,这么定义流: 流就是一个管道里面有流水,这个管道连接了文件和程序

UNIX系统认为一切皆文件,所有的外部设备都被看做文件。

C语言系统定义了三个默认的文件指针:
1、stdin 即标准输入文件,与键盘连接。(即把键盘当做文件)
2、stdout 即标准输出文件,与屏幕连接。(即把屏幕当做文件)
3、stderr 即标准出错文件,与屏幕连接。

注意:
stdout 和 stderr 是不同的设备描述符。
stdout是块设备,stderr则不是。
对于块设备,只有当下面几种情况下才会被输入:1)遇到回车,2)缓冲区满,3)flush被调用。而stderr则不会。

例:
fprintf(stdout,"hello-std-out");//不一定会输出
fprintf(stderr,"hello-std-err");//一定会输出

流的性质

你只能顺序地访问并提取流中的数据,未提取的数据只能阻塞在流中,等待下次被访问或提取。
访问文件时,流指针会自动的改变当前访问内容的位置。即文件指针会随着读写的流向而移动,它不是一直指向文件头部的。

由于UNIX一切皆文件的思想,所以,所有操作文件的函数也都可以用来操作输入/输出设备。

输入

从输入流中读取数据时,停留在流中的字符可能会影响到下次的正确读入。(这往往会造成隐藏的Bug)

丢弃输入流中字符的方法:

  • 输入结束后,把流中剩余的垃圾字符手动读掉

    while( (ch = getchar())!=EOF && ch!= ‘\n’) NULL ;

  • 用函数fflush清除一个流

    例:fflush(stdin);

fgets

从文件中读取字符串
char fgets (char string, int n, FILE *fp) ;
功能:
从文件 fp 中读取 n-1 个字符放入以 string 为首地址的空间里。读入结束后,系统将自动在最后加 ’\0’,并以 string 作为函数值返回。

gets( )和fgets( )不同,gets( )会丢弃换行符,并不把它存储在缓冲数组中。
但gets( )对于输入长度没有限制,很可能导致输入长度超过缓冲数组的长度,导致缓冲区溢出
【我们一般用fgets( )函数来接收用户输入。(这样可允许用户输入任意字符)再在程序中分析用户输入,提取数据】

fgetc

fgetc( ) 接受一个输入流作为参数,它从这个流中读取一个字符(可读入回车等空白符),如果发生错误或流已到结尾,则返回 EOF。
注意:其返回值是读入的字符的 ASCII 码值,是 int 型

小心下面代码中的隐式转换的Bug

char ch ;
While( (ch=fgetc(stdin)) != EOF ) …..//错误!

fgetc() 返回一个整型值而不是字符值,若把 fgetc 返回值存储于 ch 中,将导致它被截断!然后这个被截断的值被提升为整形并与 EOF 比较,循环会出错。
【用整形来定义一个字符变量更好!字符就是一个小整数】

scanf

scanf系列函数
int scanf( char const format, …… ) ;
int fscanf( FILE
fp, char const format, …… ) ;
int sscanf( char const
string, char const *format, ……) ;
//以上函数的读入处理规则都相同,不同的是它们读取的源不同,一个是从键盘读取、一个从文件流读取、一个从字符串读取。(注意:字符串不是流,其没有流指针保存读取位置)

扫描集

一个字符序列可以用一个扫描集(scan set)来输入。
扫描集是位于格式控制字符串中,以百分号开头、用方括号[]括起来的一组字符.

检查与扫描集中的字符相匹配的字符。一旦找到匹配的字符,那么这个字符将被存储到扫描集对应的实参(即指向一个字符数组的指针)中。只有遇到扫描集中没有包含的字符时,扫描集才会停止输入字符。

如果输入流中的第一个字符就不能与扫描集中包含的字符相匹配,那么只有空操作符被存储到字符数组中。
(如果输入的字符属于方括号内字符串中某个字符,那么就提取该字符;如果一经发现不属于就结束提取。该方法会自动加上一个'\0'到已经提取的字符后面。)

例:
char str[512] ;
printf(“Enter string:\n”) ;
scanf(“%[aeiou]”, str) ;
//程序使用扫描集[aeiou]在输入流中寻找元音字符,直到遇到非元音字符。

我们还可以用缩写a-z表示abcd….xyz字母集。
scanf(“%[a-z]”, str) ;
同理,也可以用缩写0-9 缩写A-Z。
想只取字母,那就可以写成 %[A-Za-z]

对于字符串"abDEc123"如果想按照字母和数字读到两个字符串中就应该是 "%[a-zA-Z]%[0-9]",buf1,buf2 ;

逆向扫描集

逆向扫描集还可以用来扫描那些没有出现在扫描集中的字符

创建一个逆向扫描集的方法是,在方括号内扫描字符前面加一个“脱字符号”(^)。这个符号将使得那些没有出现在扫描集中的字符被保存起来。只有遇到了逆向扫描集中包含的字符时,输入才会停止。(即取其后字符们的补集作为扫描集)

scanf(“%[^aeiou]”, str) ;
//即接受输入流中的非元音字符。

用这种方法还可以解决scanf的输入中不能有空格的问题。只要用 scanf("%[^\n]",str); 就可以了。很神奇吧。

【注意】
[]内的字符串可以是1或更多字符组成。空字符集(%[])是违反规定的,可导致不可预知的结果。%[^]也是违反规定的。

指定域宽

我们可以在scanf函数的转换说明符中指定域宽来从输入流中读取特定数目的字符。

例:
scanf(“%2d%d”, &x, &y) ;
程序从输入流中读取一系列连续的数字,然后,将其前两位数字处理为一个两位的整数,将剩余的数字处理成另外一个整数。

赋值抑制字符

即星号
赋值抑制字符使得 scanf 函数从输入流中读取任意类型的数据,并将其丢弃,而不是将其赋值给一个变量。如果你想忽略掉某个输入,使用在% 后使用

% [^=] 前面带 号表示不保存变量。跳过符合条件的字符串

char s[]="notepad=1.0.0.1001";
char szfilename [32] = "" ;
int i = sscanf( s, "% [^=]", szfilename ) ;
// szfilename=NULL,因为没保存
int i =sscanf( s, "%
[^=]=%s", szfilename ) ;
// szfilename=1.0.0.1001

所有对%s起作用的控制,都可以用于%[]
比如"% [^\n]%c"就表示跳过一行,"%-20[^\n]"就表示读取\n前20个字符。

把扫描集、赋值抑制符和域宽等综合使用,可实现简单的正则表达式那样的分析字符串的功能。

返回值

scanf的返回值是读入数据的个数;
比如scanf("%d%d",&a,&b);读入一个返回1,读入2个返回2,读入0个返回0;读入错误返回EOF即-1

安全性

你应该非常小心的使用 scanf 因为它可能会是你的输入缓冲溢出!
通常你应该使用 fgets 和 sscanf 而不是仅仅使用 scanf,使用fgets 来读取一行,然后用 sscanf 来解析这一行,就像上面演示的一样。

上一篇: 类型转换 下一篇: 输出