目录
1.认识文件
1.1什么是文件
• 任何外部数据源都是文件(于操作系统而言)• 和运行的程序(内存数据)不同,文件中的数据是持久化的,断电也存在• 但本质仍是 数据 (二进制字节块/字节流)1.2 IO:IN & OUT
• 程序 = 数据 + 指令• 数据需要输入 input 和输出 output1.3从标准IO到文件IO
• 我们熟悉使用 printf 和 scanf 等 库函数 用以控制台IO• 控制台本质也是一个外部数据源,换言之文件IO和控制台IO是类似的• 即学习文件IO就是学习一系列 C语言文件操作 的函数的用法/效果#include <stdio.h> scanf(“%d”, &a); printf(“%d”, a);
#include <stdio.h> FILE *fp = fopen("data.txt","w+");//文件打开 if ( NULL == fp ) { // 省略... }//文件检查 fscanf( fp,"%d", &a);//从文件读 fprintf( fp,"%d", a);//往文件写 fclose(fp)//文件关闭
2.文本文件IO
2.1文件打开与检查: fopen
• 文件是外存上的实体,在C程序中用一个结构体指针(FILE*)表示它• 类比:同学们(文件)把学生卡(文件指针)都交给老师进行抽奖(而不是把每个学生复制一遍放进盒子里,意思是文件不可能加载到程序内存中,因为太大了,用一个指针进行访问文件,相当于用学生卡或者代表学生信息物件充当指针从而进行访问)#include <stdio.h> // ... FILE *fp = fopen("data.txt","w+"); if ( NULL == fp ) { return 0; }
• 函数 fopen(文件路径, 打开模式) 返回该路径的文件指针!文件路径, 打开模式都是 字符串,所以要用双引号• 由于目录权限、占用等原因, fopen 不一定成功• 因此在通过文件指针读写文件之前要 检查 其不为空指针• NULL 是C语言关键字常量,本质是整数0,表示空指针2.2文件路径
• 相对路径:以 程序执行路径 为基准的路径• 绝对路径:以操作系统 根目录 为基准的路径FILE *fp = fopen("..\\data.txt","w+"); C: /Code data.txt /bin a.exe c盘code下有两个文件,一个data.txt一个bin文件夹 ./ 表示当前目录 ../ 表示相对当前路径的上一级目录
\表示转义字符,\\在转义字符\前面再加一个\才表示\本身的含义FILE *fp = fopen("..\\files\\data.txt","w+"); C: /Code /files data.txt /bin a.exe
FILE *fp = fopen(".\\data.txt","w+"); FILE *fp = fopen("data.txt","w+"); C: /Code data.txt a.exe .\\ 表示当前目录 ..\\ 表示相对当前路径的上一级目录
FILE *fp = fopen("C:\\Code\\files\\data.txt","w+"); C: /Code /files data.txt /bin a.exe 绝对路径从系统根目录(盘符)开始
2.3文件打开模式
r : r ead 读w : w rite 写a : a ppend 追加FILE *fp = fopen("data.txt" , "打开模式字符串" );打开模式字符串= 包括:①权限 ②方式1.读写 权限FILE *fp = fopen("data.txt" , " r ");只读打开, 文件必须存在FILE *fp = fopen("data.txt" , " w ");写入打开,文件不存在则新建,存在则 抹掉从头写,形成一个新文件,不能读FILE *fp = fopen("data.txt" , " a ");追加打开,文件不存在则新建,存在则 从最后开始追写,不能读FILE *fp = fopen("data.txt" , " r+ ");给r添加写权限,文件必须存在,可以修改(写/ 覆 )原有内容FILE *fp = fopen("data.txt" , " w+ ");给w添加读权限,文件不存在则新建,存在则抹掉, 可读FILE *fp = fopen("data.txt" , " a+ ");给a添加读权限,文件不存在则新建,存在则 从最后开始追写,可读2.4读写方式
FILE *fp = fopen("data.txt" , "r");FILE *fp = fopen("data.txt" , "r b ");FILE *fp = fopen("data.txt" , "w b ");FILE *fp = fopen("data.txt" , "r+ b ");默认以文本(text)的方式通过 b 指明以二进制( binary )的方式打开2.4.1从文本文件中读: fscanf
• 从文本文件中读内容等效于从控制台中读取用户输入的相同内容• 只不过,使用 f scanf 函数并 指定文件指针items.txt : 10, 3.5, milk 3, 18.0, book 21, 1.5, tissue
如果从控制台读: int c; float p; char name[100]; for (int i = 0; i < 3; i += 1) { scanf(“%d, %f, %s”, &c, &p, name); printf(“%d, %.2f, %s\n”, c, p, name); }
如果从 items.txt 读: int c; float p; char name[100]; FILE *fp = fopen("items.txt","r"); for (int i = 0; i < 3; i += 1) { fscanf(fp,"%d, %f, %s", &c, &p, name); printf("%d, %.2f, %s\n", c, p, name); }
2.4.2往文本文件中写: fprintf
• 往文本文件中写内容等效于往控制台输出内容• 只不过,使用 f printf 函数并 指定文件指针往 items.txt 写: int c[3] = {10, 3, 21}; float p[3] = {3.5, 18.0, 1.5}; char name[3][100] = {"milk","book","tissue"}; FILE *fp = fopen("items.txt","w"); for (int i = 0; i < 3; i += 1) { fprintf(fp,"%d, %.2f, %s\n", c[i], p[i], name[i]); }
运行后 items.txt 内容: 10, 3.5, milk 3, 18.0, book 21, 1.5, tissue
2.4.3判断是否读取至文件末尾: feof
• 文件指针随着读写而在文件中不同位置前后移动• 在读文件时,文件指针移动到 文件末尾 就意味读取完成了文件所有内容• 使用专门的函数 feof 进行此判断items.txt : 10, 3.5, milk 3, 18.0, book 21, 1.5, tissue
从 items.txt 读: int c; float p; char name[100]; FILE *fp = fopen("items.txt","r"); while ( !feof(fp) ) { fscanf(fp,"%d, %f, %s", &c, &p, name); printf("%d, %.2f, %s\n", c, p, name); } //会移动文件指针 fp
2.4.4往文本文件中追写
• 往文本文件中追写内容需要在打开模式字符串中包含字母 ‘a’• 会从文件的原有内容的 最末尾后 继续写入• 追写模式实质是打开文件后,将文件指针置于文件末尾items.txt : 10, 3.5, milk 3, 18.0, book 21, 1.5, tissue
往 items.txt 追写: int c[3] = {10, 3, 21}; float p[3] = {3.5, 18.0, 1.5}; char name[3][100] = {"milk","book","tissue"}; FILE *fp = fopen("items.txt,"a"); for (int i = 0; i < 3; i += 1) { fprintf(fp,"%d, %.2f, %s\n", c[i], p[i], name[i]); }
运行后 items.txt 内容: 10, 3.5, milk 3, 18.0, book 21, 1.5, tissue 10, 3.5, milk 3, 18.0, book 21, 1.5, tissue
2.5例题
• Key 1 :通过 fopen 函数用指定模式打开指定路径的文件,获得一个 FILE* 指针• Key 2 :文件打开模式包括:①权限 ②方式,以字符串的形式传递给 fopen 函数①权限 ②方式=先研究能不能进这栋大厦,进去了再思考是走楼梯还是坐电梯吧1. 程序路径为 D:\prj\bin\test.exe,要打开日志 D:\prj\log\20221011.log 进行内容的追加应当使用:___FILE*___ fp = fopen__("..//..//log//20221011.log","a" )_________________分析:相对路径中 ../ 表示上一级目录,../../ 表示上两级目录,以此类推,也可以写绝对路径 “D:\\prj\\log\\20221011.log2. 如果需要打开一个已经存在的非空文本文件 "FILE" 并进行修改,正确的语句是( D)A. FILE *fp = fopen("FILE" , "r");B. FILE *fp = fopen("FILE" , "a+");C. FILE *fp = fopen("FILE" , "w+");D. FILE *fp = fopen("FILE" , "r+");分析:文件打开权限中,最难理解的就是”r+” :其中 r 表明这个文件是存在的,现在要打开读取其内容,因此文件指针一开始会置于文件最开头; + 表示同时需要写权限,随着读写操作导致的文件指针移动,可以在任意一处进行内容的写入( 覆盖原有文本 ),起到修改的效果。3. 关于文件打开模式,说法正确的是(C )A. “r”必须打开已有文件,想新建文件需要使用”r+”B. “w”用于已有文件表示在其后追写内容C. “a+”模式的+表示”a”基础上增加上读取权限D. “w+”模式适合用于修改已有文件分析:想要新建文件要有w,修改已有文件的是r+• Key 3 :通过 fscanf 和 fprintf 读写文本文件,其效果等同于控制台IO1. 文件 f.txt 内首行是一个整数,表明接下来由空格分隔的小数的数量,正确加载这些数据的是( B)A. FILE *fp = fopen("f.txt","r"); if (!fp) return 0; int n; scanf(fp,"%d", &n); float *p = malloc(4 * n); for (int i = 0; i < n; i += 1) fscanf("%f", p + i); free(p); fclose(fp);
FILE *fp = fopen("f.txt","r"); if (!fp) return 0; int n; fscanf(fp,"%d", &n); float *p = malloc(4 * n); for (int i = 0; i < n; i += 1) fscanf("%f", &p[i]); free(p); fclose(fp);
FILE *fp = fopen("f.txt","r"); if (fp) return 0; int n; fscanf(fp,"%d", &n); float *p = malloc(4 * n); for (int i = 0; i < n; i += 1) fscanf("%f", p + i); free(p); fclose(fp);
FILE *fp = fopen("f.txt","w"); if (!fp) return 0; int n; fscanf(fp,"%d", &n); float *p = malloc(4 * n); for (int i = 0; i < n; i += 1) fscanf("%f", p + i); free(p); free(fp);
分析:条件语句为!fp,因为当fp为0时返回0,为假时想进入语句,用取反,内存开辟就要释放,文件指针不需要释放,但要关闭文件。2. 文本文件DATA.txt的原始内容如下所示,执行该段代码后,文件的内容变为( D)DATA.txt: hello, world
FILE *fp = fopen("DATA.txt","r+"); if (!fp) return 0; char str[100] = "HELLO"; fprintf(fp,"%s", str);
A. hello, worldB. hello, worldHELLOC. HELLOD. HELLO, world分析:以”r+”模式打开文件,若文件存在则打开后文件指针放置于文件开头,能读能写。因此打开DATA.txt后直接写入一个字符串将从头开始覆写原有内容。3.二进制文件IO
3.1二进制文件
• 思考:通过文本文件存储整数 123456789 需要多少空间?(需要九字节,文本文件存储原则是一个字节一个字符,9个数字分别占一个字节)• 在文本文件中,将被存放成字符串“123456789” ,每字符1字节,共占9个字节• 但它本来是个int,只占4字节呀!为什么不 直接把这4字节写入文件 就好了• 文本文件:对数据进行ASCII编码后,按字节 逐字符 写入/读取• 二进制文件:直接将内存二进制数据 整体搬入 文件,要解读需要知道其 特定格式( 可以尝试用记事本打开一个.mp3文件,或 者.jpg文件,.ppt文件,.mp4文件...等,会发现出现一对乱码,因为每个文件有特殊的读取方式 )FILE *fp = fopen("data.bin","wb"); if (!fp) { return 0; } int a = 999999; char b =‘a’; float c = 12.34f; fwrite(&a, sizeof(int), 1, fp); fwrite(&b, sizeof(char), 1, fp); fwrite(&c, sizeof(float), 1, fp);
FILE *fp = fopen("data.bin","rb"); if (!fp) { return 0; } int a; char b; float c; fread(&a, sizeof(int), 1, fp); fread(&b, sizeof(char), 1, fp); fread(&c, sizeof(float), 1, fp); printf("%d, %c, %.2f", a, b, c);
3.2二进制文件读写: fread / fwrite
• 直接把二进制文件读写理解成 字节块搬家 即可, fwrite 搬进去, fread 搬出来•fwrite( 待写入数据指针,数据单元大小,数据个数,fp )• fread( 存放数据的指针,数据单元大小,数据个数,fp )数据单元大小与数据个数 乘积为总读(写)字节数int a[3] = {1, 2, 3}; float b[2] = {1.7f, 3.14f}; FILE *fp = fopen("data.bin","wb"); if (!fp) { return 0; } fwrite(a, sizeof(int), 3, fp); fwrite(b, sizeof(b[0]), sizeof(b) / sizeof(b[0]), fp);
int a[3]; float b[2]; FILE *fp = fopen("data.bin","rb"); if (!fp) { return 0; } fread(a, sizeof(int), 3, fp); fread(b, sizeof(float), 2, fp);
3.3例题
• Key 1 :二进制文件的本质是数据的原生字节块,需要知道对应的编码方式才能读取• Key 2 :将数据存入二进制中使用fwrite,读取使用fread,指明类型和数量即可1. 下面是通过二进制模式读写结构体数据的两段代码,代码行及其描述正确的是( C)A. 行 [b] 的作用是检测文件是否已经存在B. 行 [d] 中的 1 指的是待写入数据所占内存的字节数C. 行 [2] 中的 “rb” 指的是以只读权限和二进制方式打开文件D. 行 [4] 中的 sizeof(s) 是获得待读取数据的字节总数typedef struct Student { int age; float score; char name[20]; }S;
[a] S s; [b] FILE *fp = fopen("student.dat","wb"); [c] if (!fp) { return 0; } [d] fwrite(&s, sizeof(s), 1, fp); [e] fclose(fp);
[1] S s; [2] FILE *fp = fopen("student.dat","rb"); [3] if (!fp) { return 0; } [4] fread(&s, sizeof(s), 1, fp); [5] fclose(fp);
3. 执行完左侧的代码段后,执行右侧代码段的输出是( B)注:1094795585 (10) = 41414141 (16) = 01000001,01000001,01000001,01000001 (2)FILE *fp = fopen("data.bin","wb"); unsigned n = 0x41414141; fwrite(&n, sizeof(n), 1, fp); fclose(fp);
FILE *fp = fopen("data.bin","r"); char ch[20]; fscanf(fp,"%s", ch); printf("%s", ch); fclose(fp);
A. 1094795585B. AAAAC. 01000001010000010100000101000001D. 程序会报错,因为文件的写入和读取方式不匹配分析:变量n实际字节内容就是01000001010000010100000101000001,二进制方式会将这4个字节直接写入文件,即文件实际存放的就是 这4字节的内容。而右侧代码是通过文本方式读文件,会把这四个字节理解成一个一个的字符(文本),即按ASCII码进行解码,得到的结果为字 符串“AAAA” ,因为字符'A'的ASCII码为65(10) = 01000001 (2)文件读写字节数目必须匹配,实质就是字节搬家,文本文件是一个字节一个字符