标准I/O库处理很多细节,如缓冲区分配、以优化的块长度执行I/O等。这些处理使用户不必担心如何选择正确的块长度。

缓冲

标准I/O库提供缓冲的目的是尽可能减少使用read和write的调用次数。标准I/O提供了3种类型的缓冲。

  1. 全缓冲。在这种情况下,在填满标准I/O缓冲区后才进行实际的I/O操作。对于驻留在磁盘上的文件通常是由标准I/O库实施全缓冲的。flush说明标准I/O缓冲区的写操作。缓冲区可由标准I/O例程自动flush,或者可以调用函数fflush冲洗一个流。
  2. 行缓冲。在这种情况下,当输入和输出中遇到换行符时,标准I/O库执行I/O操作。当流涉及一个终端时,通常使用行缓冲。
  3. 不带缓冲。标准I/O库不对字符进行缓冲存储。标准错误流stderr通常是不带缓冲的,这就使得出错信息可以尽快显示出来。

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

#include <stdio.h>

void setbuf(FILE *fp, char *buf);
int setvbuf(FILE *fp, char *buf, int mode, size_t size);

返回值:若成功,返回0;若出错,返回非0

可以使用setbuf函数打开或关闭缓冲机制。为了带缓冲I/O,参数buf必须指向一个长度为BUFSIZ的缓冲区(该常量定义在<stdio.h>中)。通常在此之后该流就是全缓冲的。为了关闭缓冲,将buf设置为NULL。

使用setvbuf函数,可以精确的说明所需的缓冲类型。这是用mode参数实现的:

  • _IOFBF 全缓冲
  • _IOLBF 行缓冲
  • _IONBF 不带缓冲

如果指定一个不带缓冲的流,则忽略buf和size参数。如果指定全缓冲或行缓冲,则buf和size可选择地指定一个缓冲区及其长度。如果该流是带缓冲的,而buf是NULL,则标准I/O库将自动地为该流分配适当长度的缓冲区。适当长度指的是由常量BUFSIZ所指定的值。

一般而言,应由系统选择缓冲区的长度,并自动分配缓冲区。

任何时候,我们都可以用fflush函数冲洗一个流。

#include <stdio.h>

int fflush(FILE *fp);

返回值:若成功,返回0;若出错,返回EOF

打开流

#include <stdio.h>

FILE *fopen(const char *pathname, const char *type);
FILE *freopen(const char *pathname, const char *type, FILE *fp);
FILE *fdopen(int fd, const char *type);

3个函数返回值:若成功,返回文件指针;若出错,返回NULL

这三个函数的区别如下。

  1. fopen函数打开路径名为pathname的一个指定的文件。
  2. freopen函数再一个指定的流上打开一个指定的文件,如该流已经打开,则先关闭该流。如该流已经定向,则使用freopen清除该定向。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准错误。
  3. fdopen函数取一个已有的文件描述符,并使一个标准的I/O流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数返回的描述符。因为这些特殊类型文件不能用标准I/O函数fopen

参数type指定对该I/O流的读写方式。

除非流引用了终端设备,否则按系统默认,流被打开时是全缓冲的。若流引用终端设备,则该流是行缓冲的。

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

#include <stdio.h>

int fclose(FILE *fp);

返回值:若成功,返回0;若出错,返回EOF

在该文件被关闭之前,冲洗缓冲中的输出数据。当一个进程正常终止时,则所有带未写缓冲数据的标准I/O流都被冲洗,所有打开的标准I/O流都被关闭。

读写流

一旦打开了流,则可在三种不同类型的非格式化I/O中进行选择,对其进行读写操作。

  1. 每次一个字符的I/O。
  2. 每次一行的I/O。如果想要每次读或写一行,则使用fgets或fputs。每次都以换行符终止。当调用fgets时,应说明能处理的最大行长度。
  3. 直接I/O。fread和fwrite函数支持这种类型的I/O。这两个函数常用于从二进制文件中每次读或写一个结构。

