说到文件操作,大家会第一印象想到不就是电脑硬盘中创建文件,写入数据吗,键盘、鼠标就可以搞定,那么接下来我要告诉你的是C语言也可以实现文件操作哦!!!
目录
前言
一、为什么要使用文件操作
二、文件
2.1 程序文件
2.2数据文件
2.3 文件名
三、文件操作
3.1 文件打开和关闭
1. 文件指针
2. 文件的打开和关闭
3.2 文件的顺序读写
3.3 函数使用
1. fgetc和fputc
2. fgets和fputs
3. fscanf和fprintf
4. fread和fwrite
5. 对比一组函数
3.4 文件的随机读写
1. fseek
2.ftell
3.rewind
3.5 文本文件和二进制文件
3.6文件读取结束的判定
总结
前言
首先、我们要实现的文件操作,不只是简单的打开和关闭,写入数据,我们也可以从相对应文件中,读取数据,到程序中,使得程序能顺利运行,并对传来的数据进行处理使用!
一、为什么要使用文件操作
我们在前文中,实现了静态、动态通讯录,但是我们也发现了,只要程序关闭,那么添加的通讯录的信息数据,也就一起消失,我们下次打开的时候,通讯录是空的,那么这不太符合我们实际所需。我们需要的是,可以利用、可以存储、可以保存的通讯录,那么我们就必须了解一下,文件操作。
文件操作:使用文件操作我们可以将数据直接存放到电脑的硬盘上,做到数据的持久化
二、文件
磁盘上的文件就是文件。
在程序设计中,我们一般说的文件有两种,分别是程序文件、数据文件(从文件功能的角度进行分类)。
2.1 程序文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
2.2数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
由上文对于两种文件的介绍,我们可知,我们需要了解的是数据文件,本文就主要讲解一下,如何处理数据文件,并对该文件进行操作
2.3 文件名
文件名是一个文件的唯一的文件标识,以便于用户识别和引用。
比如:D:\new.txt 这是D盘下的文件名为new的文本文档
三、文件操作
我们之前在写C语言程序的时候,都是通过键盘和屏幕,进行输入和输出数据,我们这一次要将数据输入和读取数据都是于硬盘有关。
3.1 文件打开和关闭
我们首先要了解的是我们之前一直所处理的,输入输出数据都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。
如图:
文件操作需要的是,以硬盘中的文件为数据输入输出对象。
如图:
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,规定的文件的 所有信息,都是可以存储在上面
不同编译器不同的内容,但是都是可以存放各种文件类型的结构体FILE,正常使用即可,不用深究
每次打开一个文件,系统就会根据文件的情况自动创建一个FILE结构的变量,并填充信息,一般都是通过一个FILE指针来维护这个FILE结构的变量,方便使用
如图:
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
2. 文件的打开和关闭
文件在使用前应该先打开,之后再关闭,我们先认识一下C语言给我们提供的文件开关函数
第一个fopen来打开文件,第二个fclose来关闭文件
代码如下:
//打开文件
FILE * fopen ( const char * filename, const char * mode );//返回文件类型
//关闭文件
int fclose ( FILE * stream );//返回整型
打开方式:
1.r表示读 w表示写 a表示追加(即不会覆盖原有数据)
2.带b,标识的是打开二进制文件
3. +号表示读写,如果是r+的话,且没有指定文件,那么会报错,但是w+、a+若没有指定文件,会创建一个指定文件,再输入数据(写文件)
代码演示:
3.2 文件的顺序读写
什么叫做顺序读写呢,意思就是,在读取/写文件的时候,读取一个或者写一个内容进去,光标就会向后移动一位,有顺序的读取和写入
如图:
通过fgetc和fputc函数解释了,文件确实是按照顺序读写的
3.3 函数使用
输入流和输出流介绍:
流的概念:
上面函数使用于所有输入流或文件,这是什么意思?
图示解释如下:
1. fgetc和fputc
上文图示用到这两个函数
代码如下:
//这是fgetc函数,可以理解为得到文件中的一个字符
int main()
{
FILE* pf = fopen("test.txt", "r");
for (int i = 0; i < 10; i++) {
char src = fgetc(pf);
printf("%c", src);
}
fclose(pf);
pf = NULL;
return 0;
}
//这是fputc函数,可以理解为放置文件中一个字符
int main()
{
FILE* pf = fopen("test.txt", "w");
for (int i = 0; i < 26; i++) {
fputc('a' + i, pf);
}
fclose(pf);
pf = NULL;
return 0;
}
实际上,fgetc和fputc函数就是得到或放置一个字符在文件中,返回值为int
2. fgets和fputs
fgets和fputs,可以理解为得到或者放置一个字符串在文件中。
还有一个特点,如图所示:
fgets如果没有读取到内容,返回NULL
代码如下:
//fgets函数
int main()
{
FILE* pf = fopen("test.txt", "r");
char arr[20];
while (fgets(arr, 20, pf) != NULL) {
printf("%s", arr);
}
fclose(pf);
pf = NULL;
return 0;
}
//fputs函数
int main()
{
FILE* pf = fopen("test.txt", "w");
char arr[20];
fputs("hello world!!\n", pf);
fputs("hello why\n", pf);
fputs("hello fzx\n", pf);
fclose(pf);
pf = NULL;
return 0;
}
3. fscanf和fprintf
实际上和scanf、printf差不多的形式,只是多一个参数 FILE*stream
如图:
如果fscanf读取的时候没有数据,那么也不会报错,就认为是没有读取吧
代码如图所示:
struct S {
char name[20];
int age;
double score;
};
//fscanf格式化读取pf流中的数据,赋值给后面的参数,和scanf用法差不多,
//只是多一个FILE*类型,后面于scanf用法一致,改用&就用
int main()
{
FILE* pf = fopen("test.txt", "r+");
//读写都行
//格式化输出
//struct S s = { "why",19,100 };
struct S s = {0};
struct S s1 = { 0 };
fscanf(pf, "%s %d %lf\n", s.name, &(s.age), &(s.score));
printf("%s %d %lf\n", s.name, (s.age), (s.score));
//fscanf(pf, "%s %d %lf\n", s1.name, &(s1.age), &(s1.score));
//printf("%s %d %lf\n", s1.name, (s1.age), (s1.score));
int a = 10;
/*fscanf(pf, "%d", a);
printf("%d\n", a);*/ //这是进行测试如果没有相应的数据读取的时候,不会报错,只是相当于没有这一行代码,不会发生任何改变(对相应a)
fclose(pf);
pf = NULL;
return 0;
}
//这是fprintf函数的使用
int main()
{
FILE* pf = fopen("test.txt", "r+");
//读写都行
//格式化输出
struct S s = { "why",19,100 };
fprintf(pf, "%s %d %lf\n", s.name, (s.age), (s.score));
//printf("%s %d %lf\n", s.name, (s.age), (s.score));
fclose(pf);
pf = NULL;
return 0;
}
1.fscanf 格式化读取pf流中的数据,赋值给后面的参数,和 scanf 用法差不多,只是多一个FILE*类型,后面于 scanf 用法一致,该用&就用
2.fprintf 格式化写入文件中数据,上面代码中,将结构体变量s,数据,写入pf管理的文件中
3.fscanf 如果没有相应的数据读取的时候,不会报错,只是相当于没有这一行代码,不会发生任何改变(对相应a)
4. fread和fwrite
只能接收文件流,二进制文件,可能会有乱码产生
如图分析:
代码演示:
struct S {
char name[20];
int age;
double score;
};
//先写入二进制文件 fwrite
int main()
{
struct S s = { "fzx",18,100 };
FILE* pf = fopen("test.txt", "wb");
//写一个二进制文件
fwrite(&s, sizeof(struct S), 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
//再读取二进制文件 fread
int main()
{
struct S s = { 0 };
FILE* pf = fopen("test.txt", "rb");
//读取文件
fread(&s, sizeof(struct S), 1, pf);
printf("%s %d %lf\n", s.name, s.age, s.score);
return 0;
}
5. 对比一组函数
scanf / fscanf / sscanf
printf / fprintf /sprintf
前面四种大家都认识了,那么sscanf和sprintf是什么呢?
如图所示:
3.4 文件的随机读写
1. fseek
根据文件指针的位置和偏移量来定位文件指针。(改变光标位置)
代码演示:
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;
}
2.ftell
返回文件指针相对于起始位置的偏移量
代码演示:
int main()
{
FILE* pFile;
pFile = fopen("example.txt", "wb");
int num = ftell(pFile);
printf("%d ", num);
return 0;
}
3.rewind
让文件指针的位置回到文件的起始位置
图文演示:
代码演示:
int main()
{
FILE* pf = fopen("test.txt", "w");
for (int i = 0; i < 10; i++) {
fputc('a' + i, pf);
}
int num = ftell(pf);
printf("%d \n", num);
rewind(pf);
printf("%d \n", ftell(pf));
fclose(pf);
pf = NULL;
return 0;
}
3.5 文本文件和二进制文件
1.根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
2.数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
3.如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储 的文件就是文本文件。
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
测试文本文件和二进制文件代码:
int main()
{
int a = 10000;
FILE* pf = fopen("test.txt", "wb");
fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
fclose(pf);
pf = NULL;
return 0;
}
3.6文件读取结束的判定
在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束,而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
1.文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
fgetc 判断是否为 EOF .
fgets 判断返回值是否为 NULL2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
fread判断返回值是否小于实际要读的个数
图示:
代码演示:
//文本文件
int main(void)
{
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if(!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((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);
}
总结
主要讲解文件操作的的一些函数,fopen、fclose、fgets、fwrite等等函数的介绍,以及对于文件的分类、输入和输出流的分析,以及更多文件操作的函数图示分解。
下文我们将使用文件操作,升级通讯录。