一、流 和 FILE对象
之前在文件I/O中提到的函数,都是围绕文件描述符的,当打开一个文件时,返回的是一个文件描述符,然后对该文件描述符进行后续的I/O操作。而对于标准I/O库,他们的操作是围绕着 流 进行的。当用标准I/O库打开或创建一个文件时,我们就使一个流与一个文件相关联。
在标准IO流中预定义了3个文件指针,stdin(标准输入)、stdout(标准输出)、stderr(标准错误)。这三个文件指针都定义<stdio.h>中。
对于ASCII字符集,一个字符用一个字节来表示。对于国际字符集而言,一个字符可用多个字符集来表示。标准I/O文件流可用于单字节或多字节(俗称"宽")字符集
流的定向,决定了所读所写的字符是单字节的还是多字节的。当一个流刚被创建出来时,是没有定向的,如果在一个没有定向的流上使用一个多字节的I/O函数,则将该流设置为宽定向,具体多宽由使用的多字节IO函数定。有两个函数可以改变流的定向:
(1) fwide函数
#include<stdio.h>
#include<wchar.h>
int fwide(FILE *fp, int mode);
返回值:若流是宽定向的,返回正值;若流是字节定向的,返回负值;若流是为定向的,返回0
注意:
fwide并不改变以定向流的定向。还应注意的是,fwide无出错返回。试想一下如果流是无效的会发生什么呢?我们唯一依靠的就是fwide前先清除errno,从fwide返回时检查errno的值。
(2) freopen函数
该函数的解释,可见 三、打开流
二、缓 冲
1、标准IO库提供缓冲的目的是,尽可能少的使用read和write调用次数。标准IO的底层实现是运用了read和write,通过缓冲区将要写入或者读出的数据先放到缓冲区中,然后达到一定的条件后(比如:缓冲区满、遇见换行符 等等)去调用一次read或是write。
2、标准的IO提供了三种类型的缓冲
(1)全缓冲:
在填满标准的IO缓冲区后才进行实际的IO操作。对于驻留在磁盘上的文件通常是有标准IO库实施全缓冲的。在一个流上执行第一次操作时,相关的标准IO函数通常是调用malloc去获得所需的缓冲区。
该类型的缓冲区会在 缓冲区填满时 以及 调用fflush()冲刷流时执行一次写操作。
(2)行缓冲:
在遇到换行符时,执行一次IO操作。对于行缓冲有两个限制,
第一,因为标准IO库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使没有写一个换行符也执行一次IO操作。
第二,任何时候只要通过标准IO库要求从一个不带缓冲的流,或者是一个行缓冲的流(它从内核请求需要的数据)得到输入数据,那么就会冲洗所有的行缓冲输出流。对于行缓冲的流后面的括号中的说明,着重解释一下:
从不带缓冲的流中输入需要从内核中获取数据。
从行缓冲的流中得到输入数据,所需的数据可能还在缓冲区中,不需要从内核中读数据。
(3)不带缓冲:
不对字符进行缓冲存储。比如:stderr
ISO C中要求缓冲有一下几项特征:
a、当且仅当标准输入和标准输出,并不指向交互式设备时,他们才是全缓冲。当指向交互设备时,是行缓冲还是无缓冲都视情况而定,很多系统在指向交互设备后,默认标准错误无缓冲。
b、标准错误永远不会是全缓冲。
对任意一个给定的流,若不喜欢他的默认缓冲类型,可以通过下列函数去进行修改缓冲类型。
#include<stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
void setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
返回值:成功,返回0;失败,返回,非0
void setbuf(FILE *restrict fp, char *restrict buf);
可以使用 setbuf 去打开或关闭缓冲机制,参数buf不需指向一个长度为 BUFSIZ 的缓冲区,设置之后该流的缓冲区为全缓冲。如果要关闭缓冲机制,则将 buf 为NULL。
#include <stdio.h>
char outbuf[50];
#if 0
int main(void)
{
/* 将outbuf与stdout输出流相连接 */
setbuf(stdout,outbuf);
/* 向stdout中放入一些字符串 */
puts("This is a test of buffered output.");
puts("This output will go into outbuf");
puts("and won't appear until the buffer");
puts("fills up or we flush the stream.\n");
/* 以下是outbuf中的内容 */
// puts(outbuf);
/*刷新流*/
// fflush(stdout);
return 0;
}
#else
#include<stdio.h>
#include<stdlib.h>
//char buf[6];
int main()
{
// static char buf[6];
setbuf(stdout,malloc(100));
int c;
int i = 0;
while((c=getchar())!=EOF && getchar())
{
putchar(c);
#if 1
printf("hello\n");
i++;
if(i>5)
{
return 0;
}
#endif
}
return 0;
}#endif
void setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
参数:stream :指向流的指针 ;
buf : 期望缓冲区的地址;
type : 期望缓冲区的类型:
_IOFBF(全缓冲):当缓冲区为空时,从流读入数据。或者当缓冲区满时,向流写入数 据。
_IOLBF(行缓冲):每次从流中读入一行数据或向流中写入一行数据。
_IONBF(无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。
size : 缓冲区内字节的数量。
注意:意思是这个函数应该在打开流后,立即调用,在任何对该流做输入输出前
下面是这两个函数,参数设置之后其得到的缓冲类型的表:
补充说明一个常见C语言关键字:
三、打开流
#include<stdio.h>
FILE* fopen(const char *restrict pathname, char *restrict type);
FILE* freopen(const char *restrict pathname, char *restrict type, FILE* restrict fp);
FILE* fdopen(int fd, const char *type);
返回值:成功,返回文件指针;失败,返回NULL
(1) fopen 打开路径执行一个文件
(2) freopen 在一个制定的流(三参)上打开一个文件(一参),如果该流打开了,那就先关闭之前的流,然后再打开新的文件。如果该流已定向,则 freopen 清除该定向。通常该函数将一个指定的文件打开为一个预定义的流,标准输入、标准输出、标准错误。
(3) fdopen 这个函数比较有用。将一个文件描述符(open,dup,管道、socket等)与一个标准IO中的流相结合。此函数用于创建管道和网络通信通道函数返回的描述符。因为这些特殊的文件描述符不能用标准IO函数fopen打开,所以必须使用该函数将其文件描述符与流相结合。
这三个都有一个标准IO流的参数,其解释如下:
注意1:在 UNIX 环境下,UNIX内核对二进制文件 和 文本文件不做区分,故 b 选项没有意义。
注意2:还有以下在使用fdopen时的注意事项:
注意3:还有注意的一点就是,用 w 或是 a 创建一个新文件时,无发说明该文件的访问权限位。文件IO中,open和creat都能做到。如果要用标准IO用权限位集来创建文件时,必须使用umask函数。在这里需要插入一个文件和目录的知识点umask函数:
include<sys/stat.h>
mode_t umask(mode_t cmask);
返回值:之前的文件模式创建屏蔽字
注意4: 流引用的是终端设备,该流属于行缓冲;按系统默认情况下,流被打开是全缓冲。
四、读写流
在打开流之后,有三种不同类型的非格式化IO进行读写操作:
(1) 每次一个字符的IO。一次的读或写一个字符,如果流带缓冲,则标准IO函数处理所有的缓冲。
(2) 每次一行的IO。读写以换行符结尾,主要函数有 fputs 和 fgets .
(3) 直接IO。每次的IO操作读或写某种数量的对象,而每个对象具有指定的长度。常用于从二进制文件中每次读写一个结构等,主要的函数 fread 和 fwrite。
1、用于一次只读一个字符的函数(总共三个函数)
include<stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
返回值:成功返回下一个字符;
若文件已到达文件尾端或出错,返回EOF
函数 getchar 等同于 getc(stdin); getc 可被定义为宏,而fgetc不能实现为宏,其解释引用书中原文:
注意:
在这三个函数中,他们无论 出错 还是 到文件尾端其返回的都是EOF,为了区分这两种情况,引入了两个函数:
include<stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
返回值:条件为真,返回非0;条件为假,返回0
void clearerr(FILE *fp);
他的区分原理是,如下:
从流中读取的数据,可以通过 ungetc 将字符再压回去;
#include<stdio.h>
int ungetc(int c,FILE* fp);
返回值:若成功,返回c;若出错,返回EOF
ungetc 该函数实现并不是在底层文件或是设备上执行的,而是在标准IO的缓冲区中操作的。以下是书中全部的解释:
以上是介绍了每次单个字符的读操作,以下是写操作,也是三个函数
include<stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
返回值:成功返回c;若出错,返回EOF
putchar(c) 等同于 putc(c, stdout) ;putc可被实现为宏,而 fputc 不能实现为宏。
文章引用:setbuf 和setvbuf 简单介绍