标准I/O的机理
第一步:调用fopen()打开文件, fopen()在打开文件的同时创建了一个缓冲区(读写模式下两个)以及一个包含文件和缓冲区的结构, 同时返回一个指向该结构的指针,若把指针赋给一个指针变量fp, 文本模式下打开, 获得一个文本流; 二进制模式则为二进制流。
fopen()创建的结构通常包含一个指定流中当前位置的文件位置指示器, 错误和文件结尾的指示器, 一个指向缓冲区的指针, 一个文件标识符和一个计数器(统计实际拷贝进缓冲区的字节数)。
第二步:文件输出, 调用一个定义在stdio.h中的输入函数。文件中的缓冲数据块被拷贝至缓冲区, 大小一般是512字节或其整数倍。
输入函数发现已读完缓冲区的全部字符后, 会请求把下一个数据块从文件拷贝到该缓冲区。
输出函数同理。
其他I/O函数
int ungetc(int c, FILE *fp)函数
该函数把c指定的字符放回输入流中。
int fflush()函数
int fflush (FILE * fp);
该函数使输出缓冲区所有未写入数据发送到文件fp中, 即刷新缓冲区, 若fp为空指针, 所有输出缓冲区都被刷新。
输入流中该函数的效果未定义。
int setvbuf()函数
int setvbuf(FILE * restrict fp, char * restrict buf, int mode, size_t size);
setvbuf()函数可创建一个供I/O函数替换的缓冲区。
fp识别待处理的流, buf指向待使用的存储区, 若其值不是NULL, 则必须创建一个缓冲区, 如果·NULL是buf的值, 则该函数会自行分配一个缓冲区; size告诉stevbuf()数组的大小·; mode选择如下:
_IOFBF ——————完全缓冲
_IOLBF——————行缓冲
_IONBF——————无缓冲
函数操作成功返回0, 否则返回非0。
二进制I/O: fread() & fwrite()
double 1. / 3.;
fpriintdf(fp, "%f", num);
实际上只是把数值转换为字符串进行存储。如果要保证数值储存前后一致, 则应使用与计算机相同的位。
fwrite(&num, sizeof(double), 1, fp);
fread()和fwrite()用于以二进制形式处理数据
通常使用二进制模式在二进制格式文件中储存二进制数据。
size_t fwrite() 函数
size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);
fwrite()将二进制文件写入文件, ptr为待写入数据块的地址, size为数据块的大小, nmemb为待写入数据块的数量, fp指定待写入文件。
double earnings[10];
fwrite(earnings, sizeof(double), 10, fp);
fwrite()函数返回成功写入项的数量, 出错时返回值会比nmemb小。
size_t fread()函数
size_t fread(void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);
ptr为待读取文件在内存中的地址, fp指定待读取的文件, 该函数用于读取被fwrite()写入文件的数据。
double earnings[10];
fread(earnings, sizeof (double), 10, fp);
该调用把10个double大小的值拷贝至earnings数组中。
该函数返回成功读取项的数量
int feof(FILE * fp) & int ferror(FILE * fp)函数
用于区分是标准输入函数到达文件结尾返回的eof还是读取错误导致的。
feof()在上一次输入调用检测到文件结尾的话, 返回非零, 否则返回0;
ferror()在上一次输入调用检测到读取错误的话, 返回非零, 否则返回0;
程序示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 4096
#define SLEN 81
void append(FILE* source, FILE* dest);
int main(void)
{
extern char* s_gets(char* str, int n);
FILE* fa, * fs;
int files = 0, ch;
char file_app[SLEN], file_src[SLEN];
puts("Enter name of destination file:");
s_gets(file_app, SLEN);
if ((fa = fopen(file_app, "a+")) == NULL)
{
fprintf(stderr, "Can not open %s\n", file_app);
exit(EXIT_FAILURE);
}
if (setvbuf(fa, NULL, _IOFBF, BUFSIZE) != 0)
{
fputs("Can't create output buffer\n", stderr);
exit(EXIT_FAILURE);
}
puts("Enter name of first source file (empty line to quit):");
while (s_gets(file_src, SLEN) && file_src[0] != '\0')
{
if (strcmp(file_src, file_app) == 0)
fputs("Can not append file to itself\n", stderr);
else if ((fs = fopen(file_src, "r")) == NULL)
fprintf(stderr, "Can't open %s\n", file_src);
else
{
if (setvbuf(fs, NULL, _IOFBF, BUFSIZE) != 0)
{
fputs("Can't create input buffer\n", stderr);
continue;
}
append(fs, fa);
if (ferror(fs) != 0)
fprintf(stderr, "Error in reading file %s.\n", file_src);
if (ferror(fa) != 0)
fprintf(stderr, "Error in writing file %s.\n", file_app);
fclose(fs);
files++;
printf("FILE %s appended. \n", file_src);
puts("Next file (empty line to quit):");
}
}
printf("Done appending. %d files appended.\n", files);
rewind(fa);
printf("%s contents:\n", file_app);
while ((ch = getc(fa)) != EOF)
putchar(ch);
puts("Done displaying.");
fclose(fa);
return 0;
}
void append(FILE* source, FILE* dest)
{
size_t bytes;
static char temp[BUFSIZE];
while ((bytes = fread(temp, sizeof(char), BUFSIZE, source)) > 0)
fwrite(temp, sizeof(char), bytes, dest);
}
用二进制I/O进行随机访问
#include <stdio.h>
#include <stdlib.h>
#define ARSIZE 1000
int main(void)
{
double numbers[ARSIZE], value;
const char* file = "number.dat";
int i;
long pos;
FILE* iofile;
for (i = 0; i < ARSIZE; i++)
numbers[i] = 100.0 * i + 1.0 / (i + 1);
if ((iofile = fopen(file, "wb")) == NULL)
{
fprintf(stderr, "Could not open %s for output.\n", file);
exit(EXIT_FAILURE);
}
fwrite(numbers, sizeof(double), ARSIZE, iofile);
fclose(iofile);
if ((iofile = fopen(file, "rb")) == NULL)
{
fprintf(stderr, "Could not open %s for random access.\n", file);
exit(EXIT_FAILURE);
}
printf("Enter an index in the range 0-%d.\n", ARSIZE - 1);
while (scanf("%d", &i) == 1 && i >= 1 && i < ARSIZE)
{
pos = (long)i * sizeof(double);
fseek(iofile, pos, SEEK_SET);
fread(&value, sizeof(double), 1, iofile);
printf("The value there is %f.\n", value);
printf("Next index (out of range to quit):\n");
}
fclose(iofile);
puts("bye!");
return 0;
}
程序以二进制模式创建了一个number.dat的文件, 使用fwrite()把数组内容拷贝至文件,储存在文件中的每个值都与储存在内存中的值完全相同, 无任何精细度损失, 每个值在文件中占64位大小, 每个值的位置很好算出。
程序第二部分用于打开待读取文件, 提示用户输入一个值的索引, 把索引值与double类型值占用的字节数相乘, 即可得出文件上的一个位置, 随后调用fseek()定位到该位置, 用fread()进行读取, fread()从已定位位置拷贝8字节到内存中地址为&value的位置。
关键概念
c把输入看作为字节流, 输入流来源为文件, 输入设备, 或者其他程序的输出。
输出也看作是字节流, 目的地可以是文件, 视频显示等等
c对流的解释方法取决于使用的输入输出函数
访问文件必须创建文件指针