东阳的学习笔记
一、二进制 I/O
如果进行二进制 I/O 操作,那么我们更愿意一次读或写一个完整的结构。
- 如果使用 getc 或 putc 读写一个结构,那么必须循环通过整个结构,每次循环处理一个字节,一次读或者写一个字节,
这太麻烦了
- 如果使用 fgets 和 fputs,那么因为 fputs 在遇到 null 字节时停止,
而在结构中可能含有 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.1 函数用法
- 读或写一个二进制数组:
float data[10];
if (write(&data[2], sizeof(float), 4, fp) != 4)
err_sys(“fwrite error");
- 读或写一个结构
struct {
short count;
long total;
char name[NAMESIZE];
} item;
if (fwrite(&item, sizeof(item), 1, fp) != 1)
err_sys(“fwrite error");
fread 和 fwrite 返回读 或 写的对象数。
1.2 使用中的基本问题
它只能用于读在同一个系统上已写的数据。常常有这种情况,在一个系统上写的数据,要在另一个系统上进行处理,在这种情况下,这两个函数可能就不能正常的工作。原因是:
- 在一个结构中,同一个成员的偏移量可能随编译程序系统的不同而不同
- 用来储存多字节整数和浮点值的二进制格式在不同的系统间也可能不同
二、定位流
有三种方法可以定位标准I/O流。
- ftell 和 fssek 函数。这两个函数自 v7 就存在了,但是它们都假定文件的位置可以存放在一个长整型中。
- ftello 和 fseekpos 函数,使文件偏移量可以不必一定使用长整型,它们使用 off_t 数据类型代替了长整型。
- fgetpos 和 fsetpos 函数,这两个函数是由 IOS C引入的。它们使用一个抽象数据类型 fpos_t 记录文件的位置。这种数据类型可以根据需要定义一个足够大数,用以记录文件位置。
需要移植到非 UNIX 系统上运行的应用程序应当使用 fgetpos 和 fsetpos
#include <stdio.h>
long ftell(FILE* fp); // Returns:current file position indicator if OK, -1L on error
int fseek(FILE* fp, long offset, int whence); // Returns:0 if OK , -1 on error
void rewind(FILE* fp);
其中rewind 函数可以将一个流设置到文件的起始位置。
- 二进制文件的偏移量是从文件开始到当前位置的字节数;
- ftell函数返回当前文件的偏移位置;
- fseek函数用来定位文件到指定偏移位置;
- fseek函数的参数whence,用来设置计算偏移量的方法:SEEK_SET表示从文件开头开始计算,
- SEEK_CUR表示从文件当前偏移位置开始计算,SEEK_END表示从文件结尾开始计算。
- 对于一些非Unix操作系统,存储文本文件的存储格式会有所不同,当前文件偏移量无法通过字节数来表示,这种情况下,参数whence需要设置为SEEK_SET,并且offset只有两个值可以使用:0,表示倒回文本开头;另一个可用值为函数ftell的返回值。
#include <stdio.h>
off_t ftello(FILE* fp); // Returns: current file position indicator if OK, (off_t) -1 on error
int fseeko(FILE* fp, off_t offset, int whence); /// Returns: 0 if OK, -1 on error
除了偏移量的类型是 off_t 而非 long 以外,ftello 函数与 ftell 相同,fseeko 函数与 fseek 相同
#include <stdio.h>
int fgetpos(FILE* restrict fp, fpos_t *restrict pos);
int fsetpos(FILE* fp, const fpos_t pos);
ffetpos 将文件位置指示器的当前值存入由 pos 指向的对象中。在以后调用的 fsetpos 时,可以使用此值将流重新定位至该位置。
三、格式化 IO
#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, ...);
函数细节:
- format参数后面接得参数,包含存放读入字符串的变量地址。
- 其他:略
四、实现细节
#include <stdio.h>
int fileno(FILE* fp); // 返回与该流相关联的文件描述符
如果我们需要调用dup和fcntl,则需要调用该函数。
五、临时文件(Temporary Files)
标准IO库提供了两个函数用于创建临时文件。
#include <stdio.h>
char* tempnam(char *ptr);
FILE* tmpfile(void);
函数细节:
- 函数tmpnam生成一个字符串,该字符串为一个合法的路径名,并且不和任何已存在的文件重复。
- 函数tmpnam每次调用都生成不同的字符串,知道TMP_MAX次数。
- 如果函数tempnam的参数ptr为NULL,则生成的路径字符串存在内存静态区,函数返回值为指向该路径字符串的指针。如果随后再次使用null参数调用tempnam,会覆盖之前生成的字符串。
- 如果函数tempnam的参数ptr不是NULL,那么生成的路径字符串存在ptr指向的数组内,所以需要保证ptr指向的数组的长度至少为L_tmpnam。
- 函数tmpfile函数创建一个临时二进制文件(type wb+),程序终止或者该文件被关闭,则该文件自动被删除。对于UNIX操作系统而言,生成一个二进制文件并没有什么影响,因为内核并不区分文本文件还是二进制文件。
\#include "apue.h"
int main(void)
{
char name[L_tmpnam], line[MAXLINE];
FILE *fp;
printf("%s\n", tmpnam(NULL)); /* first temp name */
tmpnam(name); /* second temp name */
printf("%s\n", name);
if ((fp = tmpfile()) == NULL) /* create temp file */
err_sys("tmpfile error");
fputs("one line of output\n", fp); /* write to temp file */
rewind(fp); /* then read it back */
if (fgets(line, sizeof(line), fp) == NULL)
err_sys("fgets error");
fputs(line, stdout); /* print the line we wrote */
exit(0);
}
在系统The Single UNIX Specification定义了另外两个函数处理临时文件:
char* mkdtemp(char* template); // Returns: pointer to directory name if OK, NULL on error
int mkstemp(char* template); // Returns: file descriptor if OK, -1 on error
函数细节:
- mkdtemp函数创建一个名字唯一的文件夹
- mkstemp函数创建一个名字唯一的常规文件(regular file)
- 命名规则为 template + 六位随机字符
六、内存流
有的标准输入输出流并没有对应打开的硬盘文件,所有操作都是通过在缓冲区和主存之间来回传送字节完成的,这些流被叫做内存流(memory streams)。