上一篇:从0开始学c语言-36-C语言文件(1)文件打开关闭和输入输出_阿秋的阿秋不是阿秋的博客-优快云博客
接续上一篇
5. 文件的随机读写
5.1 fseek
int fseek ( FILE * stream, long int offset, int origin );
函数参数:
stream
Pointer to FILE structure(文件结构指针)
offset
Number of bytes from origin(起始字节大小)
origin
Initial position(最初的位置)
需要注意的是,最初的位置(origin参数)需要是以下三个之一
SEEK_CUR
Current position of file pointer(现在文件指针的位置)
SEEK_END
End of file(文件结尾)
SEEK_SET
Beginning of file(文件开头)
现在我们试着从一个文件中根据文件指针的位置和偏移量来读取一下内容。
在偏移之前文件内容如下。
现在我们写一段代码来偏移着读取,读取出 “I” 和 “you.”。
int main()
{
//打开文件
FILE* pf = fopen("test.dat", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
fseek(pf, 0, SEEK_SET);
int ret = fgetc(pf);
printf("%c", ret);
fseek(pf, 6, SEEK_CUR);
char tmp[20] = { 0 };
fgets(tmp, 5, pf);
printf("%s", tmp);
//关文件
fclose(pf);
pf = NULL;
return 0;
}
特别解读一下,每一句所对应的文件结构指针位置。
要特别明确 fgets函数 设置 读取5个字节 的位置是包括 字符串结束标志 \0字符的,所以实际上指针只走了4个位置。
为了验证这个猜想,我又在上面读文件位置的代码处,接续了一段代码。
fseek(pf, 0, SEEK_CUR);
fgets(tmp, 4, pf);
printf("%s", tmp);
fseek(pf, -2, SEEK_END);
ret = fgetc(pf);
printf("%c", ret);
fgets函数读取内容的时候不会自动换行,所以只读取了换行符,还需要知道的是fseek从文件结尾位置需要向前挪动1个字节的位置才可以读取到最后一个我们可以看见的字符。
于是这两段代码合在一起的运行结果会是这样。
第一次读取:I
第二次读取:you.\0
第三次读取:\n\0
第四次读取:.
如果我们把偏移量改为两个字节。
fseek(pf, 2, SEEK_CUR);
fgets(tmp, 4, pf);
printf("%s", tmp);
指针就正好换行了。
为了搞清楚为什么需要两个字节才能换行,所以我做了以下尝试。
//读文件
fseek(pf, 10, SEEK_SET);
char tmp[20] = { 0 };
fgets(tmp, 20, pf); // .\n\0
printf("%s", tmp);
fseek(pf, 11, SEEK_SET);
fgets(tmp, 20, pf); // \n\0
printf("%s", tmp);
fseek(pf, 12, SEEK_SET);
fgets(tmp, 20, pf); // \n\0
printf("%s", tmp);
注释中是读取结果,也就是说当偏移量是10个字节的时候,指针位置在 I love you. 语句的标点 . 位置,而在偏移量是11和12的时候,都会读取\n,当偏移量是13的时候,指针才会换行。也就是说,想要换行的话,在指针指向\n字符的时候需要偏移两个字节才可以换行。
5.2 ftell
long int ftell ( FILE * stream );
这个函数适合配合其他函数一起使用,以便于更好的理解指针的位置,以及每个函数读取后指针的位置。
我用这段代码去探索了文件结尾偏移量的多少。
int main()
{
//打开文件
FILE* pf = fopen("test.dat", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
long int sz = 0;
fseek(pf, 0, SEEK_END);
sz = ftell(pf);
printf("%d\n", sz);
printf("%c\n",fgetc(pf));
fseek(pf, -1, SEEK_END);
sz = ftell(pf);
printf("%d\n", sz);
printf("%c\n",fgetc(pf));
fseek(pf, -2, SEEK_END);
sz = ftell(pf);
printf("%d\n", sz);
printf("%c\n", fgetc(pf));
//关文件
fclose(pf);
pf = NULL;
return 0;
}
事实证明,文件结尾偏移量会比我们能看到的字符以及换行符要多一个字节的位置。(这是指有换行的情况下,如果并没有换行,那么有几个字符文件结尾的位置就是几个字节)
至于其他函数,大家可以自行探索。注意一下fgets函数就行,这函数的移动位置比你规定的要少一个字节,因为包含了\0的位置。
5.3 rewind
void rewind ( FILE * stream );
这个很简单,不多介绍。直接使用。
int main()
{
FILE* pf = fopen("test.dat", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fseek(pf, 4, SEEK_SET);
printf("%c", fgetc(pf));
rewind(pf);
printf("%c", fgetc(pf));
return 0;
}
6. 文本文件和二进制文件
int main()
{
int a = 10000;
FILE* pf = fopen("test.txt", "wb");
fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
fclose(pf);
pf = NULL;
return 0;
}
运行完代码后,把文件拖到我们的编译器中,右击选择打开方式,以二进制编辑器打开。

要知道,我们以二进制存储10000数字,而这个数字转换为十六进制就是00002710,在编译器中打开后如图。
因为vs2019是小端储存,所以把低位的数字存到了低地址。会呈现为10270000的样子。
现在我们以ASCII码值进行数字10000的储存,把二进制序列进行十六进制转换如图
在打开文件后,正好能够对应起来。
说起来,你有没有发现这次打开后好像刚好能对应起来,但是更重要的是,这东西占了五个字节。所以在储存数字的时候用二进制的方式存储更能节省空间。
7. 文件读取结束的判定
7.1 被错误使用的feof
1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )例如:fgetc 判断是否为 EOF .fgets 判断返回值是否为 NULL .2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。例如:fread 判断返回值是否小于实际要读的个数。
现在我们利用这个函数feof来判断把一个文件的信息输入到另外一个文件里,当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
feof函数在文件没有读取结束的时候会返回0 ,如果读取结束会返回一个非零值。
所以我们写出来了这段代码。
int main()
{
FILE* pfread = fopen("test.dat", "r");
if (pfread == NULL)
{
perror("fopen");
return 1;
}
FILE* pfwrite = fopen("test2.dat", "w");
if (pfwrite == NULL)
{
fclose(pfread);
pfread = NULL;
return 1;
}
//文件打开成功
//读写文件
int ch = 0;
while ((ch = fetc(pfread)) != EOF) //判断是否文件结束
{
//写文件
fputc(ch, pfwrite);
}
if (feof(pfread))
{
printf("遇到文件结束标志,文件正常结束\n");
}
else if (ferror(pfread))
{
printf("文件读取失败结束\n");
}
//关闭文件
fclose(pfread);
pfread = NULL;
fclose(pfwrite);
pfwrite = NULL;
}
运行后,可以把文件1的信息拷贝到文件2里。
8. 文件缓冲区
ANSIC 标准采用 “ 缓冲文件系统 ” 处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“ 文件缓冲区 ” 。从内存向磁盘 输出数据 会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机 读入数据 ,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
#include <stdio.h>
#include <windows.h>
int main()
{
FILE* pf = fopen("test.dat", "w");
//代码放在输出缓冲区
fputs("aaaaa", pf);
printf("睡眠10秒-已经写数据了,打开test.dat文件,发现文件没有内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);
//刷新缓冲区时,才能把缓冲区的数据写到文件(磁盘)
//注:fflush在高版本的VS上不能使用
printf("再睡眠10秒-再次打开test.dat文件,文件有内容了\n");
Sleep(10000);
fclose(pf);
//注:fcolse在关闭文件的时候也会刷新缓冲区
pf = NULL;
return 0;
}
可以看到,确实是有缓冲区的存在,数据并不是直接输入到(文件)磁盘中去。