【C语言】文件操作详解

✨✨欢迎大家来到Celia的博客✨✨

🎉🎉创作不易,请点赞关注,多多支持哦🎉🎉

所属专栏:C语言

个人主页Celia's blog~

目录

 引言

一、二进制文件与文本文件

二、文件的打开与关闭

2.1 流和标准流

2.1.1 流

2.1.2 标准流 

2.2 文件指针

2.3 文件的打开与关闭

三、文件的顺序读写

3.1 fgetc

3.2 fputc

3.3 fgets

3.4 fputs

3.5 fprintf

 3.6 fscanf

 3.7 fwrite

3.8 fread

四、文件的随机读写

4.1 fseek

4.2 ftell

4.3 rewind

五、文件读取结束的判定

 5.1 feof

5.2 常见判断文件读取结束的方法

 六、文件缓冲区


 

 引言

  我们知道,C语言程序是储存在电脑的内存中的,如果程序退出,内存会被回收,保存在变量中的数据就会被删除,如果我们想把程序运行时产生的数据永久性的保存(存储到在磁盘上),就会用到有关文件的操作,本篇文章将会介绍C语言中有关文件操作的内容。

一、二进制文件与文本文件

  根据数据的组成形式,分为二进制文件文本文件

  • 数据在内存中以二进制的形式存储,如果不加以转换,直接输出到外部的文件内,就是二进制文件
  • 如果数据要求以ASCII码的形式存储,在存储之前就需要转换,以ASCII字符存储的文件就是文本文件

 以上是数据存入文件的两种方式。那么一个数据中的内容如何区分用哪种方式存储呢?

字符数据一律以ASCII码形式存储,数值型数据既可以用二进制存储,也可以用ASCII码存储。

如:数值为 10 的数据,用二进制存储为 1010(此处省略高位0),用字符存储为 ‘1’,‘0’。(2个字符)。

二、文件的打开与关闭

2.1 流和标准流

2.1.1 流

  我们的程序需要输出数据到外部设备,也需要从外部设备输入数据,不同的外部设备输入输出的操作和处理方式各不相同。为了方便对内外部数据的操作,C语言抽象出“流”的概念,把流想象成一条充满数据的河,外部数据可以通过流输入数据,内部数据也可以通过流输出数据。

  C语言对文件,键盘,屏幕的输出、输入数据的操作都需要通过流来完成。一般情况下,我们想要向流里输入数据或者从流里读取数据,都需要先打开流,然后操作。

2.1.2 标准流 

  为什么我们在编写程序输入输出数据时,没有手动写出打开流的代码呢?

  那是因为在C语言程序在启动的时候,默认打开了3个流:

  • stdin  ——  标准输入流,在大多数情况下从键盘输入,scanf就是向流中输入数据。
  • stdout  ——  标准输出流,大多数情况下输出到显示屏界面,printf就是在流中读取数据。
  • stderr  ——  标准错误流,大多数情况下输出到显示屏界面。

这三个流的类型是 FILE* ,通常称为文件指针,C语言就是通过文件指针对文件进行操作的。 

2.2 文件指针

  当我们使用文件时,每个文件都会在内存中开辟一块相应的文件信息区,所有对文件操作所需要的信息会自动填充到这个文件信息区内,并用一个结构体变量来保存。同时系统将这个结构体重命名为 FILE

以下是VS2013编译环境提供的 stdio.h 头文件中对于文件类型申明:

struct _iobuf {
        char *_ptr;
        int _cnt;
        char *_base;
        int _flag;
        int _file;
        int _charbuf;
        int _bufsiz;
        char *_tmpfname;
};
typedef struct _iobuf FILE;//重命名为FILE

不同的编译环境对于文件类型的申明有可能不同,但大致上是相同的。

每当我们打开一个文件时,系统会自动创建一个 FILE 结构的变量并自动填充相关的信息,一般情况下,对于文件的操作都是通过创建一个 FILE* 的指针来维护这个结构体变量。

创建一个文件指针:

FILE* pf;//创建一个文件指针变量pf

pf 实际上指向的是一个文件的信息区(一个结构体变量),通过文件信息区就可以访问这个文件,也就是说,通过文件指针能够间接的找到与它相关联的文件

2.3 文件的打开与关闭

  我们在读写文件之前需要先打开文件,在使用后关闭文件。

在打开一个文件时,相应的函数会返回一个文件指针(FILE*)指向该文件。

ANSI C 规定使用 fopen 来打开文件,fclose 来关闭文件。

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen(const char*_FileName,const char*_Mode);

	//相应操作


	//关闭文件
	fclose(pf);
	return 0;
}