每次一个字符I/O

#include <stdio.h>

int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);

三个函数的返回值:若成功,返回下一个字符;若已到达文件尾或出错,返回EOF

前两个函数的区别是,getc可被实现为宏,而fgetc不能实现为宏。这意味着以下几点。

  1. getc的参数不能是具有副作用的表达式。
  2. 因为fgetc一定是一个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传送给另一个函数。
  3. 调用fgetc所需的时间很可能比调用getc要长,因为调用函数的时间通常长于调用宏。

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

#include <stdio.h>

int ferror(FILE *fp);
int feof(FILE *fp);
两个函数返回值:若条件为真,返回非0;否则,返回0

void clearerr(FILE *fp);

大多数实现中,为每个流在FILE对象中维护了两个标志:出错标志;文件结束标志。调用clearerr可以清除这两个标志。

对应于getc,fgetc和getchar三个输入函数,输出函数如下。

#include <stdio.h>

int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);

三个函数返回值:若成功,返回c;若出错,返回EOF

每次一行I/O

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

#include <stdio.h>

char *fgets(char *buf, int n, FILE *fp);
char *gets(char *buf);

两个函数返回值:若成功,返回buf;若已到达文件尾或出错,返回NULL

gets从标准输入读,而fgets从指定的流读。对于fgets,必须指定缓冲的长度n,此函数一直读到写一个换行符为止,但是不超过n-1个字符,读入的字符被送入缓冲区。该缓冲区以null字符结尾。如果改行包括最后一个换行符的字符数超过n-1,则fgets只返回一个不完整的行,但是,缓冲区总是以null字符结尾。对fgets的下一次调用会继续读该行。gets是一个不推荐使用的函数。其问题是调用者在使用gets时不能指定缓冲区的长度。这有可能导致内存溢出,产生不可预料的结果。

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

#include <stdio.h>

int fputs(const char *str, FILE *fp);
int puts(const char *str);

两个函数返回值:若成功,返回非负值;若出错,返回EOF

函数fputs将一个以null字符终止的字符串写到指定的流,尾端的null字符不写出。puts将一个以null字符终止的字符串写到标准输出,null字符不写出。但是,puts随后又将一个换行符写到标准输出。

二进制I/O

#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp);

两个函数返回值:读或写的对象数

fread和fwrite返回读或写的对象数。对于读,如果出错或到达文件尾端,则此数字可以少于nobj。在这种情况应该用ferror或feof以判断究竟是哪一种情况。对于写,如果返回值少于nobj,则出错。

定位流

有三种方式可以定位标准I/O流。

  1. ftell和fseek函数。它们都假定文件的位置可以存放在一个长整形中。
  2. ftello和fseeko函数。它们用off_t代替了long。
  3. fgetpos和fsetpos函数。它们使用一个抽象数据类型fpos_t记录文件位置。这种数据类型可以根据需要定义为一个足够大的数,用以记录文件位置。
#include <stdio.h>

long ftell(FILE *fp);
返回值:若成功,返回当前文件位置指示;若出错,返回-1L

int fseek(FILE *fp, long offset, int whence);
返回值:若成功,返回0;若出错,返回-1

void rewind(FILE *fp);

使用rewind函数也可以将一个流设置到文件的起始位置。

除了偏移量的类型是off_t以外,ftello函数与ftell相同,fseeko与fseek相同。

#include <stdio.h>

off_t ftello(FILE *fp);
返回值:若成功,返回当前文件位置;若出错,返回(off_t)-1

int fseeko(FILE *fp, off_t offset, int whence);
返回值:若成功,返回0;若出错,返回-1
#include <stdio.h>

int fgetpos(FILE *fp, fpos_t *pos);
int fsetpos(FILE *fp, const fpos_t *pos);

两个函数返回值:若成功,返回0;若出错,返回-1

