文章目录

  • 前言
  • 一、为什么使用文件
  • 二、什么是文件
  • 三、文件的打开和关闭
  • 四、文件的顺序读写
  • 五、文件的随机读写
  • 六、文本文件和二进制文件
  • 七、文件读取结束的判定
  • 八、文件缓冲区

前言

电脑文件,也可以称之为计算机文件,是存储在某种长期储存设备或临时存储设备中的一段数据流,并且归属于计算机文件系统管理之下。所谓“长期储存设备”一般指磁盘、光盘、磁带等。而“短期存储设备”一般指计算机内存。需要注意的是,存储于长期存储设备的文件不一定是长期存储的,有些也可能是程序或系统运行中产生的临时数据,并于程序或系统退出后删除。


提示:以下是本篇文章正文内容,下面案例可供参考

一、为什么使用文件

我们前面学习结构体时,写了通讯录的程序,当通讯录运行起来的时候,可以给通讯录中增加、删除数 据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯 录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受。 我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。 这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据 库等方式。 使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。

二、什么是文件

磁盘上的文件是文件。 但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。

1.程序文件

包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境 后缀为.exe)。

2.读入数据

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文 件,或者输出内容的文件。

3.文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。

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

例如: c:\code\test.txt(存储于C盘code文件夹中的 test.txt 文件)

为了方便起见,文件标识常被称为文件名。

注:本篇博客讨论的是数据文件


三、文件指针

1.文件指针

缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。 每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名 字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统 声明的,取名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

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。 每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息, 使用者不必关心细节。 一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。 下面我们可以创建一个FILE*的指针变量:

FILE* pf;//文件指针变量

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变 量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联 的文件。

2.文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。 在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了 指针和文件的关系。 ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。

//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream );

打开文件:第一个参数是文件的文件名,第二个参数是文件的打开方式,返回类型是文件指针类型

关闭文件:参数是存储文件首地址的文件指针,返回类型为整型,成功关闭文件返回 0  ,失败返回 EOF (-1)。

文件使用方式 含义 如果指定文件不存在
“r”(只读) 为了输入数据,打开一个已经存在的文本文件 出错
“w”(只写) 为了输出数据,打开一个文本文件 建立一个新的文件
“a”(追加) 向文本文件尾添加数据 建立一个新的文件
“rb”(只读) 为了输入数据,打开一个二进制文件 出错
“wb”(只写) 为了输出数据,打开一个二进制文件 建立一个新的文件
“ab”(追加) 向一个二进制文件尾添加数据 出错
“r+”(读写) 为了读和写,打开一个文本文件 出错
“w+”(读写) 为了读和写,建议一个新的文件 建立一个新的文件
“a+”(读写) 打开一个文件,在文件尾进行读写 建立一个新的文件
“rb+”(读写) 为了读和写打开一个二进制文件 出错
“wb+”(读写) 为了读和写,新建一个新的二进制文件 建立一个新的文件
“ab+”(读写) 打开一个二进制文件,在文件尾进行读和写 建立一个新的文件

文件打开关闭的实例操作:

#include <stdio.h>
int main()
{//打开文件FILE* pf = fopen("data.txt", "r");//判断文件是否打开成功if (pf == NULL){perror("fopen");return;}//关闭文件fclose(pf);pf = NULL;return 0;
}

由于文件夹中没有这样的一个 data.txt 文件,我们又是以“r”(只读)的方式打开的,所以这样程序会出错。

如果我们的文件夹中有这样的文件

这里什么都没有就表示文件打开和关闭都成功了。


四、文件的顺序读写

功能 函数名 适用于
字符输入函数 fgetc 所有输入流
字符输出函数 fputc 所有输出流
文本行输入函数 fgets 所有输入流
文本行输出函数 fputs 所有输出流
格式化输入函数 fscanf 所有输入流
格式化输出函数 fprintf 所有输出流
二进制输入 fread 文件
二进制输出 fwrite 文件

