第三十二章 文件操作(2)

发布于:2022-10-12 ⋅ 阅读:(220) ⋅ 点赞:(0)

C语言学习之路

第一章 初识C语言
第二章 变量
第三章 常量
第四章 字符串与转义字符
第五章 数组
第六章 操作符
第七章 指针
第八章 结构体
第九章 控制语句之条件语句
第十章 控制语句之循环语句
第十一章 控制语句之转向语句
第十二章 函数基础
第十三章 函数进阶(一)(嵌套使用与链式访问)
第十四章 函数进阶(二)(函数递归)
第十五章 数组进阶
第十六章 操作符(详解及注意事项)
第十七章 指针进阶(1)
第十八章 指针进阶(2)
第十九章 指针进阶(3)
第二十章 指针进阶(4)
第二十一章 数据的存储(秒懂原、反、补码)
第二十二章 指针和数组笔试题详解(1)
第二十三章 指针和数组笔试题详解(2)
第二十四章 字符串函数(1)
第二十五章 字符串函数(2)
第二十六章 内存函数
第二十七章 自定义数据类型之结构体进阶(1)
第二十八章 自定义数据类型之结构体进阶(2)
第二十九章 动态内存管理(1)
第三十章 动态内存管理(2)
第三十一章 文件操作(1)
第三十二章 文件操作(2)



前言

前面一章中我们所了解到的是文本的顺序读写,即从头开始向下读写。但是当我们想要直接访问一个文本中的某个位置的时候,我们又该如何做呢?


一、随机读取文件

我们发现刚才的读取方式都是按顺序从头到尾读取的,倘若我们只是想读取某个位置的字符,我们又该使用什么函数呢?

1、fseek()

函数用途

在这里插入图片描述
我们先来看这个函数的形参列表:第一个参数是一个文件指针,传入的就是我们操作的目标文件,第二个参数是一个指针偏移量,那么这个偏移量是相对哪个位置而言的呢?这时候就需要第三个参数:指针位置。
指针位置共有三种情况:
在这里插入图片描述
SEEK_SET——文章的开头
SEEK_CUR——指针当前所在位置
SEEK_END——文章的结尾

然后我们来看一下这个函数的返回值:
如果成功,该函数返回零,否则,它将返回非零值。

函数使用

#include<stdio.h>
int main()
{
	FILE*pfw=fopen("test.txt","w");
	if (pfw == NULL)
	{
		perror("fopen");
	}
	for (int i = 0; i < 26; i++)
	{
		fprintf(pfw,"%c",'a'+i);
	}
	fclose(pfw);
	pfw = NULL;

	FILE* pfr = fopen("test.txt","r");
	if (pfr == NULL)
	{
		perror("fopen");
	}
	fseek(pfr,2,SEEK_CUR);
	char c=fgetc(pfr);
	printf("%c",c);
	printf("%c", fgetc(pfr));

	fclose(pfr);
	pfr = NULL;

	return 0;
}

我们来分析一下上面的代码,首先我们创建了一个文件,然后通过fprintf()函数向里面输入了26个英文字母。
输入完成后,我们以只读的方式再次打开这个文件,然后利用pfr简介访问这个文件。pfr会指向这个文档中的第一个元素。此时我们利用fseek函数,fseek(pfr,2,SEEK_CUR);这行代码的意思就是将pfr指针从当前位置向后移动两个元素,即从a的位置移动到了c的位置。此时我们再次使用fgetc函数,这个函数的作用就是读取pfr所指向的字符,读取的同时,将pfr指针向后移动一个单位,然后返回这个字符的ascii码值,此时我们利用一个字符型变量接收这个ASCII值,然后利用printf打印到屏幕上。
我们如何证明fputc函数在读取的同时,又向后移动了一个位置呢?我们只需要再次利用fputc函数读取一个字符,然后打印到屏幕上。如果还是c就说明没有移动,如果是d就说明移动了。
我们运行上面的代码:
在这里插入图片描述
发现最终的结果如我们所料。

2、ftell()

函数用途:

在这里插入图片描述
当我们多次的调整文件指针的位置的时候,我们最终可能就忘记了文件指针到底指向哪里了。此时这个函数就可以发挥作用了,这个函数就是来反馈文件指针相对于起始位置的偏移量。
我们还以刚才的代码为例,仅仅是在最后加上一个ftell函数来验证一下我们当前的指针位置。

#include<stdio.h>
int main()
{
	FILE*pfw=fopen("test.txt","w");
	if (pfw == NULL)
	{
		perror("fopen");
	}
	for (int i = 0; i < 26; i++)
	{
		fprintf(pfw,"%c",'a'+i);
	}
	fclose(pfw);
	pfw = NULL;

	FILE* pfr = fopen("test.txt","r");
	if (pfr == NULL)
	{
		perror("fopen");
	}
	fseek(pfr,2,SEEK_CUR);
	char c=fgetc(pfr);
	printf("%c",c);
	printf("%c", fgetc(pfr));

	int a = ftell(pfr);
	printf("\n当前指针相对文件头的偏移量是:%d",a);

	fclose(pfr);
	pfr = NULL;

	return 0;
}

在这里插入图片描述
我们打印出来d的同时,fputc函数会将指针再次向后一个单位,所以最终指向的是e,而e恰好相对于a偏移了4次,即可验证我们的结果。

3、rewind()

函数用途

在这里插入图片描述
这个函数的作用就是重置文件指针的位置,将其再次指向文件中的第一个元素。

函数使用

