1.流和FILE对象。
2.标准输入、标准输出和标准错误
3.缓冲
标准I/O提供3种类型缓冲:
可以对I/O缓冲的类型:
#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
这些函数一定要在流被打开后调用。
5.打开流
下列3个函数打开一个标准I/O流:
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 fd, const char *type);
type参数指定对该I/O流的读、写方式,ISO C规定type参数有15种不同值:
type | 说明 | open标志 |
---|---|---|
r或rb | 为读而打开 | O_RDONLY |
w或wb | 把文件截断为0长,或为写而创建 | O_WRONLY/O_CREAT/O_TRUNC |
a或ab | 追加,为在文件尾写而打开,或为写而创建 | O_WRONLY/O_CREAT/O_APPEND |
r+或r+b或rb+ | 为读写而打开 | O_RDWR |
w+或w+b或wb+ | 把文件截断为0长,或为读写而打开 | O_RDWR/O_CREAT/O_TRUNC |
a+或a+b或ab+ | 为在文件尾读写而打开或创建 | O_RDWR/O_CREAT/O_APPEND |
使用字符b作为type的一部分,使得标准I/O系统可以区分文本文件和二进制文件,但内核对这两种文件并不区分。
调用fclose关闭一个打开的流。
6.读和写流
一旦打开了流,则可在三种不同类型的非格式化I/O中进行选择,对其进行读、写操作,(printf和scanf为格式化I/O函数):
输入函数:
#include <stdio.h>
int getc(FILE* fp);
int fgetc(FILE* fp);
int getchar(void);
函数getchar等同于getc(stdin)。前两个函数的区别是getc可被实现为宏,而fgetc则不能实现为宏。
这3个函数在出错或到达文件尾端时,都返回同样的值,为了区分不同情况,必须调用ferror或feof。
int ferror(FILE *fp);
int feof(FILE *fp);
void clearerr(FILE *fp);
在大多数实现中,每个流对象在FILE中维护两个标志:
调用clearerr可以清除这两个标志。
从流中读取数据后,可以调用ungetc将字符再压送回流中:
int ungetc(int c, FILE *fp);
当正在读一个输入流,并进行某种形式的分字或分记号操作时,会经常用到回送字符操作。有时需要先看一看下一个字符,以决定如何处理当前字符。然后就需要方便地将刚查看的字符送回,以便下一次调用getc时返回该字符。ungetc压送回字符时,并没有将它们写入到文件或设备,只是将它们写回到流的缓冲区。
输出函数:
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
与输入函数一样,putchar(c) 等同于putc(c, stdout),putc可被实现为宏,而fputc则不能实现为宏。
7.每次一行I/O
#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
fgets和gets:
fputs和puts:
8.二进制I/O
二进制I/O一次读写若干个完整的结构,比起fputs和fgets,有些场景更加适合。
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);
这些函数有两个常见的用法:
float data[10];
if(fwrite(&data[2], sizeof(float), 4, fp) != 4)
err_sys("fwrite error");
其中,指定size为每个数组元素的长度,nobj为欲写的元素数。
struct{
short count;
long total;
char name[NAMESIZE];
}item;
if(fwrite(&item, sizeof(item), 1, fp) != 1)
err_sys("fwriteerror");
其中,指定size为结构的长度,nobj为1(要写的对象数)。
二进制I/O的基本问题是:它只能用于读在同一系统上已写的数据。原因是:
9.定位流
有3种方法定位标准I/O流。
10.格式化I/O
格式化输出由5个printf函数来处理:
#include <stdio.h>
int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fp, const char *restrict format, ...);
int dprintf(int fd, 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, ...);
格式说明控制其余参数如何编写和显示,每个参数按照转换说明编写,转换说明以%开始,除转换说明外的其他字符按原样输出。一个转换说明有4个可选部分:
%[flags][fldwidth][precision][lenmodifier]convtype
convtype不是可选的,它控制如何解释参数。
下列5种printf族的变体类似于上面5种,但是可变参数变成了arg:
#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 vdprintf(int fd, 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);
格式化输入处理的函数有3个:
#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, ...);
scanf族用于分析输入字符串,并将字符序列转换成指定类型的变量。在格式之后的各参数包含了变量的地址,用转换结果对这些变量赋值。
格式说明控制如何转换参数,以便对它们赋值。转换说明以%开始。除转换说明和空白字符外,格式字符串中的其他字符必须与输入匹配。若有一个字符不匹配,则停止后续处理,不再读输入的其余部分。一个转换说明有3个可选部分:
%[*][fldwidth][m][lenmodifier]convtype
与printf族相同,scanf族也使用stdarg.h说明的可变长度参数表:
#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);
11.实现细节
标准I/O库最终都要调用文件I/O的系统调用。每个标准I/O流都有一个与其相关联的文件描述符,可以对一个流调用fileno函数获得其描述符。fileno不是ISO C标准部分,而是POSIX.1支持的扩展。
#include <stdio.h>
int fileno(FILE *fp);
12.临时文件
标准I/O库提供两个函数以帮助创建临时文件:
#include <stdio.h>
char *tmpnam(char *ptr);
FILE *tmpfile(void);
tmpnam产生一个与现在文件名不同的一个有效路径名字符串。每次调用它时,它都产生一个不同的路径名,最多调用次数是TMP_MAX。
tmpfile创建一个临时二进制文件(类型wb+),在关闭该文件或程序结束时将自动删除这种文件。注意,UNIX对二进制文件不作特殊区分。tmpfile函数经常使用的标准UNIX技术是先调用tmpnam产生一个唯一的路径名,然后立即unlink它。
Single UNIX Specification为处理临时文件定义另外两个函数:
#include <stdlib.h>
char *mkdtemp(char *template);
int mkstemp(char *template);
13.内存流
内存流通过FILE指针进行访问,但是并没有底层文件。所有的I/O都是通过在缓冲区与主存之间传送字节完成。
有3个函数可以用于创建内存流:
#include <stdio.h>
FILE *fmemopen(void *restrict buf, size_t size, const char *restrict type);
FILE *open_memstream(char **bufp, size_t *sizep);
#include <wchar.h>
FILE *open_wmemstream(wchar **bufp, size_t *sizep);