Linux系统的文件IO都是针对文件描述符的,而标准IO(ISO C)的操作则是围绕流进行的,一个最明显的区别是标准IO比Linux文件IO多了缓冲机制。为了使用流,需要用到文件指针即指向FILE结构的指针,在“libio.h”头文件中有FILE结构的详细说明。“stdio.h”头文件中定义了三个标准流,stdin、stdout和stderr,分别是标准输入、标准输出和标准出错。流可以分为非格式化IO和格式化IO,前者又分为字符IO、行IO和二进制IO,后者如常见的scanf、printf函数等。对于ASCII字符集,有的字符用一个字节(单字节)表示,有的字符用多个字节(宽字节)表示,当一个流最初被创建时,它并没有定向,若在未定向的流上使用一个多字节IO函数,则将该流的定向设置为宽定向,若在未定向的流上使用一个单字节IO函数,则将该流的定向设置为字节定向,更改流定向只有两个函数,freopen和fwide,freopen用以清除流定向,fwide用于设置流定向。
#include
FILE *freopen(const char *path, const char *mode, FILE *stream);
#include
int fwide(FILE *stream, int mode);
标准IO提供缓冲的目的是尽可能地减少read和write系统调用的次数,它对每个IO流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。标准IO提供了三种类型的缓冲,全缓冲、行缓冲和无缓冲。全缓冲在填满标准IO缓冲区后才进行实际IO操作;行缓冲在输入和输出中遇到换行符或行缓冲区填满时执行IO操作;无缓冲即标准IO不对字符进行缓冲存储。一般清空下,有默认的缓冲方式,标准出错流stderr不带缓冲,从而使得出错信息可以尽快显示出来,涉及终端设备时流是行缓冲的,其它情况是全缓冲的,更改缓冲方式可通过setbuf和setvbuf函数完成,setvbuf可以指定具体的缓冲方式(mode),在“stdio.h”头文件中有一个适合系统的缓冲区长度宏定义BUFSIZ。
#include
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
调用fflush函数可强制冲洗一个流或所有输出流:
#include
int fflush(FILE *stream);
当一个进程正常终止时,即调用exit函数或者从main函数返回,那么所有未写的带缓冲数据的标准IO流都会被冲洗,所有打开的标准IO流都会被关闭。
#include
FILE* fopen(const char *path, const char *mode);
FILE* fdopen(int fd, const char *mode);
FILE* freopen(const char *path, const char *mode, FILE *stream);
int fclose(FILE *fp);
上面前三个函数可以打开一个标准IO流,fopen打开指定的文件,freopen在预定义的流(一般为stdin/stdout/stderr)上打开指定的文件,fdopen使得流关联到一个文件描述符上。fclose函数用以关闭一个打开的流。
下面三个函数用于检查、设置流的状态,如EOF标志(一般为-1)、错误标志。
#include
int ferror(FILE *stream);
int feof(FILE *stream);
void clearerr(FILE *stream);
下面列出的是用于流定位的几个函数,它们基于不同的标准。
#include
int ftell(FILE *stream);
int fseek(FILE *stream, long offset, int whence);
void rewind(FILE *stream);
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, fpos_t *pos);
off_t ftello(FILE *stream);
int fseeko(FILE *stream, off_t offset, int whence);
#include
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar(void);
int ungetc(int c, FILE *stream);
int putc(int c, FILE *stream);
int fputc(int c, FILE *stream);
int putchar(int c);
在上面的字符IO中,前三个get系列函数用于一次读取一个字符,其中getchar等效于getc(stdin),getc可实现为宏而fgetc不能,这意味着getc的参数不能是有副作用的表达式,fgetc的函数地址看作为参数传递,fgetc耗时可能比getc要长。ungetc函数用于把字符压送会流中,后面三个put系列函数则用于一次输入一个字符。
#include
char* gets(char *s);
char* fgets(char *s, int size, FILE *stream);
int puts(const char *s);
int fputs(const char *s, FILE *stream);
在上面的行IO中,get系列函数用于一次读取一行,put系列函数用于一次输入一行,需要注意的是gets由于未指定行的长度,有可能造成缓冲区溢出,这几个函数的行尾换行符也要注意。
#include
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);
上面的两个二进制IO在处理复杂的数据结构时,如果在不同的机器上执行,可能会有问题,如编译方式、字节对齐等问题。
下面列出的是常见的格式化输入、输出IO。
#include
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, ...);
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
#include
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, 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);
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);
printf系列函数的format中,需要进行格式控制时由百分号开始,其余字符则原样输出。格式控制形式如下:
%[flag][width][precision][modifier]type
flag可选,“-”表示左对齐,默认右对齐,“+”表示输出正负号,默认正数和零不输出,“ ”即空格,表示不带正负号时在前面添加空格,默认不添加,“#”表示添加进制符号,如十六进制的0x,默认不添加,“0”表示填充数字零,默认使用空格填充。
width表示输出的字符宽度,是一个十进制数,或是一个星号“*”,星号的意思是具体宽度由后面的参数给出。
precision表示精度,对于整型来说表示最少输出位数,对于浮点型来说表示小数点后的最少位数,对于字符串来说表示最多输出位数,precision以小数点开始,后接一个十进制整数或者星号“*”。
标准IO库还提供了用于创建临时文件的若干函数,tmpnam、tempnam、mktemp用以构建临时文件名,tmpfile、mkstemp用于创建临时文件。
#include
FILE *tmpfile(void);
char *tmpnam(char *s);
char *tempnam(const char *dir, const char *pfx);
#include
int mktemp(char *template);
int mkstemp(char *template);
标准IO库的一个不足之处是效率不高,这与它需要复制的数据量有关。当使用每次一行函数fgets和fputs时,通常需要复制两次数据:一次是在内核和标准IO缓冲之间(当调用read和write时),第二次是在标准IO缓冲区和用户程序中的行缓冲区之间。而快速IO库则避免了这一点,其方法是使读一行的函数返回指向该行的指针,而不是将该行复制到另一个缓冲区。