#include<stdio.h>
int main()
{
	FILE*pfw=fopen("test.txt","w");
	if (pfw == NULL)
	{
		perror("fopen");
	}
	for (int i = 0; i < 26; i++)
	{
		fprintf(pfw,"%c",'a'+i);
	}
	fclose(pfw);
	pfw = NULL;

	FILE* pfr = fopen("test.txt","r");
	if (pfr == NULL)
	{
		perror("fopen");
	}
	fseek(pfr,2,SEEK_CUR);
	char c=fgetc(pfr);
	printf("%c",c);
	printf("%c", fgetc(pfr));

	int a = ftell(pfr);
	printf("\n当前指针相对文件头的偏移量是:%d",a);
	rewind(pfr);
	int b = ftell(pfr);
	printf("\n当前指针相对文件头的偏移量是:%d", b);
	fclose(pfr);
	pfr = NULL;

	return 0;
}

我们还是在刚才代码的基础上,继续添加上这行代码,我们运行发现最终的结果乳如下:
在这里插入图片描述

二、文本文件和二进制文件

1、内存的存储方式

一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

2、二进制文件和文本文件

根据数据存储的模式不同, 最终存储数据后的数据文件可以分为两种,一种是文本文件,一种是二进制文件。数据在内存中以二进制的形式存储,如果不加转化,直接存储到文件中,那么这个文件就叫做二进制文件。
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节。
在这里插入图片描述

如果我们将内存中的二进制文件以ASCII字符的形式存储,那么这个文件就叫做文本文件。
之前我们在输出数据的时候,用到了二进制的方式输出,我们发现最终文档中存储的并不是二进制,而是一堆乱码。出现乱码的原因是我们的txt格式的文件无法识别二进制,所以显示出的是乱码。那么我们如何看到乱码背后的二进制呢?
我们先创建一个文本,将1000的二进制形式传入文本中:

#include<stdio.h>
int main()
{
	FILE*pf=fopen("Test.txt","wb");
	if (pf == NULL)
	{
		perror("fopen");
	}
	int a = 10000;
	fwrite(&a,sizeof(int),1,pf);
	
	fclose(pf);
	pf = NULL;

	return 0;
}

在这里插入图片描述
此时文本中的是一个乱码。
我们用如下的步骤就能够看到背后存储的二进制。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上述操作结束后,我们就能看到下面的画面:
在这里插入图片描述
在这里插入图片描述

三、 文件读取结束的判定

1、文件结束的判断方式

(1)文本文件

文本文件读取是否结束,需要判断的是函数的返回值是否为EOF或者NULL
例如:

  • fgetc判断是否为EOF
  • fgets 判断是否为NULL

(2)二进制文件

二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。

  • fread判断返回值是否小于实际要读的个数。

这里我们要格外地注意一个库函数,叫做feof。这个函数不是用来判断文件是否读到文件尾的,而是判断是以何种方式到达文件尾的。也就是说,feof判断的是这个文件是以出现错误而结尾还是以读到文件尾部而结尾的

提到这里我们不妨介绍两个函数feof()ferror()。如果函数读到了文件尾,那么feof会返回一个非零值,如果函数因为出错而结束了文件的阅读,那么就返回0。
ferror()与之相反,如果在读取过程中出现错误,那么 这个函数返回非零值,如果在阅读过程中没有错误就返回0。
在这里插入图片描述
在这里插入图片描述

(3)示例

文本文件示例

#include<stdio.h>
int main()
{
	//创建一个文件并写入26个字母
	FILE*pf=fopen("test.txt","w");
	if (pf == NULL)
	{
		perror("fopen");
	}
	for (int i = 0; i < 26; i++)
	{
		fputc('a'+i,pf);
	}
	fclose(pf);
	pf = NULL;
	//读取这个文件中的26个字母
	FILE* pfr = fopen("test.txt","r");
	if (pfr == NULL)
	{
		perror("fopen");
	}
	int a = 0;
	//读取字母,判断是否到文件尾
	while ((a = fgetc(pfr)) != EOF)
	{
		printf("%c",a);
	}
	printf("\n");
	//判断文件读取结束的原因:出错或者读完
	if (ferror(pfr))
	{
		printf("I/O error when reading\n");
	}
	if (feof(pfr))
	{
		printf("End of file reached successfully\n");
	}
	//关闭文件
	fclose(pfr);
	pfr = NULL;

	return 0;
}

二进制文件示例

#include<stdio.h>
#define MAX_NUM 5
int main()
{
    double arr[MAX_NUM] = { 1.,2.,3.,4.,5. };
    FILE*pfw = fopen("test.txt","wb");
    if (pfw == NULL)
    {
        perror("fopen");
    }
    fwrite(arr,sizeof(double),MAX_NUM,pfw);
    fclose(pfw);

    double emp[MAX_NUM];
    FILE* pfr = fopen("test.txt","rb");
    if (pfr == NULL)
    {
        perror("fopen");
    }
    size_t read_num = fread(emp,sizeof(double),MAX_NUM,pfr);
    if (read_num == MAX_NUM)
    {
        puts("Array read successfully, contents: ");
        for (int n = 0; n < MAX_NUM; ++n)
        {
            printf("%f ", emp[n]);
            putchar('\n');
        }
    }
    else 
    { // error handling
        if (feof(pfr))
        {
            printf("Error reading test.bin: unexpected end of file\n");
        }   
        else if (ferror(pfr))
        {
            perror("Error reading test.bin");
        }
    }


    return 0;
}

三、文件缓冲区

ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
在这里插入图片描述

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到