打开流

// return: `FILE *` or `NULL`
FILE *fopen(char const *name, char const *mode);

mode的选项: “r”, “w”, “a”, “rb”, “wb”, “ab”, “a+”

使用:

FILE *input;
input = fopen("file", "r");
if(input == NULL){
    perror("can not open file");
    exit(EXIT_FAILURE);
}

以指定文件名和模式重新打开 stream:

// return: `FILE *stream` or `NULL`
FILE *freopen(char const *filename, char const *mode, FILE *stream);

关闭流

// return: `0` or `EOF`
int fclose(FILE *f);

使用:

if(fclose(input) != 0){
    perror("fclose %s", input);
    exit(EXIT_FAILURE);
}

字符I/O

// stream/stdin ==> character
// return: 函数执行成功会返回读取的字符,失败返回`EOF`
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);

不用char作为返回值类型的原因是为了返回EOF(end of file),EOF作为常量,它的值在任何可能出现的字符范围之外。

// character ==> stream/stdout
// return: `0` or `EOF`
int fputc(int character, FILE *stream);
int putc(int character, FILE *stream);
int putchar(int character);

在写入流之前,character会被剪裁为无符号字符型值。

getc, putc, getchar, putchar是通过#define定义的宏

int ungetc(int character, FILE *stream);

ungetc把一个先前读入的字符返回的流中,这样它可以在以后被重新读入 比如:

/*
** 从一串标准输入中读取的数字转化为整数
*/
#include <stdio.h>
#include <ctype.h>

int read_int(){
    int ch;
    int value = 0;

    while((ch = getchar()) != EOF && isdigit(ch)){
        value *= 10;
        value += ch - '0';
    }

    // 把非数字字符退回的流中,这样它不会丢失
    ungetc(ch, stdin);
    return value;
}

未格式化的行I/O

// stream/stdin ==> buffer
// return: 如果在任何字符读取前就到达了文件末尾,返回`NULL`, 否则返回buffer
char *fgets(char *buffer, int buffer_size, FILE *stream);
char *gets(char *buffer);

fgets读取到一个换行符并存储的buffer之后就不再读取,如果buffer中的字符数达到buffer_size-1时它也停止读取,任何情况下,NUL字节会被添加到buffer所存储数据的末尾。

fgets无法把字符读入到长度小于两个字节的buffer,因为其中一个字节需要为NUL字节保留

getsstdin读取一行输入时,并且不在buffer中存储结尾的换行符。

// buffer ==> stream/stdout
// return: 执行错误返回`EOF`,否则返回非负值
int fputs(char const *buffer, FILE *stream);
int puts(char const *buffer);

传递给fputs的buffer预期以NUL结尾,buffer中可以有换行符(\r\n),即可以一次可以写入多行。

puts写入一个字符串时,它在字符串写入之后向输出在添加一个换行符(即不需显式的\n)。

格式化的行I/O

每个原型中的省略号表示一个可变长度的指针列表,从输入转换而来的值逐个存储到这些指针指向的内存地址。

// stdin/stream/string ==> ...
// return: 在任何情况下,函数返回被转换的输入值的数目;如果在任何输入值被转换之前文件就已到达末尾,函数就返回常量值`EOF`
int scanf(char const *format, ... );
int fscanf(FILE *stream, char const *format, ... );
int sscanf(char const *string, char const *format, ... );

当格式化字符串达到末尾或者读取的输入不再匹配格式字符串指定的类型时,输入就停止。

scanf函数家族中的format字符串参数可能包含的内容:

  • 空白字符: 它们与输入中的零个或多个空白字符匹配
  • 格式代码: 它们指定函数如何解释接下来的输入字符
  • 其他字符: 当任何其他字符出现在格式字符串时,下一个输入字符必须与它匹配;如果匹配,该输入字符随后就被丢弃,如果不匹配,函数就不再读取直接返回
// ... ==> stdout/stream/buffer
//  return: 实际打印或存储的字符数
int printf(char const *format, ... );
int fprintf(FILE *stream, char const *format, ... );
int sprintf(char *buffer, char const *format, ... );

二进制I/O

// stream --> buffer
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
// buffer --> stream
size_t fwrite(void *buffer, size_t size, size_t count, FILE *stream);

return: 函数返回实际读取或写入的元素(而非字节)数目,如果输入过程中遇到了EOF或输出过程中出现错误,这个数字可能比请求的元素数目小。

刷新和定位函数

fflush迫使一个输出流的缓冲区内的数据进行物理写入,不管缓冲区是否已经写满。

int fflush(FILE *stream);

在程序调试时可以用来在特定位置输出调试信息,而不是等待缓冲区写满之后才打印:

printf("something or other");
fflush(stdout);

随机访问I/O需要在读取或写入前先定位到文件中需要访问的位置来实现的。

long ftell(FILE *stream);
int fseek(FILE *stream, long offset, int from);

ftell返回流的当前位置,即下一个读取或写入将要开始的位置距离文件起始位置的偏移量;在二进制流中,这个值就是当前位置距离文件起始位置之间的字节数;在文本流中,它不一定准确的表示当前位置和文本起始位置之间的字符数(因为有些系统将对行末字符进行翻译转换),但ftell的返回值总是可以用于fseek中,作为一个距离文件起始位置的偏移量。

fseek在一个流中定位,这个操作将改变下一个读取或写入操作的位置。

from 你将定位到
SEEK_SET 从流开始位置起offset个字节,offset非负
SEEK_CUR 从流当前位置起offset个字节,offset可正可负
SEEK_END 从流尾部位置起offset个字节,offset可正可负,如果它是正值,它将定位到文件尾的后面

以限制更严格的方式执行文件定位:

void rewind(FILE *stream);
int fgetpos(FILE *stream, fpos_t *position);
int fsetpos(FILE *stream, fpos_t const *position);

rewind将读/写指针设置回流的起始位置,它同时清除流的错误提示标志。

fgetposposition中存储文件的当前位置。

fsetpos把文件位置设置为存储在position的值。

使用一个fgetpos设置的position的唯一安全的用法是把它作为参数传递给后续的fsetpos


待补充

feof(fp) //当文件结束时得到不为0的数
ferror(fp) //当文件出错时得到不为0的数
fileno(fp) //得到文件描述符
BUFSIZE //正常I/O缓冲区的大小,一般为512或1024