APUE - 标准 I/O 库

流和 FILE 对象

对于标准 I/O 库,它们的操作是围绕流进行的。当用标准 I/O 库打开或创建一个文件时,我们已使一个流与一个文件相关联。

流的定向决定了所读、写的字符是单字节还是多字节的。当一个流最初被创建时,它并没有定向。如若在未定向的流上使用一个多字节 I/O 函数,则将该流的定向设置为宽定向的。若在未定向的流上使用一个单字节 I/O 函数,则将该流的定向设置为字节定向的。

fwide 函数设置流的定向。

#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp, int mode);
  • 如若 mode 参数值为负,fwide 将试图使指定的流是字节定向的。
  • 如若 mode 参数值为正,fwide 将试图使指定的流是宽定向的。
  • 如若 mode 参数值为0,fwide 将不试图设置流的定向,但返回标识该流定向的值。

注意,fwide 并不改变已定向流的定向,也无出错返回。

缓冲

标准 I/O 提供了三种类型的缓冲:

  1. 全缓冲。在这种情况下,在填满标准 I/O 缓冲区后才进行实际 I/O 操作。
  2. 行缓冲。在这种情况下,当在输入和输出中遇到换行符时,标准 I/O库 执行 I/O 操作。
  3. 不带缓冲。标准 I/O库 不对字符进行缓冲存储。

ISO C 要求下列缓冲特征:

  • 当且仅当标准输入和标准输出并不涉及交互式设备时,它们才是全缓冲的。
  • 标准出错决不会是全缓冲的。

很多系统默认使用下列类型的缓冲:

  • 标准出错是不带缓冲的。
  • 如若是涉及终端设备的其他流,则它们是行缓冲的;否则是全缓冲的。

对任何一个给定的流,如果我们并不喜欢这些系统默认的情况,则可调用下列两个函数中的一个更改缓冲类型:

#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);

任何时候,我们都可以强制冲洗一个流。

#include <stdio.h>
int fflush(FILE *fp);

打开流

下列三个函数打开一个标准 I/O 流:

#include <stdio.h>
FILE *fopen(const char *restrict pathname, const char *restrict type);
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp);
FILE *fdopen(int filedes, const char *type);

这三个函数的区别是:

  1. fopen 打开一个指定的文件。
  2. freopen 在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流。若该流已经定向,则 freopen 清除该定向。
  3. fdopen 获取一个现有的文件描述符,并使一个标准的 I/O 流与该描述符相结合。

调用 fclose 关闭一个打开的流。

#include <stdio.h>
int close(FILE *fp);

在该文件被关闭之前,冲洗缓冲区中的输出数据。丢弃缓冲区中的任何输入数据。如果标准 I/O 库已经为该流自动分配了一个缓冲区,则释放此缓冲区。

读和写流

输入函数

以下三个函数可用于一次读一个字符。

#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);

注意,不管是出错还是到达文件尾端,以上三个函数都返回同样的值。为了区分这两种不同的情况,必须调用 ferror 或 feof。

#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);

void clearerr(FILE *fp);

大多数实现中,为每个流在 FILE 对象中维持了两个标志:

  1. 出错标志。
  2. 文件结束标志。

调用 clearerr 则清除这两个标志。

从流中读取数据以后,可以调用 ungetc 将字符再压送回流中。

#include <stdio.h>
int ungetc(int c, FILE *fp);

压送回到流中的字符以后又可从流中读出,但读出字符的顺序与压送回的顺序相反。虽然 ISO C 允许实现支持任何次数的回送,但是它要求实现提供一次只送回一个字符。我们不能期望一次能送回多个字符。

回送的字符不必一定是上一次读到的字符。不能回送 EOF。但是当已经到达文件尾端时,仍可以回送一个字符。下次读将返回该字符,再次读则返回 EOF。之所以能这样做的原因是一次成功的 ungetc 调用会清除该流的文件结束标志。

用 ungetc 压送回字符时,并没有将它们写到文件中或设备上,只是将它们写回标准 I/O 库的流缓冲区中。

输出函数

对应于上面所述的每个输入函数都有一个输出函数。

#include <stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);

每次一行 I/O

下面两个函数提供每次输入一行的功能。

#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);

fputs 和 puts 提供每次输出一行的功能。

#include <stdio.h>
int *fputs(const char *restrict str, FILE *restrict fp);
int *puts(const char *str);

二进制 I/O

下列两个函数执行二进制 I/O 操作。

#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);

使用二进制 I/O 的基本问题是,它只能用于读在同一系统上已写的数据。其原因是:

  1. 在一个结构中,同一成员的偏移量可能因编译器和系统而异。即使在同一个系统上,一个结构的二进制存放方式也可能因编译器选项的不同而不同。
  2. 用来存储多字节整数和浮点值的二进制格式在不同的机器体系结构间也可能不同。

定位流

有三种方法定位标准 I/O 流。

  1. ftell 和 fseek 函数。它们都假定文件的位置可以存放在一个长整型中。
  2. ftello 和 fseeko 函数。它们使用 off_t 数据类型代替了长整型。
  3. fgetpos 和 fsetpos 函数。它们使用一个抽象数据类型 fpos_t 记录文件的位置。

需要移植到非 Unix 系统上运行的应用程序应当使用 fgetpos 和 fsetpos。

#include <stdio.h>
long ftell(FILE *fp);
int fseek(FILE *fp, long offset, int whence);
void rewind(FILE *fp);
off_t ftello(FILE *fp);
int fseeko(FILE *fp, off_t offset, int whence);
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);

格式化 I/O

格式化输出

#include <stdio.h>
int printf(const char *restrict format, ... );
int fprintf(FILE *restrict fp, const char *restrict format, ... );
int sprintf(char *restrict buf, const char *restrict format, ... );
int snprintf(char *restrict buf, size_t n, const char *restrict format, ... );

#include <stdarg.h>
#include <stdio.h>
int vprintf(const char *restrict format, va_list arg);
int vfprintf(FILE *restrict fp, const char *restrict format, va_list arg);
int vsprintf(char *restrict buf, const char *restrict format, va_list arg);
int vsnprintf(char *restrict buf, size_t n, const char *restrict format, va_list arg);

格式化输入

#include <stdio.h>
int scanf(const char *restrict format, ... );
int fscanf(FILE *restrict fp, const char *restrict format, ... );
int sscanf(const char *restrict buf, const char *restrict format, ... );

#include <stdarg.h>
#include <stdio.h>
int vscanf(const char *restrict format, va_list arg);
int vfscanf(FILE *restrict fp, const char *restrict format, va_list arg);
int vsscanf(const char *restrict buf, const char *restrict format, va_list arg);

fileno 函数

每个标准 I/O 流都有一个与其相关联的文件描述符,可以对一个流调用 fileno 函数以获得其描述符。

#include <stdio.h>
int fileno(FILE *fp);

临时文件

#include <stdio.h>
char *tmpnam(char *ptr);
FILE *tmpfile(void);

#include <stdio.h>
char *tempnam(const char *directory, const char *prefix);

#include <stdlib.h>
int mkstemp(char *template);