0
点赞
收藏
分享

微信扫一扫

【C语言】文件操作详解



文章目录

  • ​​1.为什么需要文件?​​
  • ​​2.什么是文件?​​
  • ​​2.1文件分类​​
  • ​​2.2文件名​​
  • ​​3.文件的使用​​
  • ​​3.1文件指针​​
  • ​​3.2打开和关闭文件​​
  • ​​3.2.1文件使用方式​​
  • ​​3.2.2标准输入输出流​​
  • ​​3.3文件输入输出函数​​
  • ​​3.3.1字符输入输出​​
  • ​​实现文件拷贝​​
  • ​​3.3.2文本行输入输出​​
  • ​​3.3.3格式化输入输出​​
  • ​​3.3.4二进制输入输出​​
  • ​​3.3.5 sscanf/sprintf函数​​
  • ​​3.4.其他文件函数​​
  • ​​3.4.1 fseek​​
  • ​​3.4.2 ftell​​
  • ​​3.4.3 rewind​​
  • ​​4.文本文件和二进制文件​​
  • ​​5.文件读取结束的判定​​
  • ​​5.1错误使用feof​​
  • ​​6.文件缓冲区​​
  • ​​代码示例1​​
  • ​​代码示例2​​
  • ​​结语​​


好久没有更新C语言学习的博客了,今天带来的是文件部分的知识点!????

1.为什么需要文件?

之前学习过通讯录的代码实现,可以给通讯录中增加、删除联系人。但是这个通讯录在你exe文件关闭的同时就被销毁了,它的内容并不能顺延到下一次打开这个通讯录,这对我们的使用产生了不便。

而文件可以帮助我们​实现数据的持久化​:将数据保存在磁盘文件中,下次打开通讯录的时候,之前保存的联系人不会消失。

【C语言】文件操作详解_文件指针

2.什么是文件?

文件就是存放在磁盘上的带特定格式的数据。

2.1文件分类

在程序设计中,一般讨论两种文件:程序文件、数据文件

  • 程序文件:代码源文件​​如.c​​​,目标文件​​.obj/.o​​​,可执行文件​​.exe​
  • 数据文件:程序在使用过程中读写的数据,比如读取内容的文件,以及数据输出的文件

这篇博客我们了解的是​​数据文件​

2.2文件名

文件名包含3个部分:​​文件路径+文件名主干+文件后缀​

如:​​c:\code\test.txt​

文件标识常被称为文件名

3.文件的使用

3.1文件指针

在文件操作中,非常重要的一个知识点就是​​文件类型指针​​,简称文件指针

每个文件在开辟的时候都有一个对于的文件信息区,用于保存文件的名字、状态、当前的位置等相关信息。这些信息保存在了一个结构体中,该结构体系统声明为​FILE

不同的C语言编译器都有不同的FILE类型,但是大同小异。

打开一个文件的时候,系统会根据文件的内容,自动创建FILE结构体变量,并填充它的信息。

【C语言】文件操作详解_文件指针_02

我们需要使用文件的时候,​就可以通过一个FILE类型的指针来访问这个结构体变量

【C语言】文件操作详解_数据_03

3.2打开和关闭文件

文件在读写之前需要​打开文件​,使用结束后需要​关闭文件


这一点和动态内存管理很相似


ANSIC规定用fopen函数来打开文件,fclose来关闭文件。

打开文件的同时,会返回一个​​FILE*​​的指针变量指向该文件。


关闭文件后,文件指针就变成了​野指针​,需要置为NULL防止错误调用


fopen函数打开文件失败,会返回空指针

#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));//用该函数打印错误信息
return 0;
}
//1.读文件

//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));//用该函数打印错误信息
return 0;
}
//2.写文件

//关闭文件
fclose(pf);
pf = NULL;

return 0;
}


strerror函数在这篇博客里面有讲解????​​点我​​


3.2.1文件使用方式

