【C语言】文件操作详解

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

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

所属专栏:C语言

个人主页Celia's blog~

目录

引言

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

二、文件的打开与关闭

[2.1 流和标准流](#2.1 流和标准流)

[2.1.1 流](#2.1.1 流)

[2.1.2 标准流](#2.1.2 标准流)

[2.2 文件指针](#2.2 文件指针)

[2.3 文件的打开与关闭](#2.3 文件的打开与关闭)

三、文件的顺序读写

[3.1 fgetc](#3.1 fgetc)

[3.2 fputc](#3.2 fputc)

[3.3 fgets](#3.3 fgets)

[3.4 fputs](#3.4 fputs)

[3.5 fprintf](#3.5 fprintf)

[3.6 fscanf](#3.6 fscanf)

[3.7 fwrite](#3.7 fwrite)

[3.8 fread](#3.8 fread)

四、文件的随机读写

[4.1 fseek](#4.1 fseek)

[4.2 ftell](#4.2 ftell)

[4.3 rewind](#4.3 rewind)

五、文件读取结束的判定

[5.1 feof](#5.1 feof)

[5.2 常见判断文件读取结束的方法](#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 头文件中对于文件类型申明:

cpp 复制代码
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* 的指针来维护这个结构体变量。

创建一个文件指针:

cpp 复制代码
FILE* pf;//创建一个文件指针变量pf

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

2.3 文件的打开与关闭

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

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

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

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

	//相应操作


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

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

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

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

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

三、文件的顺序读写

3.1 fgetc


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

cpp 复制代码
#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。

初始文件为空:

cpp 复制代码
#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。

现在文件中有以下内容:

cpp 复制代码
#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。

初始文件为空:

cpp 复制代码
#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会将数据以格式化的方式存入文件当中,如果成功,将返回存入字符的总数,如果失败,将返回一个负数。

初始文件为空:

cpp 复制代码
#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。

现在文件中有以下数据:

cpp 复制代码
#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是以二进制的方式写文件,返回成功写入的元素总数,因此可以接收它的返回值来判断是否完全写入成功。

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

初始文件为空:

cpp 复制代码
#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 是以二进制的方式读文件,将文件中的二进制数据存入指定的字符串数组中,返回成功读取的元素总数。

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

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

cpp 复制代码
#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值。

现在文件有以下内容:

cpp 复制代码
#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会返回当前文件指针相对于文件起始位置的 字节数

现在文件中有以下内容:

cpp 复制代码
#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会将文件指针的位置 设置为文件的起始位置

现在文件中有以下内容:

cpp 复制代码
#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传输指令,能让整个程序以至整个电脑更有效率的工作。

相关推荐
代码雕刻家22 分钟前
数据结构-3.9.栈在递归中的应用
c语言·数据结构·算法
吾爱星辰1 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
ChinaDragonDreamer1 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
IT良1 小时前
c#增删改查 (数据操作的基础)
开发语言·c#
Kalika0-02 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
_.Switch2 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
代码雕刻家2 小时前
课设实验-数据结构-单链表-文教文化用品品牌
c语言·开发语言·数据结构
一个闪现必杀技2 小时前
Python入门--函数
开发语言·python·青少年编程·pycharm
Fan_web2 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
龙图:会赢的2 小时前
[C语言]--编译和链接
c语言·开发语言