前提:引入流的概念

 1.fgetc函数

①.所需头文件

②.参数及返回类型

 这里的参数是成功打开的文件指针,返回类型是整型(返回读到字符的assic码值)。

③.函数的使用

假设我们有如图的文本内容,我们依次顺序去读每一个字符,得到的字符assic码值存在一个整型变量里,实现字符的读取操作

#include <stdio.h>
int main()
{//打开文件FILE* pf = fopen("data.txt", "r");//判断文件是否打开成功if (pf == NULL){perror("fopen");return;}//写文件int ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);//关闭文件fclose(pf);pf = NULL;return 0;
}

我们看到这样就读取到了文件中的前3个字符

2.fputc函数

①.所需头文件

②.参数及返回类型

第一个参数是需要写进文件的字符,第二个参数是文件指针,返回类型是整型记录是否写入成功。

③.函数的使用

#include <stdio.h>
void main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return;}fputc('b', pf);fputc('i', pf);fputc('t', pf);fclose(pf);pf = NULL;return 0;
}
#include <stdio.h>
void main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return;}fputc(98, pf);fputc(105, pf);fputc(116, pf);fclose(pf);pf = NULL;return 0;
}

两种参数的样式都是可以的,这样就可以把内容写入到文件中

3.fgets函数

①.所需头文件

②.参数类型及返回类型

第一个参数是指针类型存储提前定义好的用来存储读取到字符串的首地址(下方代码中的arr数组),第二个参数是读取的字符最大个数(这里实际读取到的字符个数是 n-1),第三个参数是文件指针。返回类型是字符指针类型,用来存放读取到的字符串首地址。

③.函数的使用

#include <stdio.h>
void main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return;}char arr[20] = { 0 };fgets(arr, 5, pf);printf("%s\n", arr);fgets(arr, 5, pf);printf("%s\n", arr);fclose(pf);pf = NULL;return 0;
}

我们看到这里实际读取到的字符是4个。

4.fputs函数

①.所需头文件

②.参数类型及返回类型

第一参数是需要写进文件的字符串首地址,第二个参数是文件指针。返回类型是整型,用来记录是否写入成功

③.函数的使用

#include <stdio.h>
void main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return;}fputs("hello abc", pf);fclose(pf);pf = NULL;return 0;
}

5.fscanf函数

①.所需头文件

②.参数类型及返回值类型(对比熟知的 scanf 函数即可)

相比我们熟知的scanf函数只需要多一个参数,即第一个参数的文件指针

③.函数的使用

#include <stdio.h>
struct S
{int a;float b;
};
int main()
{struct S s = { 0 };FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return;}fscanf(pf, "%d %f", &(s.a), &(s.b));printf("%d %f", s.a, s.b);fclose(pf);pf = NULL;return 0;
}

6.fprintf函数

①.所需头文件

②.返回类型及参数类型(对比熟知的printf函数即可)

参数只是在第一个参数的位置多了一个文件指针,其他的均完全一样

③.函数的使用

#include <stdio.h>
struct S
{int a;float b;
};
int main()
{struct S s = { 235,3.26 };FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return;}fprintf(pf, "%d %f", s.a, s.b);fclose(pf);pf = NULL;return 0;
}

7.fread函数

①.所需头文件

②.参数类型及返回类型

第一个参数是指针变量用来存储读取到的二进制数据的地址,第二个参数是读取到的数据的总大小,第三参数是每次读取几个数据,第四个参数是文件指针,返回值无符号整型

③.函数的使用

#include <stdio.h>
struct S
{int a;float b;char name[20];
};
int main()
{struct S s = { 0 };FILE* pf = fopen("data.txt", "rb");if (pf == NULL){perror("fopen");return;}fread(&s, sizeof(s), 1, pf);printf("%d %f %s", s.a, s.b, s.name);fclose(pf);pf = NULL;return 0;
}