通过这张表格,我们可以了解一下文件使用方式的不同类型

  • 注意:使用它们用的都是​双引号​,而不是单引号!

【C语言】文件操作详解_文件指针_04

用w写入的时候,​会覆盖原本已有内容​。如果需要在已有内容后面​追加​,需要使用a

【C语言】文件操作详解_数据_05

3.2.2标准输入输出流

  • 输出:内存→文件
  • 输入:文件→内存

【C语言】文件操作详解_数据_06

C语言程序,运行的时候会默认打开3个流

  • stdin:标准输入流
  • stdout:标准输出流
  • stderr:标准错误流

在执行输入输出操作的时候,之前我们是直接​将内存中的数据printf打印到屏幕上

现在我们可以通过文件指针,将数据​输入到标准输出流​,达到类似printf的效果

【C语言】文件操作详解_数据_07

3.3文件输入输出函数

上述代码中,用到了​​fputc​​函数,这个函数的作用是将​一个字符输入到文件中

下表列出了一些我们会用到的文件函数

【C语言】文件操作详解_后端_08

3.3.1字符输入输出

fputc函数:向文件中写入单个字符

【C语言】文件操作详解_c语言_09

fgetc函数:从文件中读取单个字符

可以看到,我们把刚刚文件中写入的字符全部打印出来了

【C语言】文件操作详解_数据_10

实现文件拷贝

将一个文件的内容拷贝到另外一个文件中

int main()
{
//实现一个代码将data.txt 拷贝一份 生成data2.txt
FILE* pr = fopen("data.txt", "r");
if (pr == NULL)
{
printf("open for reading: %s\n", strerror(errno));
return 0;
}

FILE* pw = fopen("data2.txt", "w");
if (pw == NULL)
{
printf("open for writting: %s\n", strerror(errno));
fclose(pr);
pr = NULL;
return 0;
}
//拷贝文件
int ch = 0;
while ((ch = fgetc(pr)) != EOF)
{
fputc(ch, pw);
}

fclose(pr);
pr = NULL;
fclose(pw);
pw = NULL;

return 0;
}

【C语言】文件操作详解_数据_11

3.3.2文本行输入输出

fputs函数:将字符串写入到文件中

//写一行
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
fputs("hello world\n", pf);
fputs("hehe\n", pf);


fclose(pf);
pf = NULL;

return 0;
}

运行代码,可以看到两行字符串已经被写入到了项目路径下的data.txt文件中

【C语言】文件操作详解_数据_12

fgets函数:从文件中读取规定长度的字符串

该函数在使用的时候具有第3个参数,用于限制读取字符串的长度

读文件-读一行
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
char buf[1000] = {0};
//读文件
fgets(buf, 3, pf);
printf("%s\n", buf);

fgets(buf, 3, pf);
printf("%s\n", buf);

fclose(pf);
pf = NULL;

return 0;
}

运行程序,可以看到我们设置的是3,却只读取了2个字符出来

【C语言】文件操作详解_文件指针_13

将​​buf[2]​​更改为1,调试查看

【C语言】文件操作详解_文件指针_14

可以看到,在执行第一个​​fgets​​​函数后,原本的1被写入成了​​\0​

【C语言】文件操作详解_开发语言_15

这就证实:fgets函数在读取字符的时候,会以​​\0​​作为结尾

如果我们需要读取3个字符,​就需要将限制设置为4

3.3.3格式化输入输出

这里的“格式化”指的是结构体这种​具有特定格式的数据内容

fprintf函数:将格式化数据写入文件中

#include<stdio.h>
//……
struct Stu
{
char name[20];
int age;
double d;
};
int main()
{
struct Stu s = { "张三", 20, 95.5 };
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//写格式化的数据
fprintf(pf, "%s %d %lf", s.name, s.age, s.d);

fclose(pf);
pf = NULL;

return 0;
}

【C语言】文件操作详解_数据_16

