文件操作---bit
为什么要使用文件?将数据持久化保存什么是文件?放在硬盘上面的数据集文件名文件名包含3个部分: 文件路径文件名主干文件后缀eg: c:\code\test.txt这是 C盘 code路径 底下的 test.txt文件二进制文件和文本文件⭐ 数据在文件中如何存储字符一律以 ASCII码值 的形式存储,数值型数据既可以用 ASCII码值 形式存储,也可以用 二进制 的形式存储⭐ 文本文件和二进制文件文本文件---- 存的是ASCII码值,输出的是一个个字符,肉眼能看懂二进制文件----- 存原生的二进制一个例子:这里演示的是整数 10000 的存储数据在内存中以二进制的形式存储,这里10000的二进制序列为 :00000000 00000000 00100111 00010000case 1 :如果存成文本文件,那么就将 10000 当作5个字符来看待,每个字符先转化为ASCII码值,再变成相应的二进制序列写入文件.1’的ASCII码值是49 (二进制00110001), 0’的ASCII码值是48 (二进制00110000)文件中存的是: 00110001 00110000 00110000 00110000 00110000 (共占5个字节)用文本编辑器打开,看到的就是 10000case 2 :如果存成二进制文件,那么直接将二进制序列写入文件文件里存的就是 00000000 00000000 00100111 00010000 (共占4个字节)如果用文本编辑器打开,看到的就是乱码,因为 ta 不是ASCII字符文本编辑器的逻辑是按字节查表把每个字节转成字符但只有部分字节有对应的可显示字符剩下的只能显示成乱码ASCII码表中的几组特殊数据 :字符 a~z 的 ASCII 码值从 97~122对应的大小写字符 (a 和 A) 的 ASCII 码值的差值是 32数字字符 0~9 的 ASCII 码值从 48~57数字字符与对应数字的ASCII码值的差值是48在这些字符中 ASCII 码值从 0~31 这 32 个字符是不可打印字符无法打印在屏幕上观察在前面的例子里int 10000 的二进制是00000000 00000000 00100111 00010000这四个字节对应的十进制是0, 0, 39, 160ASCII 是NUL控制字符编辑器显示不了39是’单引号能显示16是DLE控制字符编辑器显示不了当编辑器遇到这些「没有对应可显示字符的字节」时就会用乱码符号比如□、?、×来代替这样看到的就是一堆看不懂的东西。文件的操作流高度抽象出来的概念,可以理解为流淌着数据的河C程序针对⽂件、画⾯、键盘等的数据输⼊输出操作都是通过流操作的⼀般情况下我们要想向流⾥写数据或者从流中读取数据都是要打开流然后操作标准流为什么我们从键盘输⼊数据向屏幕上输出数据并没有打开流呢那是因为C语⾔程序在启动的时候默认打开了3个流⭐ stdin - 标准输⼊流在⼤多数的环境中从键盘输⼊scanf函数就是从标准输⼊流中读取数据⭐ stdout - 标准输出流⼤多数的环境中输出⾄显⽰器界⾯printf函数就是将信息输出到标准输出流中⭐ stderr - 标准错误流⼤多数环境中输出到显⽰器界⾯这是默认打开了这三个流我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的stdin、stdout、stderr 三个流的类型是 FILE * 通常称为⽂件指针C语⾔中就是通过 FILE* 的⽂件指针来维护流的各种操作的文件指针每个被使⽤的⽂件都在内存中开辟了⼀个相应的⽂件信息区⽤来存放⽂件的相关信息如⽂件的名字⽂件状态及⽂件当前的位置等这些信息是保存在⼀个结构体变量中的该结构体类型是由系统声明的取名 FILE每当打开⼀个⽂件的时候系统会根据⽂件的情况⾃动创建⼀个FILE结构的变量并填充其中的信息使⽤者不必关⼼细节⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量这样使⽤起来更加⽅便每一个被打开的文件都有一个文件信息区和指向这个文件信息区的文件指针文件的打开和关闭C语言中规定用 fopen函数 来打开文件,用 fclose函数 来关闭文件//打开⽂件FILE*fopen(constchar*filename,constchar*mode);//关闭⽂件intfclose(FILE*stream);其中mode表示文件打开的模式打开和关闭文件的代码模板intmain(){// 打开文件FILE*pffopen(test.txt,r);// 这里的文件名和文件使用方式都要加双引号if(pfNULL){perror(fopen);return1;}// 读文件// 关闭文件fclose(pf);pfNULL;return0;}文件的顺序读写一些顺序读写的函数顺序读写意味着每读写一个字符,光标就会自动往后移一位 ( 指针不用 )所有输入流基本上指的是 键盘,文件所有输出流基本上指的是 屏幕(显示器),文件 fputc向文件流中写入单个字符是文件顺序写入的基础函数函数原型 :intfputc(intc,FILE*stream);参数 c要写入的字符虽然类型是 int实际传 char 即可内部会转为 unsigned char 处理参数 stream目标文件指针返回值成功返回写入的字符ASCII 值失败返回 EOF值为 -1代码示例:intmain(){// 打开文件FILE*pffopen(test.txt,w);if(pfNULL){perror(fopen);return1;}// 读文件/*fputc(a, pf); fputc(b, pf); fputc(c, pf);*/charch0;// 字符0的 ASCII码值是 48// 数字 0 的 ASCII码值是 0for(cha;chz;ch){fputc(ch,pf);// 不用特意对pf进行操作}// 关闭文件fclose(pf);pfNULL;return0;}值得注意的是,这里的 pf 虽然是指针,但是不用特意对其进行来移动,每一次循环输入后, 光标就会自动后移一位, 就像现在打字一样 fgetcfgetc 的功能是从文件流中读取一个字符值得注意的还是指针的问题,这里文件指针同样不需要手动,每次读取后,光标会自动后移一位若是想一口气读完文件所有内容,可以借助循环 fputs向文件 / 屏幕写入一个字符串(按行写入), 自动带换行函数原型 :intfputs(constchar*str,FILE*stream);str要写入的字符串字符串传参传的是首个字符的地址stream文件指针 或 标准输出 stdout返回值成功非负整数通常是 0失败EOF-1代码展示 :// 写文件fputs(hello world,pf);fputs(hello bit,pf); fgets作用 : 从流中读取一行字符串, 适用于所有输入流(文件,键盘)函数原型 :char*fgets(char*str,intnum,FILE*stream);str存放读到内容的字符数组num最多读 num-1 个字符自动补 \0stream文件指针 / 标准输入 stdin这个函数的意思是在流里面读最多num-1个字节数据放到str指向的空间里面返回值 :成功返回 str地址读到文件尾 / 失败返回 NULL代码示例 :intmain(){// 打开文件FILE*pffopen(test.txt,r);if(pfNULL){perror(fopen);return1;}// 读文件chararr[10]{0};fgets(arr,10,pf);// 关闭文件fclose(pf);pfNULL;return0;}⭐ 关于fgets读取数据的问题fgets函数是一行一行读取字符的函数三条规则 :遇到 \n 就停, 表示这一行已经读完(下次读取时换行), 并且把 \n 一起读走最多读 n-1 个字符因为最后一个字节空间要留给 ’ \0 ’一行太长装不下会分段读fgets函数读取实例 :读取的文本第一行非常长,第一次只读取19个字节,没遇到 \n,表示这一行还没读完,下一次读取时仍在这一行所以第二次fgets读取时不会换行读,会继续把后面的hhhhh读完,这时候遇到 \n,表示这一行已经读完,下一次读取时换行第三次读取时,一次能读到 \n.代表这一行已经读完,所以下一次读取时换行第四次以此类推… fprintffprintf 的功能是按照指定格式,把变量数据输出到文件里这个函数的用法与 printf 几乎没有差别,就是参数多了一个文件指针代码展示 :structS{charname[20];intage;floatscore;};intmain(){structSs{zhangsan,20,65.5f};// 想把s中的数据放在文件中FILE*pffopen(test.txt,w);if(pfNULL){perror(fopen);}// 写文件 -- 是以文本的形式写进去的fprintf(pf,%s %d %f,s.name,s.age,s.score);// 关闭fclose(pf);pfNULL;return0;} fscanffscanf 的功能是从指定文件流中,按格式化字符串读取数据,存入变量中这个函数同样与scanf几乎无差别,就参数多了一个文件指针代码示例 :structS{charname[20];intage;floatscore;};intmain(){structSs{0};// 想从文件 test.txt 中读取数据放在s中FILE*pffopen(test.txt,r);if(pfNULL){perror(fopen);}// 读文件fscanf(pf,%s %d %f,s.name,(s.age),(s.score));// 这里因为name是数组名,数组名就是地址,所以不用再额外取地址// 打印在屏幕上面看看printf(%s %d %f,s.name,s.age,s.score);// 关闭fclose(pf);pfNULL;return0;} sscanfsscanf 的功能是从字符串中按照格式提取数据,存入变量代码示例 :intmain(){charstr[]18 90.5;intage;floatscore;// 从字符串 str 里读 age 和 scoresscanf(str,%d %f,age,score);printf(%d %f,age,score);return0;} sprintfsprintf 的功能就是将格式化的数据转化为字符串代码示例 :structS{charname[20];intage;floatscore;};intmain(){charbuf[200]{0};structSs{zhangsan,20,65.5f};// 这一步是 将格式化数据转化为字符串sprintf(buf,%s %d %f,s.name,s.age,s.score);printf(%s,buf);return0;} 两组函数的对比标准输入流一般指的是键盘标准输出流一般指的是屏幕指定的 输入 or 输出 流 ---- 这里多指文件流 fwrite以二进制方式把一块数据直接写入文件函数原型 :size_tfwrite(constvoid*ptr,size_tsize,size_tcount,FILE*stream);ptr要写入的数据地址数组、结构体、变量都行size一个元素的大小如 sizeof(int)count要写多少个元素stream文件指针返回成功写入的元素个数 :成功返回值 count失败返回值 count代码示例 :intmain(){intarr[]{1,2,3,4,5};FILE*pffopen(test.txt,wb);// 以二进制的形式写if(pfNULL){perror(fopen);return1;}// 写数据intszsizeof(arr)/sizeof(arr[0]);fwrite(arr,sizeof(arr[0]),sz,pf);// 以二进制的形式写入fclose(pf);pfNULL;return0;}运行后文本 : fread作用 : 从文件中以二进制方式整块读取数据只用于二进制文件rb函数原型 :size_tfread(void*ptr,size_tsize,size_tcount,FILE*stream);ptr存放读取数据的缓冲区数组 / 变量地址size一个元素的字节大小如 sizeof(int)count要读多少个这样的元素stream文件指针返回值 :返回实际成功读到的元素个数正常读完返回值 count读取结束 / 失败返回值 count代码示例 :intmain(){intarr[5]{0};FILE*pffopen(test.txt,rb);// 以二进制的形式读if(pfNULL){perror(fopen);return1;}// 读数据fread(arr,sizeof(arr[0]),5,pf);// 从文件流中读 5个 大小为sizeof(arr[0]) 的数据放入arr中inti0;for(i0;i5;i){printf(%d ,arr[i]);}fclose(pf);pfNULL;return0;}文件的随机读写 fseek作用 : 移动文件指针,实现文件的随机读写函数原型 :intfseek(FILE*stream,longoffset,intorigin);三个参数 :stream文件指针offset偏移量正数向后负数向前origin起始位置三选一SEEK_SET从文件开头开始SEEK_CUR从当前位置开始SEEK_END从文件末尾开始返回值 : 成功返回 0 ,失败返回 非0代码示例 :intmain(){FILE*pffopen(test.txt,r);if(pfNULL){perror(fopen);return1;}// 读文件charch1fgetc(pf);// 此时光标自动往后移动一位printf(%c\n,ch1);// afseek(pf,4,SEEK_CUR);// 光标从当前位置往后移动4个单位// fseek(pf,5,SEEK_SET);// fseek(pf,-4,SEEK_END);charch2fgetc(pf);printf(%c\n,ch2);// ffclose(pf);pfNULL;return0;}} ftell作用 : 返回文件指针当前位置相对于文件开头的偏移字节数,常用于获取文件大小,记录当前读写位置函数原型 :longintftell(FILE*stream);参数FILE* 文件指针返回值成功返回偏移字节数long失败返回 -1L代码示例 :intmain(){FILE*pffopen(test.txt,r);if(pfNULL){perror(fopen);return1;}// 读文件charch1fgetc(pf);printf(%c\n,ch1);// afseek(pf,4,SEEK_CUR);// 光标从当前位置往后移动4个单位printf(%d\n,ftell(pf));// 此时返回的偏移量应该是5fclose(pf);pfNULL;return0;} rewind作用 : 把文件指针直接重置到文件起始位置等价于fseek(stream, 0, SEEK_SET)函数原型 :voidrewind(FILE*stream);无返回值参数已打开的文件指针代码示例 :intmain(){FILE*pffopen(test.txt,r);if(pfNULL){perror(fopen);return1;}// 读文件charchfgetc(pf);printf(%c\n,ch);// afseek(pf,4,SEEK_CUR);// 光标从当前位置往后移动4个单位chfgetc(pf);printf(%c\n,ch);// frewind(pf);chfgetc(pf);printf(%c\n,ch);// afclose(pf);pfNULL;return0;}文件读取结束的判定 判断文本文件的读取是否结束 :判断依据读取函数返回 EOF 或 NULLfgetc返回 EOF → 结束fgets返回 NULL → 结束 判断二进制文件的读取是否结束 :判断依据fread 返回值 实际要读的个数 feoffeof 不能用来判断文件有没有读完只能在读取结束后判断是不是 “正常读到文件尾”函数原型 :intfeof(FILE*stream);返回值 :返回非 0已经到文件末尾返回0没到末尾 / 读取失败 ferror判断文件在读取 / 写入过程中是否发生了错误不是文件尾是真的出错函数原型 :intferror(FILE*stream);返回值 :返回 非 0文件流发生错误返回 0没有发生错误两个函数的对比 :feof判断是不是正常到文件尾ferror判断是不是读 / 写出错了两个都是事后判断不能当循环条件代码示例 :intmain(){FILE*pffopen(test.txt,r);if(pfNULL){perror(fopen);return1;}// 读文件intch0;while((chfgetc(pf))!EOF){printf(%c ,ch);}// 读取停止时,判断是正常读取结束还是异常停止if(feof(pf)){printf(遇到文件末尾,读取正常结束);}elseif(ferror(pf)){perror(fgetc);}fclose(pf);pfNULL;return0;}