假设提前写入了如下的二进制文本

fread读取后:

8. fwrite函数

①.所需头文件

②.参数类型及返回类型

 第一个参数是指针类型用来存放需要进行写入的数据地址,其他和fread的类型完全一致

③.函数的使用

#include <stdio.h>
struct S
{int a;float b;char name[20];
};
int main()
{struct S s = { 235,3.26,"zhangsan" };FILE* pf = fopen("data.txt", "wb");if (pf == NULL){perror("fopen");return;}fwrite(&s, sizeof(s), 1, pf);fclose(pf);pf = NULL;return 0;
}

我们可以看到这里文本中的内容我们看不懂这是因为我们是用二进制的方法写入数据的,再用二进制的方法读取即可

同步上方的 fread 函数即可。

9.函数对比


五、文件的随机读写

1.fseek函数(根据文件指针的位置和偏移量来定位文件指针)

①.所需头文件

②.参数类型及返回类型

第一个参数是文件指针,第二个参数是需要查找元素的偏移量(相对文件首地址),第三个参数是文件的起始位置,C语言中有这样的规定:

SEEK_CUR代表文件指针当前的位置

SEEK_END代表文件末尾的位置

SEEK_SET代表文件开始的位置

③. 函数的使用

SEEK_SET:

#include <stdio.h>
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return;}fseek(pf, 2, SEEK_SET);int ch = fgetc(pf);printf("%c\n", ch);fclose(pf);pf = NULL;return 0;
}

如上的代码我们把随机查找的是相对 pf 偏移量为 2 的元素,再用fgetc函数得到我们 pf 目前所指向的元素,即我们文件中的 c 元素

SEEK_CUR:

fseek(pf, -2, SEEK_CUR);ch = fgetc(pf);printf("%c\n", ch);

这里承接上方的代码,这里 SEEK_CUR 指向的是相对文件指针偏移量为 3 的地方,原因:我们上方的代码的偏移量设置的为 2 ,当文件指针指向偏移量为 2 的位置后,当前代码完成后,文件指针自动后移,指向下一个元素地址,所以这里的偏移量为 3。我们想要查找文件中的 b 元素,即向左偏移两个即可(即-2个偏移量--->左-右+)

SEEK_END:

 fseek(pf, -2, SEEK_END);ch = fgetc(pf);printf("%c\n", ch);

文件末尾偏移量为 -2 (向左偏移2个)的元素是 d


2.ftell函数(返回文件指针相对于起始位置的偏移量)

①.所需头文件

②.参数类型及返回类型

唯一参数就是文件指针,返回类型是长整型,返回的是文件指针的偏移量。

③.函数的使用

#include <stdio.h>
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return;}fseek(pf, 2, SEEK_SET);int ch = fgetc(pf);printf("%c\n", ch);int ret = ftell(pf);printf("%d", ret);fclose(pf);pf = NULL;return 0;
}

这里打印出来的偏移量为 3 ,因为上面的 fseek 函数使文件指针偏移了 2 个位置,当函数结束文件指针指向下一个元素地址,所以当 ftell 函数求得偏移量为 3


3.rewind函数(让文件指针的位置回到文件的起始位置)

①.所需头文件

②.参数类型及返回类型

参数是文件指针,返回类型是空

③.函数的使用

#include <stdio.h>
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return;}fseek(pf, 2, SEEK_SET);int ch = fgetc(pf);printf("%c\n", ch);rewind(pf);ch = fgetc(pf);printf("%c", ch);fclose(pf);pf = NULL;return 0;
}

如上的代码,上面用 fseek 函数把文件指针进行了 2 个的偏移,指向的是c,然后用 rewind 函数将文件指针回到文件的起始位置,这时指向的是 a


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

根据数据的组织形式,数据文件被称为文本文件或者二进制文件。 数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。 如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文 本文件。 一个数据在内存中是怎么存储的呢? 字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而 二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。

