一旦打开了流,则可在 3 种不同类型的非格式化 I/O 中进行选择,对其进行读、写操作。
(1) 每次一个字符的 I/O。如果流是带缓冲的,则标准 I/O 函数处理所有缓冲。
(2) 每次一行的 I/O。
(3) 直接 I/O,有时也被称为二进制 I/O。fread 和 fwrite 函数支持这种类型的 I/O。每次 I/O 操作读或写某种数量的对象,而每个对象具有指定的长度。这两个函数通常用于从二进制文件中每次读或写一个结构。
以下 3 个函数可用于一次读一个字符。
函数 getchar 等同于 getc(stdin)。前两个函数的区别是:getc 可被实现为宏,而 fgetc 不能。这就意味着以下几点。
1、getc 的参数不应当是具有副作用的表达式,以免被计算多次。
2、因为 fgetc 一定是一个函数,所以可以得到其地址。因此允许将 fgetc 的地址作为另一个函数的参数。
为了区分是出错还是到达尾端,必须调用 ferror 或 feof。
在大多数实现中,每个流在 FILE 对象中都维护了两个标志:出错标志和文件结束标志。调用 clearerr 可以清除这两个标志。
从流中读取数据以后,可以调用 ungetc 将字符再压送回流中。
压送回流中的字符以后又可以与压送回的顺序相反的顺序从流中读出。回送的字符,不一定必须是上一次读到的字符。不能回送 EOF,但是当已经到达文件尾端时,仍可以回送一个字符。下次读将返回该字符,再读则返回 EOF。这样做的原因是,一次成功的 ungetc 调用会清除该流的文件结束标记。
当正在读一个输入流,并进行某种形式的切词或记号切分操作时,会经常用到回送字符操作。有时需要先看一看下一个字符,以决定如何处理当前字符。然后就需要方便地将刚查看的字符回送,以便下一次调用 getc 时返回该字符。
用 ungetc 压送回字符时,并没有将它们写到底层文件中或设备上,只是将它们写会标准 I/O 库的流缓冲区中。
对应于上面所述的每个输入函数都有一个输出函数。
同输入函数一样,putchar(c) 等同于 putc(c, stdout),putc 可被实现为宏,而 fputc 不能。
下面再来介绍一下每次输入一行的函数。
gets 从标准输入读,而 fgets 则从指定的流读。
对于 fgets,必须指定缓冲的长度 n。此函数一直读到下一个换行符为止,但不超过 n-1 个字符,读入的字符被送入缓冲区。该缓冲区以 null 字节结尾。如若该行包括最后一个换行符的字符数超过 n-1,则 fgets 只返回一个不完整的行,但是,缓冲区总是以 null 字节结尾,下一次调用会继续读该行。
gets 因为不能指定缓冲区的长度,从而可能造成缓冲区溢出,所以并不推荐使用。它与 fgets 的另一个区别是,gets 并不将换行符存入缓冲区中。
fputs 和 puts 是用于每次输出一行的函数。
函数 fputs 将一个以 null 字节终止的字符串写到指定的流,尾端的终止符 null 不写出。不过这并不一定是每次输出一行,因为字符串不需要换行符作为最后一个非 null 字节。puts 将一个以 null 字节终止的字符串写到标准输出,终止符不写出,但随后又将一个换行符写到标准输出。
注意,如果总是使用 fgets 和 fputs,那么必须记住在每行终止处需要自己处理换行符。
最后,我们再来讨论一下二进制 I/O 操作函数。
在进行二进制 I/O 操作时,一般都是一次读或写一个完整的结构。若使用 getc 或 putc,不仅麻烦还且费时;而若使用 fgets 或 fputs,又可能会因结构中含有 null 字节而停止。因此提供了下列两个函数。
这两个函数有两种常见的用法:
1、读或写一个二进制数组。例如将一个浮点数组的第 2~5个元素写至一个文件上:
if( fwrite(&data[2], sizeof(float), 4, fp) != 4){ /* 处理错误 */ }
2、读或写一个结构。例如:
struct{ /*...*/ }item;
if( fwrite(&item, sizeof(item), 1, fp) != 1){ /* 处理错误 */ }
将这两者结合起来,就可读或写一个结构数组。
fread 和 fwrite 返回读或写的对象数。对于读,如果出错或到达文件尾端,则此数字可以少于 nobj。此时应调用 ferror 或 feof 来判断究竟是哪一种情况。对于写,如果该数字少于 nobj,则表示出错。
使用二进制 I/O 的基本问题是,它只能用于读在同一系统上已写的数据。这对于现在很多通过网络相连起来的异构系统的情况,它们可能就不能正常工作。在不同系统之间交换二进制数据的实际解决办法是使用互认的规范格式。
要定位标准 I/O 流,主要有 3 种方法。
1、ftell 和 fseek 函数,但是它们都假定文件的位置可以存放在一个长整型中。
2、ftello 和 fseeko 函数,它们使用了 off_t 数据类型代替了长整型。
3、fgetpos 和 fsetpos 函数,它们使用一个抽象数据类型 fpos_t 记录文件的位置。这种数据类型可以根据需要定义为一个足够大的数,用以记录文件位置。需要移植到非 UNIX 系统上运行的应用程序应当使用这两个函数。
对于二进制文件,其文件位置是从文件起始位置以字节为单位开始度量。ftell 用于二进制文件时的返回值就是这种字节位置。fseek 中的 whence 的值同 lseek 函数,不过 ISO C 并不要求一个实现对二进制文件支持 SEEK_END 规格说明,因为有些系统要求二进制文件的长度是某个幻数的整数倍,结尾非实际内容部分则填充为 0(但是在 UNIX 中则是支持的)。
对于文本文件,它们的文件当前位置可能不以简单的字节偏移量来度量。因为非 UNIX 系统中可能以不同的格式存放文本文件。为了定位一个文本文件,whence 一定要是 SEEK_SET,而且 offset 只能为 0(后退到文件的起始位置)或是对该文件的 ftell 所返回的值。另外,使用 rewind 函数也可将一个流设置到文件的起始位置。
除了偏移量的类型是 off_t 以外,ftello/fseeko 函数与 ftell/fseek 相同。
fgetpos 将文件位置的当前值存入由 pos 指向的对象中,以后调用 fsetpos 时,可以使用此值将流重新定位至该位置。
(1) 每次一个字符的 I/O。如果流是带缓冲的,则标准 I/O 函数处理所有缓冲。
(2) 每次一行的 I/O。
(3) 直接 I/O,有时也被称为二进制 I/O。fread 和 fwrite 函数支持这种类型的 I/O。每次 I/O 操作读或写某种数量的对象,而每个对象具有指定的长度。这两个函数通常用于从二进制文件中每次读或写一个结构。
以下 3 个函数可用于一次读一个字符。
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
/* 返回值:若成功,都返回下一个字符;若已到达文件尾端或出错,则都返回 EOF */
函数 getchar 等同于 getc(stdin)。前两个函数的区别是:getc 可被实现为宏,而 fgetc 不能。这就意味着以下几点。
1、getc 的参数不应当是具有副作用的表达式,以免被计算多次。
2、因为 fgetc 一定是一个函数,所以可以得到其地址。因此允许将 fgetc 的地址作为另一个函数的参数。
为了区分是出错还是到达尾端,必须调用 ferror 或 feof。
#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
/* 返回值:若条件为真,都返回非 0;否则,都返回 0 */
void clearerr(FILE *fp);
在大多数实现中,每个流在 FILE 对象中都维护了两个标志:出错标志和文件结束标志。调用 clearerr 可以清除这两个标志。
从流中读取数据以后,可以调用 ungetc 将字符再压送回流中。
#include <stdio.h>
int ungetc(int c, FILE *fp); /* 返回值:若成功,返回 c;否则,返回 EOF */
压送回流中的字符以后又可以与压送回的顺序相反的顺序从流中读出。回送的字符,不一定必须是上一次读到的字符。不能回送 EOF,但是当已经到达文件尾端时,仍可以回送一个字符。下次读将返回该字符,再读则返回 EOF。这样做的原因是,一次成功的 ungetc 调用会清除该流的文件结束标记。
当正在读一个输入流,并进行某种形式的切词或记号切分操作时,会经常用到回送字符操作。有时需要先看一看下一个字符,以决定如何处理当前字符。然后就需要方便地将刚查看的字符回送,以便下一次调用 getc 时返回该字符。
用 ungetc 压送回字符时,并没有将它们写到底层文件中或设备上,只是将它们写会标准 I/O 库的流缓冲区中。
对应于上面所述的每个输入函数都有一个输出函数。
#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 不能。
下面再来介绍一下每次输入一行的函数。
#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
/* 返回值:若成功,返回 buf;若已到达文件尾端或出错,返回 NULL */
gets 从标准输入读,而 fgets 则从指定的流读。
对于 fgets,必须指定缓冲的长度 n。此函数一直读到下一个换行符为止,但不超过 n-1 个字符,读入的字符被送入缓冲区。该缓冲区以 null 字节结尾。如若该行包括最后一个换行符的字符数超过 n-1,则 fgets 只返回一个不完整的行,但是,缓冲区总是以 null 字节结尾,下一次调用会继续读该行。
gets 因为不能指定缓冲区的长度,从而可能造成缓冲区溢出,所以并不推荐使用。它与 fgets 的另一个区别是,gets 并不将换行符存入缓冲区中。
fputs 和 puts 是用于每次输出一行的函数。
#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
/* 返回值:若成功,都返回非负值;否则,都返回 EOF */
函数 fputs 将一个以 null 字节终止的字符串写到指定的流,尾端的终止符 null 不写出。不过这并不一定是每次输出一行,因为字符串不需要换行符作为最后一个非 null 字节。puts 将一个以 null 字节终止的字符串写到标准输出,终止符不写出,但随后又将一个换行符写到标准输出。
注意,如果总是使用 fgets 和 fputs,那么必须记住在每行终止处需要自己处理换行符。
最后,我们再来讨论一下二进制 I/O 操作函数。
在进行二进制 I/O 操作时,一般都是一次读或写一个完整的结构。若使用 getc 或 putc,不仅麻烦还且费时;而若使用 fgets 或 fputs,又可能会因结构中含有 null 字节而停止。因此提供了下列两个函数。
#include <stdio.h>
size_t fread(void *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);
/* 两个函数的返回值:读或写的对象数 */
这两个函数有两种常见的用法:
1、读或写一个二进制数组。例如将一个浮点数组的第 2~5个元素写至一个文件上:
if( fwrite(&data[2], sizeof(float), 4, fp) != 4){ /* 处理错误 */ }
2、读或写一个结构。例如:
struct{ /*...*/ }item;
if( fwrite(&item, sizeof(item), 1, fp) != 1){ /* 处理错误 */ }
将这两者结合起来,就可读或写一个结构数组。
fread 和 fwrite 返回读或写的对象数。对于读,如果出错或到达文件尾端,则此数字可以少于 nobj。此时应调用 ferror 或 feof 来判断究竟是哪一种情况。对于写,如果该数字少于 nobj,则表示出错。
使用二进制 I/O 的基本问题是,它只能用于读在同一系统上已写的数据。这对于现在很多通过网络相连起来的异构系统的情况,它们可能就不能正常工作。在不同系统之间交换二进制数据的实际解决办法是使用互认的规范格式。
要定位标准 I/O 流,主要有 3 种方法。
1、ftell 和 fseek 函数,但是它们都假定文件的位置可以存放在一个长整型中。
2、ftello 和 fseeko 函数,它们使用了 off_t 数据类型代替了长整型。
3、fgetpos 和 fsetpos 函数,它们使用一个抽象数据类型 fpos_t 记录文件的位置。这种数据类型可以根据需要定义为一个足够大的数,用以记录文件位置。需要移植到非 UNIX 系统上运行的应用程序应当使用这两个函数。
#include <stdio.h>
long ftell(FILE *fp); /* 返回值:若成功,返回当前文件位置;否则,返回 -1L */
int fseek(FILE *fp, long offset, int whence); /* 返回值:若成功,返回 0;否则,返回 -1 */
void rewind(FILE *fp);
off_t ftello(FILE *fp); /* 返回值:若成功,返回当前文件位置;否则,返回 - 1 */
int fseeko(FILE *fp, off_t offset, int whence); /* 返回值:若成功,返回 0;否则,返回 -1 */
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);
/* 返回值:若成功,都返回 0;否则,都返回非 0 */
对于二进制文件,其文件位置是从文件起始位置以字节为单位开始度量。ftell 用于二进制文件时的返回值就是这种字节位置。fseek 中的 whence 的值同 lseek 函数,不过 ISO C 并不要求一个实现对二进制文件支持 SEEK_END 规格说明,因为有些系统要求二进制文件的长度是某个幻数的整数倍,结尾非实际内容部分则填充为 0(但是在 UNIX 中则是支持的)。
对于文本文件,它们的文件当前位置可能不以简单的字节偏移量来度量。因为非 UNIX 系统中可能以不同的格式存放文本文件。为了定位一个文本文件,whence 一定要是 SEEK_SET,而且 offset 只能为 0(后退到文件的起始位置)或是对该文件的 ftell 所返回的值。另外,使用 rewind 函数也可将一个流设置到文件的起始位置。
除了偏移量的类型是 off_t 以外,ftello/fseeko 函数与 ftell/fseek 相同。
fgetpos 将文件位置的当前值存入由 pos 指向的对象中,以后调用 fsetpos 时,可以使用此值将流重新定位至该位置。