在这其中,FileName代表文件名,Mode代表文件的打开模式:

FILE* pf = fopen("text.text","w");
//以读的形式打开名为"text.text"的文件

创建的文件会被放在程序所在的目录下。

在VS环境下,可以点击这个按钮来查看文件:

三、文件的顺序读写

3.1 fgetc

fgetc会从文件中读取一个字符,并且返回该字符的 ASCII码值,然后自动前进一位(文件指针指向文件中下一个字符),如果读取失败或者遇到文件结尾,则返回EOF。现在文件内有以下字符:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("text.text","r");//必须以读的方式打开文件!(文件已经存在)

	//相应操作
	char ch = 0;
	while ((ch = fgetc(pf)) != EOF)
		printf("%c ", ch);

	//关闭文件
	fclose(pf);
	return 0;
}

 运行结果:

3.2 fputc

fputc会将一个字符写入流中并保存在文件里,同时返回输入字符的ASCII码值, 并自动前进一位(文件指针指向文件中下一个将要保存的位置),如果出现错误会返回EOF。

初始文件为空:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("text.text","w");//以写的方式打开文件!

	//相应操作
	char ch = 0;
	for (ch = 'a'; ch <= 'z';ch++)
		fputc(ch, pf);

	//关闭文件
	fclose(pf);
	return 0;
}

运行后的文件:

3.3 fgets

fgets会从文件中读取一串字符,并且储存到字符串数组内,如果读取成功,会返回一个字符数组首地址,如果遇到文件末尾,会返回EOF,如果读取出错,会返回NULL。

  • 第一个参数为字符数组的首地址。
  • 第二个参数是一次读取的最大字符数,这里需要注意,fgets会从文件中依次读取字符直到读取(num-1)个字符或遇到文件结尾或遇到换行符(换行符也会被读取)。以先发生者为准。
  • 第三个参数为文件指针。
  • fgets会在读取结束后在字符串的末尾添加一个\0。

现在文件中有以下内容:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("text.text","r");//以读的方式打开文件!

	//相应操作
	char arr[20] = { 0 };
	fgets(arr, 20, pf);
	puts(arr);
	//关闭文件
	fclose(pf);
	return 0;
}

运行结果:

3.4 fputs

fputs会将一个字符串保存到指定的文件中,如果成功,会返回一个非负值,如果失败,会返回EOF。

初始文件为空:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("text.text","w");//以写的方式打开文件

	//相应操作
	char arr[20] = {"CELIA~haha"};
	fputs(arr, pf);
	
	//关闭文件
	fclose(pf);
	return 0;
}

运行后的文件:

3.5 fprintf

fprintf 与 printf 的参数相似,仅仅是多了一个文件指针,fprintf会将数据以格式化的方式存入文件当中,如果成功,将返回存入字符的总数,如果失败,将返回一个负数。

初始文件为空:

#include<stdio.h>
typedef struct str
{
	char flag[10];
	char name[10];
	int age;
}stu;//重命名为stu
int main()
{
	stu person = { "2024","Celia",19 };//创建一个结构体变量
	//打开文件
	FILE* pf = fopen("text.text","w");//以写的方式打开文件

	//相应操作
	fprintf(pf, "%s %s %d", person.flag, person.name, person.age);
	
	//关闭文件
	fclose(pf);
	return 0;
}

运行后的文件:

 3.6 fscanf

 fscanf与scanf的参数也很相似,多了一个文件指针,fscanf会将文件中的数据以格式化的方式储存到变量中,如果成功,返回成功读取的项数,如果失败,则返回EOF。

现在文件中有以下数据:

#include<stdio.h>
typedef struct str
{
	char flag[10];
	char name[10];
	int age;
}stu;//重命名
int main()
{
	stu person = {0};//创建一个结构体变量,初始化为0
	//打开文件
	FILE* pf = fopen("text.text","r");

	//相应操作
	fscanf(pf, "%s %s %d", person.flag, person.name, &(person.age));//从文件中读取数据到结构体变量中
	printf("%s %s %d", person.flag, person.name, person.age);//打印结构体变量中的值
	//关闭文件
	fclose(pf);
	return 0;
}

运行结果:

 3.7 fwrite

fwrite是以二进制的方式写文件,返回成功写入的元素总数,因此可以接收它的返回值来判断是否完全写入成功。

  • 第一个参数为一个指针,指向将要写到文件中的数据
  • 第二个参数为基本单元的大小(一个单独数据的大小)
  • 第三个参数为基本单元的个数(比如数组的元素个数)
  • 第四个参数为文件指针