fscanf函数:从文件中读取格式化数据,存放到对应结构体变量s中

【C语言】文件操作详解_数据_17

3.3.4二进制输入输出

  • fread、fwrite可以操作任意类型的数据
  • 正如它的名字,二进制输入函数是将内容以二进制的方式输入到文件中


使用该函数的时候需要使用**“rb”,“wb”**来打开文件

【C语言】文件操作详解_c语言_18


fwrite(s, sizeof(struct Stu), 2, pf);
//s 来源
//sizeof 需要写入元素的大小
//2 需要写入元素的个数
//pf 写入的目标文件指针

以下是写入结构体变量的例子

struct Stu
{
char name[20];
int age;
double d;
};
//二进制的写
int main()
{
struct Stu s[2] = { {"张三", 20, 95.5} , {"lisi", 16, 66.5}};

FILE* pf = fopen("data.txt", "wb");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//按照二进制的方式写文件
fwrite(s, sizeof(struct Stu), 2, pf);

fclose(pf);
pf = NULL;

return 0;
}

可以看到,此时写入的数据已经部分成了乱码。这时候它的内容已经是用二进制存放的了,txt阅读器无法正确读出这些数据

【C语言】文件操作详解_c语言_19

二进制的读取​就是复现这一步,将文本中的二进制数据以特定格式读取出来,并放入对应变量

fread(s, sizeof(struct Stu), 2, pf);
//s 存放文件内容的变量
//sizeof 需要读取元素的大小
//2 需要读取元素的个数
//pf 读取的目标文件指针

【C语言】文件操作详解_c语言_20

3.3.5 sscanf/sprintf函数

这两个函数比较特殊,它们的作用是将文件里面的​格式化数据​(如结构体)以​字符串​的形式拷贝到字符数组里面

【C语言】文件操作详解_后端_21

见下图

【C语言】文件操作详解_文件指针_22

【C语言】文件操作详解_c语言_23

3.4.其他文件函数

3.4.1 fseek


http://cplusplus.com/reference/cstdio/fseek/?kw=fseek


该函数的作用是:将文件指针移动到相对于某个位置的特定偏移量的位置

【C语言】文件操作详解_后端_24

听起来有点绕口,举例说明就知道了

给定一个字符串“abcdef”

每次使用一次fgetc,文件指针就会往后进一位。使用两次,文件指针指向的是字符c

如果我们需要指向f,就让指针

  • 从开始位置向后进5位
  • 从当前位置向后进3位
  • 从结束位置向前进1位

【C语言】文件操作详解_文件指针_25

我们可以用该函数,定位文件指针,将其更改到我们需要的位置,进行字符替换等操作

int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//写文件
int ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
fputc(ch, pf);
}

//定位文件指针

fseek(pf, -2, SEEK_END);
fputc('#', pf);//将当前字符替换成#

fclose(pf);
pf = NULL;
return 0;
}

【C语言】文件操作详解_后端_26

3.4.2 ftell

返回文件指针当前的偏移量(相对于文件开头)

【C语言】文件操作详解_数据_27

3.4.3 rewind


http://cplusplus.com/reference/cstdio/rewind/?kw=rewind


让文件指针的位置回到文件的起始位置

fseek(pf, 0, SEEK_SET);
//rewind函数与该fseek函数操作等价
//但是rewind更方便
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//读文件
int ch = fgetc(pf);
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b

int ret = ftell(pf);
printf("%d\n", ret);//2
rewind(pf);
//fseek(pf, 0, SEEK_SET);
ret = ftell(pf);
printf("%d\n", ret);//0
fclose(pf);
pf = NULL;
return 0;
}

4.文本文件和二进制文件

我们现在已经知道了fread/fwrite函数可以实现二进制的输入输出,它们是怎么具体实现的呢?

根据数据的组织形式,数据文件被称为​本文件或者二进制文件​。 数据在内存中以二进制的形式存储,如果不加换的输出到外存,就是二进制文件。

