前言
之前编写通讯录程序时,由于数据都是保存在内存上,每次关闭通讯录后,都需要重新再输入信息。有没有办法将数据持久化的保存在硬盘中呢?这就涉及到文件操作的知识。本文将介绍文件操作相关函数的使用,并对之前写过的通讯录程序进行修改。
文件分类
从文件功能的角度来分类:
1. 程序文件
比如在用vs写代码时创建的源文件(.c),目标文件(windows环境后缀为.obj),执行文件(.exe)
2. 数据文件
比如程序运行需要从中读取数据的文件,或者需要写入数据进去的文件。
此前我所写的所有程序都是以终端为数据文件,如printf是在有需要时将数据打印在屏幕上,scanf是将键盘上的输入数据读取进程序文件中。现在不过是将终端变化为数据文件,这样就好理解多了。
文件名
文件名是唯一的文件标识,有了文件名才能准确找到文件的位置并进行操作。
文件名包含三部分:文件路径+文件名主干+文件后缀
比如:D:Study新建文本文档.txt
文件指针
进行文件操作时,我们需要一个桥梁沟通程序和我们希望进行操作的数据文件。这个桥梁就是文件指针FILE*
。
每个被使用的文件都会在内存中开辟一段文件信息区域,用于存放文件的相关信息,如文件名,文件的状态,当前的位置等。而FILE是编译器提供的一个结构体类型,文件信息区的内容被保存在FLIE类型的变量里。
1
2FILE* pf;//文件指针类型变量
文件操作函数
1. 文件的打开和关闭
文件打开,使用函数fopen,关闭使用fclose。
1
2
3
4
5//打开文件 FILE * fopen ( const char * filename, const char * mode ); //关闭文件 int fclose ( FILE * stream );
当fopen失败时,会返回空指针。因此最好加上判空。
其中,const char * mode
是指输入对文件操作的不同指令字符,即“打开方式”。
打开方式汇总如下表:
比如我要打开文件,并读取文件中数据,则:
1
2
3FILE* pf = fopen("test.txt","r");//用只读方式打开test.txt文件 FILE* pf = fopen("D:\Studytest.txt","r");//当使用文件路径进行访问时,需要在盘符后多加一个''进行转义
2. 文件的顺序读写
关于文件操作,我们可以使用以下函数:

