目录
前言
文件这个名词就好像是一个熟悉的“陌生人”,它好像经常出现在我们的生活中,但又没有真正的了解过。例如:密封档案中的绝密文件,个人信息的复印文件,计算机磁盘上的文件等等。
我们今天要谈论的是在C语言程序设计中关于文件的操作,在程序设计当中,我们口中的文件一般有两种:程序文件和数据文件。
当程序运行,如果需要从文件中读取数据, 或者输出数据时。这些内容就存储在数据文件中。数据文件是本次学习的内容。
初识文件
文件名:文件名是一个文件的唯一标识。
文件名的组成:文件路径+文件名主干+文件后缀。
例如:D:\code\gitee\test.txt。
D:\code\gitee\是文件路径,test是文件名主干,.txt是文件后缀。
既然说到了文件路径,那么接下来就要分清楚相对路径和绝对路径两兄弟。
相对路径:相对于当前文件下的路径。
绝对路径:完整的描述文件位置的路径,从根目录开始,文件在磁盘上的路径。
文件的打开和关闭
每当我们需要完成一件事情,都需要一定的步骤。例如:把大象关进冰箱,首先要打开冰箱门,其次要把大象装进冰箱,最后关上冰箱门。
文件操作也是一样的道理,无论我们写入或者读取什么样的数据,打开和关闭文件都是不可或缺的重要步骤。
当打开一个文件时,系统会根据文件的情况自动创建一个 FILE结构的变量,并填充其中的信息, 当然在这期间发生的事情我们不必关心。我们只需要知道,通过一个 FILE类型 的指针就可以维护这个 FILE 结构的变量。FILE* pf;//文件指针变量
当定义好文件指针变量后,就可以通过文件指针变量找到与它关联 的文件。 了解这些后,接下来学习使用两个函数,ANSI C(由美国国家标准协会(ANSI)及国际标准化组织(ISO)推出的关于C语言的标准) 规定使用fopen函数来打开文件,fclose函数来关闭文件。
fopen:
返回值:打开文件成功则返回一个指向FILE对象的指针,否则返回空指针。类似于使用动态内存开辟函数,当用fopen函数打开文件时,应该判断是否返回NULL。
参数:要打开的文件;文件的使用方式
头文件:#include <stdio.h>
flose:
参数:指向FILE对象的指针;
返回值:如果关闭成功返回0,返回失败时返回EOF。
头文件:#include <stdio.h>
使用这两个函数,看下效果:
#include <stdio.h> int main() { //打开文件 FILE* pf = fopen("text.txt","w"); if (pf == NULL) { perror("fopen"); return; } //文件操作 //关闭文件 fclose(pf); pf = NULL; return 0; }
找到这个程序的路径
运行程序后:
通过对比不难发现,程序运行后,因为要写的文件不存在,所以建立了一个新的text.txt的文本文件。
文件的顺序读写
刚刚在上面谈到过,文件操作的第2步就是对文件进行一些读、写等操作。在学习文件操作前处理数据的输入输出都是以终端为对象的,从终端的键盘输入数据,运行结果显示到显
示器上。接下来学习一些函数对文件进行一系列操作,通过这些函数可以把数据在数据文件中操作。fputc:
功能:将内容写入文件中
参数:要写入的内容,指向FILE对象的指针。
应用:把26个字母写入文件中
#include <stdio.h> int main() { //打开文件 FILE* pf = fopen("text.txt","w"); if (pf == NULL) { perror("fopen"); return; } //文件操作 for (int i = 0; i < 26; i++) { fputc('a' + i, pf); } //关闭文件 fclose(pf); pf = NULL; return 0; }
fgetc:
功能:从指定文件中读取数据
参数:指向FILE对象的指针
练习:读取指定文件中的内容,因为我知道文件的内容,所以使用了for循环,也可使用while循环。重点在于练习fgetc函数的使用。
int main() { //打开文件 FILE* pf = fopen("text.txt", "r"); if (pf == NULL) { perror("fopen"); return; } //文件操作 for (int i = 0; i < 26; i++) { int ch = fgetc(pf); printf("%c ", ch); } //关闭文件 fclose(pf); pf = NULL; return 0; }
fgetc和fputc:
#include <stdio.h> int main() { int ch = fgetc(stdin); fputc(ch, stdout); return 0; }
fputs:
功能:写入字符串
参数:写入str指向的字符串,指向FILE对象的指针
int main() { //打开文件 FILE* pf = fopen("text.txt", "w"); if (pf == NULL) { perror("fopen"); return; } //文件操作 fputs("Hello ",pf); fputs("Word!",pf); //关闭文件 fclose(pf); pf = NULL; return 0; }
fgets:
功能:从指定文件中读取字符串
参数:读取的字符串存储到str中,读取 (num-1) 个字符或到达换行符或文件结尾,指定的FILE对象。
int main() { //打开文件 FILE* pf = fopen("text.txt", "r"); if (pf == NULL) { perror("fopen"); return; } //文件操作 char str[20] = { 0 }; fgets(str,15,pf); printf("%s",str); //关闭文件 fclose(pf); pf = NULL; return 0; }
fwrite
功能 :以二进制的方式把数据写入文件
参数:指向(要写入元素数组)的指针,元素的大小,元素的个数,指向FILE对象的指针
struct str { char name[20]; int age; char addr[20]; }; int main() { //打开文件 FILE* pf = fopen("text.txt", "w"); if (pf == NULL) { perror("fopen"); return; } //文件操作 //以2进制的方式写入文件 struct str s = {"张西洋",18,"内蒙"}; fwrite(&s,sizeof(s),1,pf); //关闭文件 fclose(pf); pf = NULL; return 0; }
通过观察发现,以二进制的方式写入,打开这个文本文件时,部分数据好像是这个文件和计算机间的加密暗语,我们无法从中读取有效信息。
fread:
功能:以二进制的方式读取文件中的数据
参数:指向一块空间的指针,元素大小,元素个数,指向FILE对象的指针
struct str { char name[20]; int age; char addr[20]; }; int main() { //打开文件 FILE* pf = fopen("text.txt", "r"); if (pf == NULL) { perror("fopen"); return; } //文件操作 //以2进制的方式读取数据 struct str s = { "张西洋",18,"内蒙" }; fread(&s, sizeof(s), 1, pf); printf("%s %d %s\n", s.name, s.age, s.addr); //关闭文件 fclose(pf); pf = NULL; return 0; }
学习了这几个函数,回想下之前写过的通讯录,每次退出程序,联系人并没有保存,这显然是不符合我们的需求,那么可不可以运用本篇文章的知识来使这些数据保存起来呢?
这里只写下保存数据和读取数据的功能:
按照通讯录的结构,保存联系人应该是在退出通讯录时发生。读取联系人应该是初始化通讯录时,把信息加载到通讯录中。
1.退出时保存通讯录
//保存数据 void SaveContact(struct Contact* pc) { //打开文件 //wb读和写 FILE* pw = fopen("Datd.txt","wb"); if (pw == NULL) { perror("fopen"); return; } //写入数据 for (int i = 0; i < pc->sz; i++) { fwrite(pc->data+i,sizeof(struct PeoInfo),1,pw); } //关闭文件 fclose(pw); pw = NULL; }
保存数据成功
重新运行通讯录:通讯录没有数据
2.在次打开通讯录可以载入保存的数据,这个动作应该发生在初始化通讯录的过程中。
static int check_capacity(struct Contact* pc) { //联系人的存储数量和容量相等 if (pc->sz == pc->capacity) { //增容 struct PeoInfo* ptr = (struct PeoInfo*)realloc(pc->data, pc->capacity + increase_sz * sizeof(struct PeoInfo)); //判断 if (ptr == NULL) { perror("check_capacity"); return 0; } else { //增容成功 pc->data = ptr; pc->capacity = pc->capacity + increase_sz; printf("增容成功!\n"); return 1; } } else { return 1; } } void LoadContact(struct Contact* pc) { //打开文件 FILE* pr = fopen("Datd.txt","rb"); if (pr == NULL) { perror("LoadContact>fopen"); return; } //读取文件 struct PeoInfo tmp = {0}; while (fread(&tmp, sizeof(struct PeoInfo), 1, pr)) { //判断是否需要增容 check_capacity(pc); //载入数据 pc->data[pc->sz] = tmp; pc->sz++; } //关闭文件 fclose(pr); pr = NULL; } //通讯录初始化 void InitContact(struct Contact* pc) { //断言 assert(pc != NULL); //动态内存开辟 pc->data = (struct PeoInfo*)malloc(capacity_max*sizeof(struct PeoInfo)); //判断 if (pc->data == NULL) { perror("InitContact"); return; } pc->capacity = capacity_max; pc->sz = 0; //加载文件中的数据到通讯录中 LoadContact(pc); }
在次打开通讯录,之前保存的数据还在,载入成功。
这样就实现了数据的保存,你学“废”了吗?具体关于通讯录实现的代码在之前的文章中,有兴趣的小伙伴可以去了解一下。
函数对比
scanf/fscanf/sscanfprintf/fprintf/sprintf功能:按照一定的格式从键盘输入数据。功能:按照一定的格式把数据输出到屏幕上。
总结:适用于标准输入/输出流的格式化输入输出数据。
功能:按照一定的格式向输出流(文件/stdout)输出数据。
struct S { char name[20]; int age; float score; }; int main() { struct S s = { "张三", 20, 88.5f }; //把s中的数据写到文件中 FILE*pf = fopen("test.txt", "w"); if (NULL == pf) { perror("fopen"); return 1; } //写文件 fprintf(pf, "%s %d %.1f", s.name, s.age, s.score); fclose(pf); pf = NULL; return 0; }
功能:按照一定格式从输入流(文件/stdin)输入数据。
读取一个结构体数据 struct S { char name[20]; int age; float score; }; int main() { struct S s = {0}; //把s中的数据写到文件中 FILE* pf = fopen("test.txt", "r"); if (NULL == pf) { perror("fopen"); return 1; } //读文件 fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score)); printf("%s %d %f\n", s.name, s.age, s.score); fclose(pf); pf = NULL; return 0; }
总结:适用于所有输入/输出流的格式化输入输出数据。
对比:
//scanf(...) //fscanf(stdin,...) //printf //fprintf(stdout, ...)
功能:把格式化的数据按照一定的格式转化成字符串 。
参数:指向存储生成的 C 字符串的缓冲区的指针,一定的格式
功能:从字符串中按照一定的格式读取出格式化的数据。
参数: 字符串,一定的格式。
struct S { char name[10]; int age; float score; }; int main() { char buf[100] = {0}; struct S tmp = { 0 }; struct S s = { "lisi", 20, 95.5f }; //把结构体的数据,转换成字符串 sprintf(buf, "%s %d %f", s.name, s.age, s.score); //以字符串的形式打印 printf("%s\n", buf); //printf("%s %d %f\n", s.name, s.age, s.score); //将buf中的字符串,还原成一个结构体数据 sscanf(buf, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score)); //以结构的形式打印 printf("%s %d %f\n", tmp.name, tmp.age, tmp.score); return 0; }
文件的随机读写:
fseek:
功能:根据文件指针的位置和偏移量来定位文件指针。
参数:指向FILE的指针;二进制文件:要从原点偏移的字节数。文本文件:零,或 ftell 返回的值;用作偏移参考的位置
int main() { FILE* pf; pf = fopen("text.txt", "wb"); fputs("Hello Word", pf); fseek(pf, 9, SEEK_SET); fputs(" ***", pf); fclose(pf); return 0; }
ftell:
功能:返回文件指针相对于起始位置的偏移量
参数:指向FILE对象的指针
返回值:成功时,返回文件指针相对于起始位置的偏移量。失败时,返回 -1L,并将 errno 设置为系统特定的正值。
int main() { FILE* pFile; long size = 0; pFile = fopen("text.txt", "rb"); if (pFile == NULL) { perror("fopen"); } fseek(pFile, 0, SEEK_END); size = ftell(pFile); //关闭文件 fclose(pFile); printf("Size of text.txt: %ld bytes.\n", size); return 0; }
rewind:
功能:让文件指针的位置回到文件的起始位置
参数:指向FILE对象的指针
int main() { int n; FILE* pf; char buffer[27]; pf = fopen("text.txt", "w+"); for (n = 'A'; n <= 'Z'; n++) { fputc(n, pf); } //rewind(pf); //二进制输入 fread(buffer, 1, 26, pf); fclose(pf); buffer[26] = '\0'; printf("%s",buffer); return 0; }
未使用rewind函数。
使用rewind函数让文件指针的位置回到文件的起始位置。
文件读取结束的判定
feof:
功能:当文件读取结束时,判断是读取失败结束,还是遇到文件尾结束。参数:指向FILE对象的指针。1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )。例如:fgetc 判断是否为 EOF .fgets 判断返回值是否为 NULL .2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。例如:fread判断返回值是否小于实际要读的个数。
今天的分享就到这里,继续加油!