椋鸟C语言笔记#34

文件的随机读写、文件读取结束或失败的判定、缓冲区

Featured image

萌新的学习笔记,写错了恳请斧正。

文件的随机读写

文件的随机读写是指我们可以控制文件位置指示器(光标)的位置,以完成复杂的读写操作

fseek
#include <stdio.h>
int fseek( FILE* stream, long offset, int origin );

fseek 函数用于移动文件位置指示器(光标)的位置,移动成功则返回 0,发生错误则光标位置不变、返回非 0 整数并设置流结构体上的错误指示器

其中,origin 是基准位置,可以取值有:SEEK_SET(文件头)、SEEK_CUR(当前光标位置)、SEEK_END(文件尾),一般这三个值分别是 0、1、2(最好不要这样写)。而 offset 为偏移的字节数,代表以 origin 为基准偏移特定字符,比方说 origin 为 SEEK_CUR 且 offset 为 - 1 时,就将光标左移一个字节。

使用实例

文件被写入 “This is a sample.”

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
	FILE* pf = fopen("file.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return EXIT_FAILURE;
	}
 
	fputs("This is an apple.", pf);
	fseek(pf, 9, SEEK_SET);
	fputs(" sam", pf);
 
	fclose(pf);
	pf = NULL;
	return EXIT_SUCCESS;
}
ftell
#include <stdio.h>
long ftell( FILE* stream );

ftell 用于返回此时文件位置指示器相对文件头的偏移量

使用实例
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
	FILE* pf = fopen("file.txt", "w");
	if (!pf)
	{
		perror("fopen");
		return EXIT_FAILURE;
	}
 
	fputs("1145141919810", pf);
	printf("%d\n", ftell(pf));
	fseek(pf, 0, SEEK_SET);
	printf("%d\n", ftell(pf));
	fseek(pf, 7, SEEK_CUR);
	printf("%d\n", ftell(pf));
 
	return EXIT_SUCCESS;
}
rewind
#include <stdio.h>
void rewind( FILE* stream );

rewind 函数用于将文件位置指示器返回到文件起始位置

效果等于 offset 为 0 且 origin 为 SEEK_SET 的 fseek 函数

文件读取结束或失败的判定

feof
#include <stdio.h>
int feof( FILE *stream );

feof 检查流结构体上的文件尾指示器,如果指示器被设置则返回非 0 值,否则返回 0

feof 函数的作用是:当文件读取结束,判断结束的原因是否是 “遇到文件尾”

请不要在读取过程中使用 feof 的返回值来判断文件是否读到结束

ferror
#include <stdio.h>
int ferror( FILE* stream );

ferror 检查流结构体上的错误指示器,如果指示器被设置则返回非 0 值,否则返回 0

典型使用方式

先判断读取是否结束,如果读取结束则通过 feof 和 ferror 判断是遇到文件尾结束还是出错

  1. 判断文本文件是否结束:判断返回值是否为 EOF(fgetc)或者 NULL(fgets)
  2. 判断二进制文件读取结束:判断 fread 返回值是否小于实际要读的个数

比方说我们处理文本文件可以:

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (!fp)
    {
        perror("File opening failed");
        return EXIT_FAILURE;
    }
 
    int c; // 注意:int,非char,要求处理EOF
    while ((c = fgetc(fp)) != EOF)  // 标准C I/O读取文件循环
        putchar(c);
 
    if (ferror(fp))
        puts("I/O error when reading.");
    else if (feof(fp))
        puts("End of file reached successfully.");
 
    fclose(fp);
    fp = NULL;
    return EXIT_SUCCESS;
}

处理二进制文件可以:

#include <stdio.h>
#include <stdlib.h>
 
enum { SIZE = 5 };
double a[SIZE] = { 1.,2.,3.,4.,5. };
double b[SIZE];
 
int main()
{
    FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式
    if (!fp)
    {
        perror("fopen-wb");
        return EXIT_FAILURE;
    }
 
 
    fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组
    fclose(fp);
 
    fp = fopen("test.bin", "rb");
    if (!fp)
    {
        perror("fopen-rb");
        return EXIT_FAILURE;
    }
 
    size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组
    if (ret_code == SIZE)
    {
        puts("Array read successfully, contents: ");
        for (int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
        putchar('\n');
    }
    else
    {
        if (feof(fp))
            printf("Error reading test.bin: unexpected end of file\n");
        else if (ferror(fp))
            perror("Error reading test.bin");
    } // error handling
 
    fclose(fp);
    fp = NULL;
    return EXIT_SUCCESS;
}

文件缓冲区

ANSIC 标准采用 “文件缓冲系统” 来处理数据文件。

也就是说,系统会在内存中为每个打开的文件流创建一个 “文件缓冲区”。内存中运行的程序如果想对外存中的文件进行 I/O(输入输出)操作,传递的信息就需要通过输入缓冲区和输出缓冲区。

在不刷新缓冲区的情况下,信息只有在填满整个输入 / 输出缓冲区后,整个缓冲区的信息才会整个打包发送给程序 / 文件,否则信息将滞留在缓冲区直到被刷新。

刷新缓冲区是指将已经写到输入缓冲区的信息冲入、已经写到输出缓冲区的信息冲出;将尚未写入缓冲区的信息舍弃

由于缓冲区的存在,我们在更新模式(存在标签 “+”)下打开文件时:

  • 若中间没有 fflush 函数或文件定位函数,则输出后不应有输入
  • 若中间没有文件定位函数且输入操作没有遇到文件尾,则输入后不应有输出
fflush
#include <stdio.h>
int fflush( FILE* stream );

fflush 函数用于刷新缓冲区,成功返回 0,否则返回 EOF 并设置流结构体的错误指示器

如果输入空指针,则刷新所有缓冲区