lienhua34
2014-09-29
1 标准 I/O 流
之前学习的都是不带缓冲的 I/O 操作函数,直接针对文件描述符的,每调用一次函数可能都会触发一次系统调用,单次调用可能比较快捷。但是,对于需要频繁进行 I/O 操作的程序,频繁触发系统调用产生的消耗太大。
标准 I/O 库提供了带缓冲的 I/O 操作函数,这些函数围绕着一种叫做流(stream)的东西进行。当使用标准 I/O 库打开或创建一个文件时,系统提供了一个流与这个文件相关联。通过流的读入和输出完成所需要的 I/O操作。
标准 I/O 库使用一个 FILE 结构来管理流所需要的所有信息,包括:用于实际 I/O 的文件描述符、指向用于该流的缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等等。指向 FILE 对象的指针我们可以称为文件指针。
标准 I/O 库为每个进程预定义了三个流:标准输入、标准输出和标准出错。这三个标准 I/O 流通过预定义文件指针 stdin、stdout 和 stderr 加以引用。这个三个文件指针定义在头文件 <stdio.h> 中。
2 缓冲
标准 I/O 流提供了缓冲是为了尽可能减少使用 read 和 write 系统调用的次数。标准 I/O 库提供了三种类型的缓冲:
1. 全缓冲。在这种情况下,在填满标准 I/O 缓冲区之后才进行实际 I/O操作。对于驻留在磁盘上的文件通常是由标准 I/O 库实施全缓冲的。
2. 行缓冲。在这种情况下,当在输入和输出中遇到换行符时,标准 I/O库执行 I/O 操作。当流涉及一个终端时(例如标准输入和标准输出),通常使用行缓冲。
3. 不带缓冲。标准 I/O 库不对字符进行缓冲存储。标准出错流 stderr 通常是不带缓冲的。
3 打开流
标准 I/O 库提供了 fopen 函数来打开标准 I/O 流,
#include <stdio.h>
FILE *fopen(const char *restrict pathname, const char *restrict type);
返回值:若成功则返回文件指针,若出错则返回NULL
参数 pathname 指定了文件路径;而参数 type 指定了对该 I/O 流的读、写方式,ISO C 规定 type 参数可以有 15 种不同的值,其分别如表 1所示,
type | 说 明 |
r 或 rb | 为读而打开 |
w 或 wb | 把文件截短至 0 长,为写而打开 |
a 或 ab | 添加;为在文件尾写而打开 |
r+ 或 rb+ 或 r+b | 为读和写而打开 |
w+ 或 wb+ 或 w+b | 把文件截短只 0 长,或为读和写而打开 |
a+ 或 ab+ 或 a+b | 为在文件尾读和写而打开或创建 |
使用字符 b 作为 type 的一部分,是为了区分文本文件和二进制文件。但是对于 UNIX 系统来说,并不区分这两种文件,所有有没有字符 b 都是一样的。
除非流引用终端设备,否则按系统默认的情况,流被打开时是全缓冲的。若流引用终端设备,则该流是行缓冲的。
因为输入和输出都是同一个位置指针,所以当以读和写类型打开一个文件时(type 中 + 符号),具有下面限制:
• 如果中间没有 fflush、fseek、fsetpos 或 rewind,则在输出的后面不能直接跟随输入。
• 如果中间没有 fseek、fsetpos 或 rewind,或这一个输入操作没有达到文件尾端,则在输入操作之后不能直接跟随输出。
UNIX 系统还提供了另外两个函数用于打开标准 I/O 流,
#include <stdio.h>
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict f
FILE *fdopen(int filedes, const char *type);
这两个函数返回值:若成功则返回文件指针,若出错则返回NULL
freopen 函数在一个指定的流上打开指定的文件,如若该流已经打开,则先关闭该流。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准出错。
fdopen 函数获取一个现有的文件描述符,并使一个标准 I/O 流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数返回的描述符。
4 读和写流
标准 I/O 库提供了三种不同类型的非格式化 I/O 来对流进行读、写操作。
1. 每次一个字符的 I/O:一次读或写一个字符。
2. 每次一行的 I/O:每次读或写一行,每行都以一个换行符终止。
3. 直接 I/O:每次读或写指定长度的数据。
4.1 每次一个字符的 I/O
每次一个字符的输入函数为,
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
三个函数的返回值:若成功则返回下一个字符,若已达到文件结尾或出错则返回EOF
函数 getchar() 等价于 getc(stdin)。getc 和 fgetc 两个函数的区别是:getc可被实现为宏,而 fgetc 不能被实现为宏。
每次一个字符的输出函数为,
#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 函数不能被实现为宏。
4.2 每次一行的 I/O
每次输入一行的输入函数,
#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
两个函数的返回值:若成功则返回buf,若已达到文件结尾或出错则返回NULL
gets 函数从标准输入读取,但是 gets 函数不推荐使用,因其未指定缓冲区大小,可能会造成缓冲区溢出。另外,gets 与 fgets 的另一个不同是,gets函数没有将换行符存入缓冲区中。
每次输出一行的输出函数,
#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
两个函数返回值:若成功则返回非负数,若出错则返回EOF。
fputs 和 puts 两个函数都是将以 null 符终止的字符串写入到特定的流中(puts 函数写入到标准输出流),终止符不输出。但是,puts 函数会自动在输出字符串之后,再输出一个换行符。
4.3 直接 I/O
直接 I/O 也被称为二进制 I/O、一次一个对象 I/O、面向记录的 I/O或面向结构的 I/O,因为这种 I/O 函数通常被用于二进制 I/O 操作,一次读写一个二进制数组或者一个结构。
#include <stdio.h>
size_t fread(viod *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);
size_t fwrite(const void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);
两个函数的返回值:读或写的对象数
这两个函数的 ptr 参数表示保存输入数据或要输出数据的缓冲区指针,size参数指定每个对象的大小,而 nobj 参数指定对象个数。
下面给出两个例子,
1. 读或写一个二进制数组。例如,将一个浮点数组的第 2 ~ 5 个元素写至一个文件上,
float data[10];
if (fwrite(&data[2], sizeof(float), 4, fp) != 4) {
printf("fwrite error");
}
2. 读或写一个结构。
struct {
short count;
long total;
char name[NAMESIZE];
} item;
if (fwrite(&item, sizeof(item), 1, fp) != 1) {
printf("fwrite error");
}
5 出错标志
在标准 I/O 流中通常维持了两个标志:
• 出错标志。
• 文件结束标志。
通过前面的几个输入函数的介绍,我们发现在读取输入时,对于达到文件结尾和读取出错,通过返回值无法来判断是哪种情况。为了区分这两个情况,我们可以通过 ferror 或 feof 来进行判断,
#include <stdio.h>
int ferror(FILE *fp);
返回值:如果文件出错标志被设置则返回非0值(真),否则返回0(假)int feof(FILE *fp);
返回值:如果文件达到结尾则返回非0值(真),否则返回0(假)
调用 clearerr 函数可以清除这两个标志。
#include <stdio.h>
void clearerr(FILE *fp);
6 关闭流
调用 fclose 函数可以关闭一个打开的流。
#include <stdio.h>
int fclose(FILE *fp);
返回值:若成功则返回0,若出错则返回EOF
在流被关闭之前,系统会自动冲洗缓冲区中的输出数据,并丢弃缓冲区中的任何输入数据,然后释放缓冲区(如果存在的话)。
当一个进程正常终止时(直接调用 exit 函数,或从 main 函数返回),则所有带未写缓冲数据的标准 I/O 流都会被冲洗,所有被打开的标准 I/O流都会被关闭。
7 定位流
UNIX 系统提供三套定位标准 I/O 流的方法,
1. ftell 和 fseek 函数。
2. ftello 和 fseeko 函数。这两个函数是 Single UNIX Specification 引入的。
3. fgetpos 和 fsetpos 函数。这两个函数是 ISO C 引入的。
#include <stdio.h>
long ftell(FILE *fp);
返回值:若成功则返回当前文件位置,若出错则返回-1Lint fseek(FILE *fp, long offset, int whence);
返回值:若成功则返回0,若出错则返回非0值void rewind(FILE *fp);
使用 rewind 函数可以将一个流设置到文件的起始位置。对于二进制文件,fseek 定位流时必须指定偏移量 offset,以及解释这个偏移量的方式 whence。whence 的值可以是:SEEK_SET 表示从文件的起始位置开始,SEEK_CUR 表示从当前文件位置开始,SEEK_END 表示从文件的尾端开始。
对于文本文件,fseek 函数的 whence 参数必须是SEEK_SET,而且 offset只能是两种值:0,或是对该文件调用 ftell 所返回的值。因为在文本文件中,文件的当前位置可能不是以简单的字节偏移量来度量的。
#include <stdio.h>
off_t ftello(FILE *fp);
返回值:若成功则返回当前文件位置,若出错则返回-1int fseeko(FILE *fp, off_t offset, int whence);
返回值:若成功则返回0,若出错则返回非0值
C 标准的两个函数 fgetpos 和 fsetpos,
#include <stdio.h>
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, cosnt fpos_t *pos);
(done)