目录
前言:
为什么我们要使用文件?
什么是文件?
程序文件:
数据文件:
文件名:
文件的打开和关闭
文件指针:
流程:
文件路径:
文件的顺序读写:
实现字符输入输出:
fputc:
fgetc:
“流”
实现文本行输入输出:
fputs:
fgets:
实现格式化输入输出函数:
fprintf:
fscanf:
二进制的输入输出:
fwrite:
fread:
文件的随机读写:
fseek:根据文件指针的位置和偏移量来定位文件指针
ftell:返回文件指针相对于起始位置的偏移量
rewind:让文件指针的位置回到起始位置
scanf/printf的理解
fscanf/fprintf的理解
sscanf/sprintf的理解
文本文件和二进制文件
文件的读取结束和判定
被错误使用的feof:
文件缓冲区
总结:
前言:
我们在之前实现的通讯录中,我们不难发现,当我们添加了足够多的联系人时点击显示确实是可以将我们添加了的联系人的各个信息显示出来。但是当我们退出程序,再想显示我们刚刚添加的联系人时,就会得到一句“该通讯录为空”。
所以在此,我们想要实现一种通讯录,是可以将我们的联系人的全部信息永久保存下来。这就引入了一个C语言新概念:《文件操作》
程序在运行时,里面的数据都是存放在内存中的,如果我们想要存放这些数据不至于丢失,可以使用文件来保存。因为文件是存放在硬盘上的!
为什么我们要使用文件?
既然我们之前提到了通讯录,通讯录不就应该把信息数据记录下来,只有自己选择删除,数据才不复存在才对呀。
这就涉及到了数据持久化问题,我们一般数据持久化的方法有:
1.把数据存放在磁盘文件中。
2.存放到数据库等方式。
使用文件我们就可以将数据直接存放在电脑的硬盘上了,做到了数据的持久化。
-
数据持久化:文件可以长期保存在磁盘上,即使程序关闭后再次运行,数据仍然存在。
-
数据共享:多个程序可以访问同一个文件,实现数据共享。
-
数据安全:文件可以设置权限,保护数据不被其他人随意访问或修改。
-
数据量大:文件可以存储大量的数据,适合处理大规模数据。
-
方便传输:文件可以通过网络等方式进行传输,方便数据交换。
什么是文件?
文件是计算机系统中存储数据的一种形式,可以是文本、图片、音频、视频等形式。文件可以保存在计算机的硬盘、SSD、闪存、光盘、云存储等介质中。文件通常有一个文件名和文件扩展名,用于标识不同的文件类型和区分不同的文件。文件的内容可以被读取、编辑、复制、移动、删除等。
程序文件:
程序文件指的是一组用来实现特定功能的计算机指令和数据。通常,它们以特定的编程语言编写并保存在计算机硬盘或其他存储介质上。程序文件也可以是一组脚本,例如JavaScript或Python,它们可以在特定环境中执行特定任务。程序文件的类型和结构取决于编写它们的编程语言和目的。程序文件经过编译、链接操作后,才能被计算机执行。简单来说,程序文件是一种用来实现特定功能的计算机代码和数据的存储形式。
数据文件:
数据文件是计算机中存储数据的一种文件形式,通常包含用于处理的结构化数据,如数字、文本、图像等。数据文件通常由多个记录组成,每个记录可以包含一个或多个字段,每个字段包含一个特定类型的数据。
在理解数据文件时,需要考虑以下几个方面:
-
数据格式:数据文件通常有特定的格式,如CSV、JSON、XML等。了解数据文件的格式可以帮助我们读取和处理其中的数据。
-
数据类型:数据文件中的数据可能包含不同的类型,如整数、浮点数、字符串、日期等。了解数据类型可以帮助我们进行适当的数据处理和分析。
-
数据结构:数据文件由多个记录组成,每个记录可以包含一个或多个字段。了解数据结构可以帮助我们理解数据的组织方式和如何访问其中的数据。
-
数据质量:数据文件中的数据可能存在错误、缺失或不一致等问题。了解数据质量可以帮助我们识别和纠正这些问题,以确保数据的准确性和可靠性。
总之,理解数据文件需要考虑数据格式、数据类型、数据结构和数据质量等方面,以便我们更好地使用和分析其中的数据。
文件名:
文件名是指用来标识电脑存储设备中的一个文件或目录的名称。在计算机系统中,每个文件都被命名为一个唯一的文件名,以便用户可以识别和访问它。文件名通常由字母、数字、下划线和点号等字符组成,不同计算机操作系统的文件名命名规则可能会有所不同。在同一文件夹中,文件名必须唯一,否则会出现命名冲突。
文件的打开和关闭
文件指针:
文件指针是一个指向文件流的指针。通过文件指针,我们可以在文件中移动读写位置,访问文件的不同部分。文件指针通常用于对文件进行输入输出操作。在C语言中,文件指针类型为FILE*,它是一个结构体指针,包含了文件的描述信息,例如文件名、文件读写位置、文件状态等。常用的文件指针操作包括打开文件、关闭文件、读写文件等。
缓冲文件系统中,关键的概念是“文件类型指针”,即“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区。
这些信息区用来存放文件的相关信息(如文件名,文件状态及当前位置)
这些信息是保存在一个结构体变量中。该结构体类型是由系统声明的,取名为FILE。
每当打开一个文件时,系统就会根据文件情况自动创建一个FILE结构的变量,并填充其中的信息。
struct _iobuf {char *_ptr;int _cnt;char *_base;int _flag;int _file;int _charbuf;int _bufsiz;char *_tmpfname;};
typedef struct _iobuf FILE;
所以我们一般通过文件指针变量找到与它相关联的文件
FILE* pf;//文件指针变量
流程:
我们在程序中实现文件打开和关闭,一般按照三步骤:
打开文件 读/写文件 关闭文件
我们在打开文件时,又不得不引入如下的函数:
FIEL* fopen(const char* filename, const char* mode)
其中的参数
filename————文件名。
mode—————打开方式。
以下是打开方式的各个操作符:
下面就是使用文件的三步骤例子:
#include <stdio.h>
int main ()
{FILE * pFile;//打开文件pFile = fopen ("myfile.txt","w");//文件操作if (pFile!=NULL){fputs ("fopen example",pFile);//关闭文件fclose (pFile);}return 0;
}
文件路径:
文件路径指定到文件或文件夹的位置的描述。它是由一系列文件夹的名称、符号和文件名组成的。在操作系统中,文件路径可以是绝对路径或相对路径。
- 绝对路径:指定文件或文件夹的完整路径,从根目录(Windows中通常是C:\)开始,一直到文件或文件夹所在的位置。例如:
- Windows:C:\Users\MyName\Desktop\file.txt
- MacOS或Linux:/Users/MyName/Desktop/file.txt
- 相对路径:指定文件或文件夹的位置相对于当前工作目录。例如:
- 如果当前工作目录是C:\Users\MyName\Desktop,则相对路径为file.txt。
- 如果当前工作目录是/Users/MyName/Desktop,则相对路径为file.txt。
在指定路径时,可以使用一些特殊符号来描述路径。如:
- .:当前目录
- ..:上级目录
- /或\:文件夹的分隔符号(Unix系统使用/,Windows系统使用\)
- ~:用户主目录
文件的顺序读写:
文件的顺序读写是指按照文件中数据的顺序依次读取或写入数据。在进行文件读写时,操作系统会维护一个文件指针,用于指示当前读写的位置。初始时,文件指针指向文件的开头。
顺序读写的过程如下:
-
打开文件,获取文件句柄。
-
从文件句柄中读取数据,操作系统会自动将文件指针指向下一个数据的位置。
-
继续读取数据,直到读取到文件末尾。
顺序写入的过程如下:
-
打开文件,获取文件句柄。
-
向文件句柄中写入数据,操作系统会自动将文件指针指向下一个数据的位置。
-
继续写入数据,直到写入完所有数据。
顺序读写可以提高文件读写的效率,因为每次读写时都能够连续地访问相邻的数据,避免了频繁地寻找数据的过程。
接下来我们就来实现文件的读写
首先我们来认识几个函数:
实现字符输入输出:
fputc:
fgetc是C语言中的标准输入输出库函数,用于从指定的文件指针中读取一个字符。其原型为:
int fputc(int character, FILE *stream);
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>int main() {FILE* pf = fopen("test.txt", "w");if (pf == NULL) {perror("fopen");return 1;}//写文件char ch = 0;for (ch = 'a'; ch <= 'Z'; ch++){fputc(ch, pf);}fclose(pf);pf = NULL;return 0;
}
上述代码会读取名为"test.txt"的文件中的所有内容,并将26个英文字母存放在文件“test.txt”中。
此时我们到当前程序的目录底下就会发现:
此时该文件就存放了a——z这26个字母。
fgetc:
fgetc是C语言中的标准输入输出库函数,用于从指定的文件指针中读取一个字符。其原型为:
int fgetc(FILE *stream);
其中,stream为指向文件的指针。函数返回值为读取的字符的ASCII码值,如果已经到达文件末尾,则返回EOF。
使用fgetc可以逐个字符地读取文件,例如:
#include <stdio.h>int main() {FILE *fp;int c;fp = fopen("test.txt", "r");if (fp == NULL) {printf("Failed to open the file.\n");return 1;}while ((c = fgetc(fp)) != EOF) {putchar(c);}fclose(fp);return 0;
}
上述代码会读取名为"test.txt"的文件中的所有内容,并输出到标准输出。
在这两个函数中,我们多次提到“流”这个概念,接下来我们就来介绍介绍“流”。
“流”
“流”是一个高度抽象的概念!目前我们可以理解为“中转站”。
流(stream)通常是指某个持续的、连续的、有序的数据序列,可以是数据源(例如文件、网络连接等)中的数据流,也可以是程序中的内存流(例如输入输出流、网络流等)。流的概念主要用于表示处理数据的可迭代性,它使得程序能够在处理数据的同时,将数据逐个读取或写入,而不需要一次性读取或写入整个数据集,从而大大提高了程序的效率和可扩展性。流可以分为输入流和输出流,输入流用于从数据源读取数据,输出流用于向目标输出数据。流也可以通过缓存机制来提高读写性能,即先将数据先读取到缓存中,再从缓存中进行读写操作。
我们对于刚刚讲的函数中,我们可以总结出如图:
我们也可以利用我们学过的知识来解释:
只要C语言程序运行起来,就默认打开了3个流:
1.标准输入流——stdin
2.标准输出流——stdout
3.标准错误流——stderr
实现文本行输入输出:
fputs:
fputs是一个C标准库函数,用于将字符串写入文件中。它的原型为:
int fputs(const char *str, FILE *stream);
其中,str是待写入的字符串,stream是目标文件指针。
函数会将字符串写入文件中,并返回一个非负整数表示操作成功。如果出现错误,函数会返回EOF。与puts函数不同的是,fputs不会在字符串末尾加上一个换行符。如果需要换行符,需要手动在字符串中添加一个"\n"字符。
fgets:
fgets()是C编程语言中的一个函数,用于从文件或控制台输入中读取字符串。它是C中标准输入/输出库(stdio.h)的一部分,通常用于从控制台中读取用户的输入。
fgets()函数的语法是:
char *fgets(char *str, int n, FILE *stream);
str是指向存储字符串的字符数组的指针。
n是要读取的最大字符数。
stream是指向要读取的文件或stdin(标准输入)的指针。
该函数最多从流所指向的文件中读取n-1个字符,直到遇到文件结束符(EOF)或换行符。生成的字符串存储在str所指向的缓冲区中,并在字符串末尾添加一个空字符\0。
fgets()在成功时返回相同的缓冲区str,在错误时或在读取任何字符之前到达文件结束时返回NULL。
例:
int main()
{FILE* pf = fopen("test.txt", "r");char arr[100] = { 0 };fgets(arr, 100, pf);printf("%s\n", arr);fclose(pf);pf = NULL;return 0;
}
运行结果如图:
若我们在文件中写:
两行数据,此时输出结果为:
这更好的说明了fgets一次只能读取一行。
实现格式化输入输出函数:
fprintf:
fprintf是C编程语言中的一个函数,用于将格式化的输出输出到文件流或控制台。它允许程序员以特定的方式输出格式化的数据,如字符串、数字和其他变量。该函数接受一个格式字符串作为其第一个参数,后跟一个参数列表,这些参数对应于格式字符串中的格式说明符。格式字符串包含要打印的值的占位符,格式字符串后面的参数提供要打印的相应值。下面是一个如何使用fprintf函数的示例:
int main() {FILE *fp;fp = fopen("test.txt", "w"); // 打开文件int num1 = 10, num2 = 20;float pi = 3.14;// 写文件fprintf(fp, "num1 = %d, num2 = %d\n", num1, num2);fprintf(fp, "pi = %f\n", pi);fclose(fp); // 关闭文件fp = NULL;return 0;
}
在本例中,fprintf函数用于将格式化的输出写入文件流。格式字符串包含变量num1、num2和pi值的占位符,它们作为函数的参数提供。输出被写入由file指针fp指定的文件。printf函数还用于将格式化的输出写入控制台。输出类似于写入文件的输出。最后,使用fclose关闭文件。
文件如图所示:
fscanf:
fscanf()是一个C库函数,用于从文件或输入流中读取数据。它类似于标准输入函数scanf(),但它不是从标准输入读取,而是从指定的文件流读取。
fscanf()的语法是:
int fscanf(FILE *stream, const char *format, ...)
第一个参数是要读取的文件流,第二个参数是指定预期输入格式的字符串,其余参数是指向将接收输入值的变量的指针。
例如,下面的代码从一个名为"test.txt"的文件中读取文件中的数值,并打印到屏幕上:
int main() {FILE *fp;fp = fopen("test.txt", "r"); // 打开文件//读文件fscanf(fp, "num1 = %d, num2 = %d\n", &num1, &num2);fscanf(fp, "pi = %f\n", &pi);printf("num1 = %d, num2 = %d\n", num1, num2);printf("pi = %f\n", pi);fclose(fp); // 关闭文件fp = NULL;return 0;
}
请注意,与scanf()一样,fscanf()返回成功匹配和分配的输入项数,可用于错误检查。
二进制的输入输出:
fwrite:
fwrite是C编程语言中的一个函数,用于将数据块写入文件。它有四个参数:指向要写入的数据的指针、要写入的每个元素的大小、要写入的元素的数量,以及指向要写入的文件的指针。
下面是一个使用fwrite将整数写入文件的示例:
#include <stdio.h>int main() {int num = 42;//打开文件FILE *file = fopen("file.bin", "wb");//写文件fwrite(&num, sizeof(int), 1, file);//关闭文件fclose(file);file = NULL;return 0;
}
在本例中,整数num被写入名为file.bin的二进制文件中。调用fwrite函数时带有一个指向num的指针、一个整数的大小(sizeof(int))、要写入的整数的数量(在本例中为1)和一个指向文件的指针。最后,使用fclose关闭文件。
此时文件中的数据为:
fread:
fread是一个C库函数,用于将二进制数据从文件读入缓冲区。它接受四个参数:
指向将存储数据的缓冲区的指针。
要读取的一个元素的大小。
要读取的元素数目。
要读取的文件的文件指针。
函数返回从文件中成功读取的元素总数。
例:
int main() {int num = 42;int arr[10] = { 0 };//打开文件FILE *file = fopen("file.txt", "rb");//写文件//fwrite(&num, sizeof(int), 1, file);//读文件fread(arr, sizeof(int), 1, file);printf("%d\n", arr[0]);//关闭文件fclose(file);file = NULL;return 0;
}
输出结果为:
文件的随机读写:
fseek:根据文件指针的位置和偏移量来定位文件指针
fseek函数是C标准库中的一个函数,用于设置文件指针的位置。其函数原型为:
int fseek(FILE *stream, long offset, int whence);
其中,stream是指向文件的指针,offset是偏移量,whence指示偏移量相对位置,它的取值及含义如下:
- SEEK_SET:偏移量相对文件开头,值为0。
- SEEK_CUR:偏移量相对文件指针的当前位置,值为1。
- SEEK_END:偏移量相对文件结尾,值为2。
fseek函数返回0表示成功,非0值表示出错。
使用fseek函数可以对文件进行定位,从而读写文件的指定位置。
例子:
#include <stdio.h>
int main ()
{FILE * pFile;pFile = fopen ( "example.txt" , "wb" );fputs ( "This is an apple." , pFile );fseek ( pFile , 9 , SEEK_SET );fputs ( " sam" , pFile );fclose ( pFile );return 0;
}
文件内容为:
ftell:返回文件指针相对于起始位置的偏移量
ftell
函数是一个C标准库函数,用于获取当前文件流(FILE对象)的当前文件位置指针的位置。该函数的原型为:
long int ftell(FILE *stream);
其中,stream
为文件流指针,表示当前要进行操作的文件流。ftell
函数的返回值为当前文件位置指针的位置,类型为long int
。
ftell
函数主要用于二进制文件的读写操作中。在进行读写操作前,我们需要先打开一个文件流并设置当前文件位置指针的位置。读写操作完成后,我们需要获取当前文件位置指针的位置,以便下次操作。
例如:
FILE* fp = fopen("test.bin", "rb");
if (fp != NULL) {fseek(fp, 0, SEEK_END); // 将文件位置指针设置到文件末尾long int file_size = ftell(fp); // 获取文件大小...fclose(fp);
}
在上述代码中,我们打开了一个二进制文件test.bin
,将文件位置指针设置到文件末尾,然后使用ftell
函数获取了文件大小。最后关闭了文件流。
rewind:让文件指针的位置回到起始位置
rewind()
是一个标准 C 库函数,可以将文件指针指向文件的开头。它的原型为:
void rewind(FILE *stream);
rewind()
函数将文件位置指针指向文件开头,这意味着接下来的所有读取操作将从文件开始处开始。 它不会关闭文件,也不会清除错误标志或重置文件模式。rewind()
函数主要用于需要多次读取文件的情况,例如读取二进制文件,可以使用它来回到文件的开头位置。
scanf/printf的理解
scanf是格式化的输入函数,针对的是标准输入流(键盘)
printf是格式化的输出函数,针对的是标准输出流(屏幕)
scanf和printf是针对标准输入/输出流的格式化输入/输出函数
fscanf/fprintf的理解
fscanf是针对所以输入流(文件流,输入流)的格式化输入函数
spirntf是针对所以输出流(文件流,输出流)的格式化输出函数
sscanf/sprintf的理解
sscanf是将字符串转换成格式化的数据
sprintf是将格式化的数据转化成字符串
文本文件和二进制文件
数据在内存中以二级制的形式存储,如果不加转换的输出到外村,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
一个数据在内存中是怎么存储的呢? 字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。 如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而 二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。
例子:
int main()
{int a = 10000;FILE* pf = fopen("test.txt", "wb");fwrite(&a, 4, 1, pf);//二进制的形式写到文件中fclose(pf);pf = NULL;return 0;
}
写完文件后将文件拖动到VS中
并且以二进制的方式打开:
得到结果 10 27 00 00
又因为机器是小端存储,则可以理解为:
所以才会输出:
文件的读取结束和判定
被错误使用的feof:
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。 而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets ) 例如: fgetc 判断是否为 EOF . fgets 判断返回值是否为 NULL .
2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。 例如: fread判断返回值是否小于实际要读的个数。
fgetc判断是否为EOF
fgets判断返回值是否为NULL
fread要求读取count个大小为size字节的数据
如果真的读取到了count,返回count。
如果小于则返回真是读到了的个数。
例子:
《完成文件的拷贝》
int main()
{FILE* pfread = fopen("data1.txt", "r");if (pfread == NULL){perror("fopen");return 1;}FILE* pfwrite = fopen("data2.txt", "w");if (pfwrite == NULL){perror("fopen");fclose(pfread);pfread = NULL;return 1;}int ch = 0;while ((ch = fgetc(pfread)) != EOF){fputc(ch, pfwrite);}fclose(pfread);pfread = NULL;fclose(pfwrite);pfwrite = NULL;return 0;
}
我们在执行代码之前,先编写一个data1.txt的文件,里面写上abcd
此时执行代码
打开data2.txt
代码执行成功!
文件缓冲区
文件缓冲区是指在处理文件时系统为文件I/O操作所分配的一段内存空间。当我们打开一个文件并执行读写操作时,操作系统会将文件的一部分或全部内容读入文件缓冲区中进行处理,因为读写操作比较耗时,通过文件缓冲区可以提高文件I/O操作的效率。当我们完成读写操作后,系统将缓冲区中的数据写回到磁盘中。因此,文件缓冲区可以有效地减少磁盘I/O操作的次数,从而提高系统的性能。
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序 中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装 满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓 冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根 据C编译系统决定的。
正因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在程序结束时主动关闭文件。
总结:
本文我们介绍了C语言实现文件操作,我们在此讲解了很多函数,希望大家在看完文章后可以动手尝试编写编写代码,自己建立再销毁一个文件,并对其中的内容进行修改和增加达到自定义程度。
刚开始会对这些函数产生疑惑甚至混淆概念。但只要每天动手尝试尝试编写函数实现文件的操作,相信很快就会上手这些函数。
记住!“坐而言不如起而行!”
Action speak louder than words!
本章代码可以访问我的Gitee仓库:test_c_with_X1: 本仓库里的代码为c语言的测试代码 - Gitee.com
接下来我们会利用文件修改通讯录。
可以再复习以下关于通讯录的创建
blog如下:
运用动态内存实现通讯录(增删查改+排序)-CSDN博客