初始文件为空:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("text.text","wb");//以二进制写的方式打开文件

	//相应操作
	char arr[] = { "Hello,Celia~" };

	fwrite(arr, sizeof(char), sizeof(arr), pf);

	//关闭文件
	fclose(pf);
	return 0;
}

运行后的文件:

3.8 fread

 fread 是以二进制的方式读文件,将文件中的二进制数据存入指定的字符串数组中,返回成功读取的元素总数。

  • 第一个参数为一个指针,指向将要接收数据的数组首地址
  • 第二个参数为基本单元的大小(一个单独数据的大小)
  • 第三个参数为基本单元的个数(比如数组的元素个数)
  • 第四个参数为文件指针

现在文件中有以下内容(二进制):

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("text.text","rb");//以二进制读的方式打开文件

	//相应操作
	char arr[100] = {0};//初始化为0

	fread(arr, sizeof(char), sizeof(arr), pf);//将文件中的数据存入数组
	puts(arr);//打印数组

	//关闭文件
	fclose(pf);
	return 0;
}

运行结果:

四、文件的随机读写

  我们在读写文件时,文件指针默认是指向文件中的第一个位置,这就使得我们只能对文件进行顺序操作,如果想从文件中的其他位置进行读写,就需要用到文件随机读写函数:

  • fseek:根据文件中指针的位置和偏移量来定位文件指针
  • ftell:返回文件指针相对于起始位置的偏移量
  • rewind:让文件指针的位置回到文件的起始位置

4.1 fseek

  • 第一个参数为文件指针
  • 第二个参数,如果是二进制文件,为偏移的字节数;如果是文本文件,为0或ftell返回的值
  • 第三个参数为文件指针的位置,共有三种选择:   

fseek会根据文件指针的位置和偏移量来定位文件指针(从左向右偏移量为正,从右向左偏移量为负),如果成功,该函数会返回0,如果失败,则返回非0值。

现在文件有以下内容:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("text.text","r");//以读的方式打开文件

	//相应操作
	char arr[100] = {0};

	fseek(pf, 6, SEEK_SET);//从文件开头偏移6个位置
	fscanf(pf, "%s", arr);
	puts(arr);

	//关闭文件
	fclose(pf);
	return 0;
}

运行结果:

4.2 ftell

ftell会返回当前文件指针相对于文件起始位置的 字节数 

现在文件中有以下内容:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("text.text","r");//以读的方式打开文件

	//相应操作
	char ch = 0;
	
	while ((ch = fgetc(pf)) != EOF)
		printf("%d ", ftell(pf));//打印每一次的相对位置(字节)

	//关闭文件
	fclose(pf);
	return 0;
}

运行结果:

4.3 rewind

rewind会将文件指针的位置 设置为文件的起始位置

现在文件中有以下内容:

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("text.text","r");//以读的方式打开文件

	//相应操作
	char ch = 0;
	int flg = 1;
	while ((ch = fgetc(pf)) != EOF)
	{
		if (ftell(pf) == 6 && flg)//当偏移量为6时,将文件指针设置为文件起始位置
		{
			rewind(pf);//设置文件指针的位置为文件起始位置
			flg = 0;
		}
		printf("%c", ch);//打印每一次读取到的字符

	}

	//关闭文件
	fclose(pf);
	return 0;
}

运行结果:

五、文件读取结束的判定

 5.1 feof

  • feof 函数会在文件读取结束时,判断文件读取结束的原因:是否为遇到文件结尾而结束
  • 如果是正常遇到文件结尾而结束,返回非0值,否则返回0
  • 所以并不能用feof的返回值直接判断文件读取是否结束,feof仅仅能检测文件读取结束的原因

5.2 常见判断文件读取结束的方法

  • 判断文本文件是否读取结束:
    --> fgetc 判断返回值是否为EOF
    --> fgets 判断返回值是否为NULL
  • 判断二进制文件是否读取结束
    --> 利用fread判断返回值是否小于实际要读的个数

 六、文件缓冲区

ANSIC采用 “缓冲文件系统” ,处理数据文件,系统会自动在内存中为正在使用的文件开辟一块文件缓冲区 ,内存和硬盘之间的数据交换会先在存入文件缓冲区,待文件缓冲区存满时,再一并进行数据传输。这样可以避免频繁向CPU传输指令,能让整个程序以至整个电脑更有效率的工作。

  • 16
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Celia~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值