fgetpos将文件位置指示器的当前值存入pos指向的对象中。在以后调用fsetpos时,可以使用此值将流重新定位至该位置。

格式化I/O

5个格式化输出函数。

#include <stdio.h>

int printf(const char *format, ...);
int fprintf(FILE *fp, const char *format, ...);
int dprintf(int fd, const char *format, ...);
三个函数返回值:若成功,返回输出字符数;若输出错误,返回负值

int sprintf(char *buf, const char *format, ...);
返回值:若成功,返回存入数组的字符数;若出错,返回负值

int snprintf(char *buf, size_t n, const char *format, ...);
返回值:若缓冲区足够大,返回将要存入数组的字符数;若出错,返回负值

printf将格式化数据写到标准输出,fprintf写到指定的流,dprintf写到指定的文件描述符,sprintf将格式化数据写入buf中,sprintf自动在buf尾端添加null字符,但null字符不包含在返回值中。snprintf指定了缓冲区的长度,如果输入的字符数超过缓冲区长度,则将被丢弃。使用snprintf更加安全。

5个格式化输出函数的变体,将可变参数换成了arg。

#include <stdarg.h>
#include <stdio.h>

int vprintf(const char *format, va_list arg);
int vfprintf(FILE *fp, const char *format, va_list arg);
int vdprintf(int fd, const char *format, va_list arg);
3个函数的返回值:若成功,返回输出的字符数;若出错,返回负值

int vsprintf(char *buf, const char *format, va_list arg);
返回值:若成功,返回存入数组的字符数;若出错,返回负值

int vsnprintf(char *buf, size_t n, const char *format, va_list arg);
返回值:若缓冲区足够大,返回存入数组的字符数;若出错,返回负值

格式化输出转换说明

一个转换说明有4个可选的部分。

%[flags][fieldwidth][precision][lenmodifier]convtype
  1. flags
  2. fieldwidth。说明最小字段宽度。转换后字符数若小于宽度,则多余字符位置用空格填充。字符宽度是一个非负十进制整数,或者是一个星号(*)。
  3. precision。
  4. lenmodifier。
  5. convtype。

对于转换说明请参考APUE的第128页到129页的详细说明。

格式化输入函数

#include <stdio.h>

int scanf(const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);
int sscanf(const char *buf, const char *format, ...);

三个函数的返回值:赋值的输入项数;若输入出错或在任一转换前已经到达文件尾端,返回EOF
#include <stdarg.h>
#include <stdio.h>

int vscanf(const char *format, va_list arg);
int vfscanf(FILE *fp, const char *format, va_list arg);
int vsscanf(const char *buf, const char *format, va_list arg);

三个函数返回值:指定的输入项目数;若出错或在任一转换前文件结束,返回EOF

可以使用fileno函数获取流的文件描述符。

#include <stdio.h>

int fileno(FILE *fp);

返回值:与该流相关的文件描述符

临时文件

ISO C标准库提供了两个函数以帮助创建临时文件。

#include <stdio.h>

char *tmpnam(char *ptr);
返回值:指向唯一路径名的指针

FILE *tmpfile(void);
返回值:若成功,返回文件指针;若出错,返回NULL

tmpnam产生一个与现有文件名不同的有效路径名字符串。tmpfile创建一个临时二进制文件(wb+),在关闭该文件或程序结束时将自动删除该文件。

Single UNIX Specification为处理临时文件定义了另外两个函数。

#include <stdlib.h>

char *mkdtemp(char *template);
返回值:若成功,返回指向目录名的指针;若出错,返回NULL

int mkstemp(char *template);
返回值:若成功,返回文件描述符;若出错,返回-1

内存流

有3个函数可以用于内存流的创建。

#include <stdio.h>

FILE *fmemopen(void *buf, size_t size, const char *type);
返回值:若成功,返回流指针;若错误,返回NULL
02-19 00:32