如果要求在外存上以ASCII码的形式存储,则需要在存储前换。以ASCII字符的形式存储的文件就是文本文件。

在内存中,字符一律以ASCII形式存储,​数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

对于数字10000,可以用下面两种方式存储

  • 1 0 0 0 0 作为5个字符来存储–占用5个字节
  • 以数字本身的二进制形式存储–占用4个字节

这时候使用二进制的方式,就能节省空间

【C语言】文件操作详解_数据_28

用如下代码,将10000以二进制方式写入文件中

【C语言】文件操作详解_文件指针_29

在VS中我们可以以特定打开方式​​二进制编辑器​​​打开​​test.txt​​文档

【C语言】文件操作详解_数据_30

可以看到10000是以二进制码的形式存放在文件中的

【C语言】文件操作详解_数据_31


这里涉及到了大小端的问题????​​点我​​


5.文件读取结束的判定

5.1错误使用feof

并不能直接使用feof函数的返回值来判断文件是否结束

而是应在文件读取结束的时候,​用feof函数判断是读取失败结束,还是遇到文件结尾正常结束

  1. 文本文件读取是否结束,判断返回值
  • EOF(fgetc)
  • NULL(fgets)

【C语言】文件操作详解_数据_32

ferror函数:判断文件是否出现了读取错误。​如果有,返回为真


http://cplusplus.com/reference/cstdio/ferror/?kw=ferror


【C语言】文件操作详解_c语言_33

  1. 二进制文件的读取结束,判断返回值是否小于实际要读的个数
  • fread的返回值是​成功读取数据的个数
  • 判断返回值是否小于实际要读取的个数

【C语言】文件操作详解_后端_34

6.文件缓冲区


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


所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。


这就和git一样,是先将需要push的文件放入缓存区,确认文件无误后再push到远程仓库中


如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓 冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小是​C编译系统​(编译器)决定的。

【C语言】文件操作详解_文件指针_35

因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文 件。 ​如果不做,可能导致读写文件的问题。

代码示例1

#include <stdio.h>
#include <windows.h>

int main()
{
FILE* pf = fopen("test.txt", "w");
fputs("abcdef", pf);//先将代码放在输出缓冲区
printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)

printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
Sleep(10000);
fclose(pf);
//注:fclose在关闭文件的时候,也会刷新缓冲区
pf = NULL;
return 0;
}

运行程序,通过sleep函数暂停程序,可以看到刚开始字符串并没有存入文件中

【C语言】文件操作详解_文件指针_36

而是先写入输入缓存区,刷新缓存区后,才写入txt文件

【C语言】文件操作详解_文件指针_37

代码示例2

【C语言】文件操作详解_c语言_38

#include <stdio.h>
#include <windows.h>
int main()
{
while (1)
{
printf("hehe\n");
//在linux环境中,不带'\n'的时候,并不会打印(没有刷新缓存区)
//而在VS环境中,带不带都会正常打印
Sleep(1000);//linux环境中,sleep函数的参数,单位是秒(VS是毫秒)
// linux环境下,sleep函数需要小写,VS下是Sleep
}
return 0;
}

【C语言】文件操作详解_后端_39

在Linux环境下(树莓派)测试这个代码

可以看到,去掉​​\n​​后,代码并不会打印hehe

【C语言】文件操作详解_文件指针_40

编译的时候,遇到报错????,但是程序依旧编译出来了

implicit declaration of function ‘sleep’

CSDN查了查,发现是需要引用头文件​​#include <unistd.h>​

重新编译,没有报错了(此处hehe已经加了​​\n​​,程序正常打印)

【C语言】文件操作详解_后端_41

结语

文件章节的内容非常丰富,你学费了吗!????

大多数内容还是需要我们多多操作来熟悉它的真正作用


如果内容有误,还请大佬无情指正!




举报

相关推荐

0 条评论