因为机器是小端存储所以文件是我们如上图这样存储十进制的10000


七、文件读取结束的判定

1.被错误使用的feof

牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。

而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。

1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets ) 例如: fgetc 判断是否为 EOF . fgets 判断返回值是否为 NULL .

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

文本文件:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{int c; // 注意:int,非char,要求处理EOFFILE* fp = fopen("test.txt", "r");if (!fp) {perror("File opening failed");return EXIT_FAILURE;}//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOFwhile ((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);
}

二进制文件:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{int c; // 注意:int,非char,要求处理EOFFILE* fp = fopen("test.txt", "r");if (!fp) {perror("File opening failed");return EXIT_FAILURE;}//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOFwhile ((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);
}

总结:ferror 函数是当文件结束时,判断文件是不是因为遇到错误结束的

           feof 函数是当文件结束时,判断文件是不是因为遇到文件末尾结束的


八.文件缓冲区

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

 验证缓冲区的存在:

#include <stdio.h>
#include <windows.h>
//VS2013 WIN10环境测试
int main()
{FILE*pf = fopen("test.txt", "w");fputs("abcdef", pf);//先将代码放在输出缓冲区printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");Sleep(10000);printf("刷新缓冲区\n");fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)//注:fflush 在高版本的VS上不能使用了printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");Sleep(10000);fclose(pf);//注:fclose在关闭文件的时候,也会刷新缓冲区pf = NULL;return 0;
}

这里可以得出一个结论: 因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。

如果不做,可能导致读写文件的问题


后续:通讯录实现文件化

普通版通讯录-->C语言----实现动态通讯录_baiyang2001的博客-CSDN博客

由于我们普通版的通讯录只能实现单次的录入,下次再运行数据还需要重新的输入,所以我们考虑使用文件的方式,把每次输入的数据都存储到文件中,这样可以保证每一次输入的数据,在下一次运行时也还存储于通讯录中

改造:

①.在推出通讯录的时候保存本次运行时输入联系人的信息

在保存文件的函数中:打开文件,后判断是否成功的打开了文件,把联系人的信息以二进制的方式写到文本文件中(addlist.txt)

②.联系人信息都写到文本文件中了,下面我们就需要把文本文件中二进制的信息读出来

还是一上来先打开文件,判断文件是不是打开成功,,以二进制的方式去读取文件,循环对文件去进行读取,进入到循环先判断通讯录满没满,满了进入函数去增容,然后存放读取到的联系人信息的结构体 tmp 赋值给我们 pc 指向的data数据,每赋值完一次,人数加 1 。

由于在读文件的时候需要用到增容函数,在初始化通讯录时也需要用到增容函数,所以我们在这里把增容函数作为一个单独的函数写在外面方便两个功能函数的调用。

③.增容函数

成果展示: 

第一个录入四个人的信息:

第二次直接展示通讯录中的信息:

 我们看到上一次运行时录入的信息还是存在于通讯录中


总结

提示:这里对文章进行总结:
希望通过本篇博客让大家对C语言中的文件操作有一个基本的认识,如果文章中存在问题或者哪里讲述不清,欢迎下方及时留言,谢谢大家的支持!!!