顾名思义,fgetc和fputc是向文件输出从文件中读取字符的函数,c == char。当成功时,fgetc和fputc会返回其得到写入字符的ASC码。并将文件的位置标识符后移一位。
同理,fgets和fputs中,s == string。
表中函数各功能与返回值均有所不同,在此不一一介绍。
2.1 两组输入/输出函数的对比
为了更深入的理解各个函数的功能,接下来对scanf/fscanf/sscanf;printf/fprintf/sprintf这两组函数的异同之处进行比较测试。
其中,scanf、printf和fscanf、fprintf的区别在于,后两者引入了流。
以scanf和fscanf为例,查看函数定义可知:
scanf函数是从标准输入流——即键盘上读取数据的。
fscanf(stdin,"%d",&a);
和 scanf("%d",&a);
的效果相同,stdin就是标准输入流的意思。
如此一来fscanf的功能就很明确了——从一个输入流中读取数据到目标变量中。
用例如下:
先创建一个文件test.txt。将其内容编辑为15并保存退出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> void test() { int a = 0; //打开文件 FILE* pf = fopen("D:\test.txt", "r"); if (pf == NULL) { perror("fopen"); return; } //使用 fscanf(pf, "%d", &a);//将文件中信息读取进变量a中 printf("%d", a); //关闭文件 fclose(pf); pf = NULL; } int main() { test(); return 0; }
运行后我们就会惊喜的发现,屏幕上输出了15。

同理,fprintf的功能即为将a中的数据打印进目标文件中。用例如下:
先将test.txt中数据清除,再运行以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23void test() { int a = 15; //打开文件 FILE* pf = fopen("D:\test.txt", "w"); if (pf == NULL) { perror("fopen"); return; } //使用 fprintf(pf, "%d", a); //关闭 fclose(pf); pf = NULL; } int main() { test(); return 0; }
运行后打开文件,发现文件内容已被修改为15。
而sscanf和sprintf两个函数与上述的区别则在于,这两个函数并非从流中读取写入数据,而是从字符串中读取写入格式化的数据。
用例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27struct s { char name[10]; int age; double score; }; void test() { char arr[20] = { 0 }; struct s s1 = { "zhang",20,90.1 }; //使用sprintf,将结构体内容转化为字符串并存入字符数组。 sprintf(arr, "%s %d %lf", s1.name, s1.age, s1.score); //打印arr内容看是否成功存入 printf("%s", arr); //使用sscanf,从字符串中读取格式化数据 struct s s2 = { 0 }; sscanf(arr, "%s %d %lf", s2.name, &(s2.age), &(s2.score)); //打印s2看是否成功读取 printf("%s %d %lf", s2.name, s2.age, s2.score); } int main() { test(); return 0; }
运行程序后得:
说明从字符串读取写入的功能均测试成功。
3. 文件的随机读写
3.1 fseek()
此处介绍文件随机读写所用到的函数:fseek
查看函数定义可知,fseek是用来改变文件指针位置的函数。其第一个参数为文件指针,第二个参数为偏移量,第三个参数表示开始添加偏移 offset 的位置,可选择三个值:
- SEEK_SET 文件的开头
- SEEK_CUR 文件指针的当前位置
- SEEK_END 文件的末尾
当fseek()成功时,返回0,否则返回非零值。
比如,我们可以如此使用fseek:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29//文件随机读写 void test() { FILE* pf = fopen("D:\test.txt", "r+");//注意此处需要使用读写,r+和 w+都可以。 if (pf == NULL) { perror("fopen"); return; } fputs("Hello, Mike!", pf); fseek(pf, -5, SEEK_END);//从文件的末尾向前偏移5个字符 fputs("Jill!", pf); fseek(pf, 0, SEEK_SET); //这里也可使用rewind(pf),将文件指针位置回到文件起始位。 char arr[50] = { 0 }; fread(arr, 1, 12, pf);//还有一个'' printf("%sn", arr); fclose(pf); pf = NULL; } int main() { test(); return 0; }
运行结果为:
测试成功。
3.2 ftell()
ftell()函数功能为:告诉我们指定流的位置标识符当前位置是多少。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23void test() { FILE* pf = fopen("D:\test.txt", "r+"); if (pf == NULL) { perror("fopen"); return; } fputs("abcde", pf); fseek(pf, -3, SEEK_END);//从文件的末尾向前偏移3个字符 long int pos = ftell(pf); printf("%ld", pos); fclose(pf); pf = NULL; } int main() { test(); return 0; }
运行结果如下:
3.3 ferror()、feof() - 文件读取结束的判定
ferror()函数的功能,是通过其返回值判断,是否因文件读取时出错导致的文件读取结束。
feof()函数的功能,是通过其返回值来判断,是不是读取到文件末尾导致的结束。
ferror()的返回值:
feof()的返回值:
用例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31void test() { //事先在文件中存入abcde FILE* pf = fopen("D:\test.txt", "r"); if (pf == NULL) { perror("fopen"); return; } int c = 0; while ((c = fgetc(pf)) != EOF)//通过判断fgetc的返回值是否为EOF,确定文件是否读取结束 { //putchar(c);//打印在屏幕上 printf("%c", c); } printf("n"); //文件读取结束后的判定 if (ferror(pf)) { printf("pf流有错。n"); } if (feof(pf)) { printf("成功到达文件末尾。n"); } fclose(pf); pf = NULL; }
运行结果为:
对通讯录程序的修改
通过对文件操作的学习,现将通讯录程序修改为使用文件进行存储的版本。
此版本修改建立在之前发布在博客C/C++ 通讯录程序构思与实现中所展示的V1版本通讯录程序的基础上。
添加了两个函数功能:
save_contact(contact* pc)
:当在主界面选择0退出通讯录时,调用本函数,会将本次通讯录存储的内容写入到文件contact.dat中。load_contact(contact* pc)
:在程序开始时,创建通讯录结构体后,对通讯录结构体进行初始化,初始化后调用本函数,加载存储在contact.dat中的数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27void load_contact(contact* pc) { FILE* pf = fopen("contact.dat", "r"); if (pf == NULL) { perror("load_contact::fopen"); return; } while (fread(pc->contact + pc->sz, sizeof(peoinfo), 1, pf)) { pc->sz++; } fclose(pf); pf = NULL; } //初始化函数 void init_contact(contact* pc) { pc->sz = 0; memset(pc->contact, 0, Con_MAX * sizeof(peoinfo));//初始化为0 //读取通讯录 load_contact(pc); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22//保存通讯录到文件 void save_contact(contact* pc) { FILE* pf = fopen("contact.dat", "w"); if (pf == NULL) { perror("save_contact::fopen"); return; } //写文件 int i = 0; for (i = 0; i < pc->sz; i++) { fwrite(pc->contact + i, sizeof(peoinfo), 1, pf); } //关闭文件 fclose(pf); pf = NULL; }
经测试,第一次在通讯录程序中存储2个人信息后,关闭通讯录。再次打开时直接选择进行打印,得到结果如下:
测试成功。
总结
本文总结了我对文件管理相关知识的学习,以做记录。
最后
以上就是有魅力画板最近收集整理的关于C C语言文件操作前言文件分类文件名文件指针文件操作函数对通讯录程序的修改总结的全部内容,更多相关C内容请搜索靠谱客的其他文章。
发表评论 取消回复