流与FILE对象
在标准IO中,所有的操作都是围绕流(stream)进行的。当用标准IO库打开或创建一个文件时,我们已使一个流与一个文件相关联。
对于ASCII字符集,一个字符用一个字节表示。对于国际字符集,一个字符可以用多个字节表示。标准IO文件流可用单个字节和多字节字符集。
流的定向决定了所读、写的字符是单字节还是多字节的。
当一个流最初被创建时,他并没有指向。如若要在为指向的流上使用一个多字节IO函数,则将该流定向设置为字节定向的。只有两个函数可以改变流的定向:freopen、fwide。
fwid设置流的定向
int fwide(FILE *fp, int mode);
返回值:宽定向为正值,字节定向为负值,未定向返回0
根据mode参数的不同值,fwide函数执行不同的工作。
- 若mode为负数,fwide试图使指定的流是字节定向的
- 若mode为正数,fwide试图使指定的流是宽定向的
- 若mode为0,fwide不试图设置流的定向,但返回标识该流定向的值。
标准输入、标准输出和标准出错
对于一个进程预定义了三个流,并且这三个流可以自动地被进程使用,他们是:标准输入、标准输出、标准出错。
这三个标准IO流通过预定义文件指针stdin、stdout、stderr加以引用。这三个指针在头文件stdio中。
缓冲
标准IO库提供缓冲地目的是尽可能减少使用read和write调用地次数。它也对每个IO流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。但是,标准IO最令人迷惑的也是他的缓冲。
标准IO库有三种类型的缓冲:
- 全缓冲。这种情况下,在填满标准IO缓冲后才进行实际IO操作。对于驻留在磁盘上的文件通常是由标准IO库实施全缓冲的。在一个流上执行第一次IO操作时,相关标准IO函数通常调用malloc获得需使用的缓冲区。
- 行缓冲。在这种情况下,在输入和输出中遇到换行付时,标准IO库才执行IO操作。
对于行缓冲有两个限制。第一,标准IO库用来手机每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,即使没有换行符,也进行IO操作。第二,任何时候只要通过标准IO库要求从一个不带缓冲的流,或者一个行缓冲的流得到数据,那么就会造成冲洗所有行缓冲输出流 - 不带缓冲。标准IO库不对字符进行缓冲存储。将字符立刻写入相关的打开文件上。
标准出错流stderr通常是不带缓冲的,这就使得出错信息可能尽快显示出来,而不管他们是否含有一个换行符。
ISO C要求下列缓冲特性:
- 当且仅当标准输入和标准输出并不涉及交互式设备时,他们才是全缓冲。
- 标准出错决不会是全缓冲的。
- 如若涉及终端设备的其他流。
对于任何一个给定的流,我们可以调用函数修改缓冲类型:
#include <stdio.h>
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode
, size_t size);
成功返回0,不成功返回非0
setbuf适用于打开或关闭缓冲机制。参数buf未只当一个长度BUFSIIZE的缓冲区。通常再次之后就是全缓冲。将buff设置成NULL,关闭缓冲。
使用setvbuf,我们可以精确的指定所需的缓冲类型,mode参数类型实现的。
- _IONBF unbuffered
_IOLBF line buffered
_IOFBF fully buffered
如果指定一个不带缓冲的流,则忽略buf和size参数。如果指定全缓冲或行缓冲,则buf和size可选择地指定一个缓冲区及其长度。
强制冲洗流
int fflush(FILE *fp);
打开流
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *pathname, const char *mode, FILE *stream);
fopen:打开一个指定文件名的文件。
fdopen:打开一个文件描述符的文件。该函数常用于创建管道和网络通信函数返回描述符。
freopen:在一个指定流上打开一个执行文件。若该流已经打开,则先关闭该流。若该流已经定向,则freopen删除该定向。
mode参数指定该IO流的读、写方式。
使用字符b作为type的一部分,这使得标准IO系统可以区分文本文件和二进制文件。因为UNIX没有对这两类文件做区分。
当使用fdopen的时候,因为文件描述符已经被打开,所以在写的时候不会对文件进行截短操作。
当以读和写类型打开一个文件是,具有一些限制:
- 中间没有fflush、fseek、fsetpos或rewind,则在输出的后面不能直接跟随输入。
fclose关闭打开的流。
#include <stdio.h>
int fclose(FILE *stream);
读和写流
- 每次一个字符的IO。一次读或写一个字符,如果带缓冲,则标准IO函数会处理所有缓冲。
- 每次一行的IO。如果想要读或写一行,则使用fgets、fputs,每行以换行符终止。
- 直到IO。fread、fwrite函数支持这种类型的IO。每次IO操作读或写某种数量的对象。这两个函数常用于从二进制文件中每次读或写一个结构。
输入函数
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
getchar等价于getc(stdin)。前两个函数的区别是getc能被实现成宏,fgetc不能。getc的参数不应当是具有副作用的表达式。
这三个函数在返回下一个字符时,会将unsigned char转为int,转换为整型是为了可以返回所有的可能的字符值以及出错的字符。(EOF为-1)所以不能将返回值存放到char中,因为还要和EOF比对。
注意:不管是出错还是到达文件尾端,这三个函数都返回同样的值,为了区分这两种不同的情况,需要调用ferror和feof
#include <stdio.h>
void clearerr(FILE *stream);
int feof(FILE *stream);
int ferror(FILE *stream);
条件为真返回非0,否则为0
在大多数实现中,为每个流在FILE对象中维持两个标志:
- 出错标志
- 文件结束标志
调用clearerr清楚标识。
从流中读取数据以后,可以调用ungetc将字符再压送回流中。(分字或分记号操作)
int ungetc(int c, FILE *stream);
成功返回c,不成功EOF
压送回到流中的字符以后又可以从流中读出,但读出的字符顺序于押送会的顺序相反。
输出函数
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
每次一行IO
以下两个函数支持每次输入一行的操作:
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
fgets必须指定缓冲区的长度,此函数一直读到下一个换行符为止,但是不超过n-1个字符,写入的字符被送到缓存区。该缓存区以NULL字段结尾。若该行的字符超过n-1,则fgets返回一个不完整的行,但是缓存区还是以NULL结尾。
以下两个函数支持每次输出一行的操作:
int fputs(const char *s, FILE *stream);
int puts(const char *s);
标准IO效率
使用标准IO的一个优点是无需考虑缓冲及最佳IO长度的选择。在使用fgets时需要考虑最大行长,但是与选择最佳IO长度相比,这要方便很多。
二进制IO
如果进行二进制IO操作,那么我们更愿意一次读或写整个结构。如果使用getc和putc读、写一个结构,那么必须循环整个结构,每次循环处理一个字节,一次读或写一个文件。
如果使用fputs和fgets,那么因为fputs在遇到null字节时就停止,而在结构中包含null字节,所以不能使用他来读结构的需求。因此,提供下面两个函数可以执行二进制IO操作。
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
FILE *stream);
参数ptr为指向要读取/写入的数组中首个对象指针;size为每个对象的大小,单位是字节;nmemb:要读取/写入的对象个数;stream:输入流
返回值为读/写入的对象数。
定位流
有三种定位标准IO流的方法;
- ftell和fseek函数。假设位置的数量不超过一个长整型。
- ftello和fseeko,这两个函数可以使文件偏移量不必一定使用长整型。它们使用off_t数据类型代替长整型。
- fgetpos和fsetpos,这两个函数使用一个抽象的数据类型fpos_t记录文件位置。这种数据类型可以定义为记录一个文件位置所需的长度。
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
对于一个二进制文件,其文件位置执行器是从文件起始位置开始度量。并以字节为计量单位。ftell用于二进制文件时,返回值时这种字节位置。
为了用fseek定位一个二进制文件,必须指定一个字节offset,并解释这种偏移量的方法。whence的值与lseek函数的相同:
- 如果 whence 是 SEEK_SET(设置请求(偏移)),则该文件的偏移量设置为距文件开始处 offset 个字节处。
- 如果 whence 是 SEEK_CUR(当前请求(偏移)),则该文件的偏移量设置为当前值加 offset(offset 可以带负号)。
- 如果 whence 是 SEEK_END(结尾请求(偏移)),则该文件的偏移量设置为距文件结束的 offset 位置(可加可减)。
对于文本文件,他们的文件当前位置可能不以简单的字节偏移量来度量,主要原因是可能存放不同格式的文本文件。为了定位一个文本文件,whence一定要时SEEK_SET,而offset执行有两种值:0(绕回到文件的起始位置),或时对该文件调用ftell返回的值。使用rewind函数可以将一个流设置到文件的起始位置。
除了返回值的类型时off_t之外,fseeko与fseek相同,ftello与ftell相同。
int fseeko(FILE *stream, off_t offset, int whence);
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
off_t ftello(FILE *stream);
实现可将off_t类型定义为长于32位。
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
fgetpos将文件位置指示器的当前值存入由pos指向的对象中。在以后调用fsetpos可以设置此值将流重新定位到该位置。
格式化IO
格式化输出
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
格式说明控制其余参数如何编写,以后如何现实。每个参数按照转换说明编写,转换说明以字符&开始。出转换说明以外,格式化字符串中的其他字符将按原样,不经任何修改的被复制输出。一个转换说明分为4个部分:
%[flags][fldwidth][precision][lenmodifier]convtype
flags:
fldwidth说明转换的最小字段宽度。
precision说明整型转换后最小输出数字位数。
lenmodifier说明参数长度。
convtype时必选的。他控制如何解释参数。
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vdprintf(int fd, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
上面的输出格式与不带v的类似,只是可变参数表换成了va_list类型。它是在函数内部通过 va_start
和 va_end
配合使用的。
va_list是在<cstdarg>中定义的特殊类型。
常规情况下,输出到控制台,多数情况下使用 printf
函数即可。当你需要自己写一个自定义printf函数时候才需要vprintf。(实际上printf底层就是调用vprintf函数来讲内容输出到控制台的)
格式化输出
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
int vscanf(const char *format, va_list ap);
int vsscanf(const char *str, const char *format, va_list ap);
int vfscanf(FILE *stream, const char *format, va_list ap);
临时文件
ISO C标准IO库提供了两个函数以帮助创建临时文件。
#include <stdio.h>
char *tmpnam(char *s);
FILE *tmpfile(void);
tmpnam函数产生一个与现有文件名不同的一个有效路径名字符串。每次调用它时,它都会产生一个不同的路径名,最多调用次数TMP_MAX。
如果s时NULL,则所产生的路径明存放在一个静态区中。指向静态区的指针作为函数返回值。下一次调用tmpnam时,会重写该静态区。若s不是NULL,则认为它指向长度至少是L_tmpnam个字符的数组。所产生的路径名存放在该数组中,ptr作为函数返回值。
tmpfile函数创建一个临时二进制文件(wb+),在关闭该文件或程序结束时讲自动删除这种文件。