C语言文件操作+通讯录实现文件操作相关推荐

  1. C语言实现动态通讯录(附带文件保存)

    ✅作者简介:嵌入式入坑者,与大家一起加油,希望文章能够帮助各位!!!!

  2. 抽丝剥茧C语言(高阶)文件操作+练习

    C语言文件操作 1. 为什么使用文件 2. 什么是文件 2.1 程序文件 2.2 数据文件 2.3 文件名 3. 文件的打开和关闭 3.1 文件指针 3.2 文件的打开和关闭 4. 文件的顺序读写 4 ...

  3. python调用什么函数实现对文件内容的读取_如何使用python语言中的方法对文件进行读写操作...

    在我们使用python语言中的文件时,可以使用open()方法打开文件,close()方法关闭文件,read()方法读取文件内容,write()方法写入内容到文件中.下面利用几个实例说明文件读写方法, ...

  4. C 语言编程 — 输入/输出与文件操作

    目录 文章目录 目录 前文列表 输入/输出 scanf() 和 printf() getchar() 和 putchar() 文件操作 打开文件 关闭文件 写入文件 读取文件 二进制 I/O 函数 前 ...

  5. 嵌入式学习在linux下使用c语言对文件进行打开、读写操作。_2000字干货!单片机进阶嵌入式Linux要怎么学习……...

    文章字数:2000 干货指数:⭐⭐⭐⭐⭐ 导读:这篇文章我们来一起简单梳理嵌入式Linux的一些知识,方便一些想跟我一样想要由单片机进阶到嵌入式Linux的朋友做一些参考学习. 现在随着嵌入式Linu ...

  6. C语言文件与数组之间输入输出操作

    C语言文件与数组之间输入输出操作 文件存到数组里面: #include<iostream> #include<fstream> #include<string> # ...

  7. 趣学 C 语言(十二)—— 文件操作

    对于 C 语言而言,无论是标准输入 stdin,还是标准输出 stdout,还是标准错误输出 stderr,本质上都是一种文件操作,只不过读写的文件变成了控制台(console),或者说 stdin/ ...

  8. c语言文件操作步骤是,文件操作的正确流程,C语言文件操作的函数

    引言 操作文件的正确操作流程为: 打开文件->读写文件->关闭文件 在对文件进行读写操作之前,需要先打开文件,操作完成之后就要关闭文件!所谓的打开文件,就是需要获取文件的信息,例如文件名. ...

  9. C语言文件的拆分与合并操作

    用C语言编写一个可以拆分文件与合并文件的应用 具体使用方法如下 程序名  -t  s  拆分文件名  拆分大小  拆分文件生成列表 程序名  -t  c  文件列表  拆分文件名 具体思路,先使用 I ...

最新文章

  1. EOS技术及生态系统介绍
  2. 产品经理打架引发的问题:如何识别需求及其价值
  3. Uboot分析(三)
  4. Docker镜像仓库
  5. Cracking the Coding Interview 5.2
  6. (转)你的团队需要一个领袖,而不是一个主管
  7. 浅谈MySql的存储引擎(表类型)
  8. 理解 JS 回调函数中的 this
  9. python get请求带参数_python_request的安装及模拟json的post请求及带参数的get请求
  10. 雅虎宣布支持谷歌OpenSocial标准
  11. 达梦数据库DCA培训总结
  12. 取色器ColorPix
  13. 计算机学安杰拉,《朗文高级英语阅读参考-(上册)》.pdf
  14. EPLAN史上最全部件库,部件宏,EDZ格式,大小合适导入容易 部件包含图片宏
  15. java工资多少 程序员薪资很高吗?
  16. 《MATLAB App Designer从入门到实践》随书源代码
  17. amigo幸运字符什么意思_Python正则表达式之初始篇:字符匹配
  18. 数字化转型,有你有我
  19. Pacbio测序原理以及SMRT bell文库构建流程简述
  20. stc89c52c语言开发,stc89c52烧写程序

热门文章

  1. char* char[]
  2. H5绘制Android机器人
  3. 《一名网络工程师的自我修养》--子网划分
  4. python摄像头动作捕捉_新版的IphoneX是一台动作捕捉设备,你知道怎么玩吗
  5. 开发人员为何应该使用苹果电脑,兼Mac OS X
  6. 网盘搜索工具整理2020.8
  7. OpenCL ICD Loader运行测试暨解决报错:ERROR: App log and stub log differ.
  8. PHP 5.3 下载时 VC9、VC6、Thread Safe、Non Thread Safe
  9. Android美化EditText
  10. JButton部分常用的方法