C-File I/O
文件的I/O操作是每一门语言的重点,因此这里我先来介绍一下如何用C语言去进行文件的I/O操作。
文件和流
就C语言程序而言,所有的I/O操作只是简单地
从程序移进或移出字节
的事情。因此,这种字节流便被称为流(stream)。
程序只需要关心创建正确的输出字节数据,以及正确地解释从输入读取的字节数据
。特定I/O设备的细节对程序员是隐藏的。绝大多数流是完全缓冲的(fully buffered),这意味着“读取”和“写入”实际上是从一块被称为缓冲区(buffer)的内存区域来回复制数据。从内存中来回复制数据是非常快速的。用于
输出流的缓冲区只有当它写满时才会被刷新(flush,物理写入)到设备或文件中
。一次性把写满的缓冲区写入和逐片把程序产生的输出分别写入相比效率更高。输入缓冲区也是类似的原理。
流分为两种类型,分别是
文本流和二进制流
。
打开流和关闭流
fopen函数打开一个特定的文件,并把一个流和这个文件相关联。它的原型如下所示:
读取 | 写入 | 添加 | |
文本 | “r” | "w" | "a" |
二进制 | “rb” | “wb” | “ab” |
如果fopen函数执行成功,它将返回一个指向FILE结构的指针,该结构代表这个新创建的流。如果函数执行失败,它将返回一个NULL指针,error会提示问题的性质。
流是用函数fclose关闭的,它的原型如下:
fclose函数在文件关闭之前刷新缓冲区
。如果它执行成功,fclose返回零值,否则返回EOF。
对于输出流,
由于fopen和fclose打开和关闭的都是FILE结构体指针,而在stdio.h头文件中,包含了对文件结构体FILE的描述。这里介绍一下FILE结构体定义:
- struct _iobuf {
- char *_ptr; // 下一个要被读取的字符的地址
- int _cnr; // 剩余的字符
- char *base; // 缓冲区基地址
- int _flag; // 读写文件标志位
- int _file; // 文件号
- int _charbuf; // 检查缓冲区的状况
- int _bufsiz; // 文件的大小
- char *_tmpfname; // 临时文件名
- };
- typedef struct _iobuf FILE;
需要操作的流作为参数传递给getc和fgetc,但是getchar始终是从标准输入读取。每个函数从流中读取下一个字符,并把它作为函数的返回值返回。如果流中不存在更多的字符,函数就返回常量值EOF(-1)。
为了把单个字符写入到流中,可以使用putchar函数家族。它的原型如下:
行I/O
行I/O其实可以用两种方式执行——未格式化的或者格式化的。这两种形式都用于操纵字符串。区别在于
未格式化的I/O只是通过fgets和fputs简单读取或写入字符串
,而
格式化的I/O则执行数字和其他变量的内部或外部表示形式之间的转换
。由于日常工作中操作的一般都是格式化I/O,因此这里不讲fgets和fputs这种非格式化I/O操作了。(当然,还有一个重要的原因,
fgets无法判断缓冲区长度
,容易导致溢出等情况)
scanf家族
scanf函数家族的原型如下所示。每个原型中的省略号表示一个可变长度的指针列表。从输入转换而来的值逐个存储到这些指针参数所指向的内存位置。
- int fscanf(FILE* stream, const char* format, ...);
- int scanf(const char* format, ...);
- int sscanf(const char* string, const char* format, ...);
这些函数都
从输入源读取字符并根据format字符串给出的格式化代码对它们进行转换
。当格式化字符串到达末尾或者读取的输入不再匹配格式字符串所指定的类型时,输入就停止。在任何一种情况下,被转换的输入值的数目作为函数的返回值返回。如果在任何输入值被转换之前文件就已经到达尾部,函数就返回常量值EOF。
printf家族
printf函数家族用于创建格式化的输出。它们的函数原型如下:
二进制I/O
把数据写到文件里效率最高的方法是用二进制形式写入,而且Android系统里也有很有用二进制文件通过位来存储数据的应用场景。介绍一下操纵二进制I/O的函数原型。
fread函数用于读取二进制数据,fwrite函数用于写入二进制数据。它们的原型如下所示:
刷新和定位函数
在处理流时,另外还有一些函数也较为有用。首先,
是fflush,它迫使一个输出流的缓冲区内的数据进行物理写入
,不管它是不是已经写满。它的原型如下所示:
在正常的情况下,数据以线性的方式写入,这意味着后面写入的数据在文件中的位置是在以前所有写入数据的后面。C同时支持随机访问I/O,也就是以任意顺序访问文件的不同位置。随机访问是通过在读取或写入前先定位到文件中需要的位置来实现的。一般使用fseek函数来实现,函数原型如下:
如果from是 | 你将定位到 |
SEEK_SET | 从流的起始位置起offset个字符,offset必须是一个非负值 |
SEEK_CUR | 从流的当前位置起offset个字符,offset可正可负 |
SEEK_END | 从流的尾部位置起offset个字符,offset值可正可负。为正数时,它将定位到文件尾的后面 |