做棋牌网站建设哪家好,西安建设工程有限公司,网站域名登陆地址查询,北京免费网站建设目录
1 关于文件
i)文件的基本知识
ii)数据文件的分类
2 文件打开和关闭
i)流和标准流
ii)文件指针
iii)文件打开和关闭
3 文件的顺序读写
i) fgetc fputc
ii) fgets fputs
iii) fscanf fprintf
iv) fwrite fread
4 对比一组函数 scanf/fscanf/sscanf/printf/fpri…目录
1 关于文件
i)文件的基本知识
ii)数据文件的分类
2 文件打开和关闭
i)流和标准流
ii)文件指针
iii)文件打开和关闭
3 文件的顺序读写
i) fgetc fputc
ii) fgets fputs
iii) fscanf fprintf
iv) fwrite fread
4 对比一组函数 scanf/fscanf/sscanf/printf/fprintf/sprintf
5 文件的随机读写
6 文件读取结束的判定
7 文件缓冲区 1 关于文件
i)文件的基本知识
在电脑中文件是随处可见的那你思考过为什么存在“文件”吗当我们运行程序的时候程序一旦结束在内存中存储的数据也会被销毁我们如果想要保存数据以方便下一次使用的话就需要用到文件也就是说文件是可以用来保存数据的。
那什么是文件呢从文件的功能性分类的话文件可以分为程序性文件和数据文件程序性文件包括源文件(.c后缀)目标文件(windows后缀为.obj)可执行程序(windows后缀为.exe)我们本章着重介绍的是数据文件程序文件会在后面的预编译章节介绍。在之前的章节我们处理数据的输入输出都是在终端处理的如键盘电脑屏幕但有点时候我们会把信息输入到磁盘里面读取数据的时候让磁盘输入数据给内存在从内存中读取数据计算机读取数据分为好几个等级从速度快慢分为寄存器缓存内存硬盘等等那么本场要学习的就是如何从磁盘从读取数据。
要使用文件我们肯定要知道文件标识是由什么组成的⽂件名包含3部分⽂件路径⽂件名主⼲⽂件后缀例如c:\code\test.txt就是一个完整的文件名文件路径是c:\code\文件名主干是test文件后缀就是txt为了方便起见我们一般把文件标识称作为文件名。
ii)数据文件的分类
数据文件被分为二进制文件和文本文件有的文件创建好了之后不是给使用者看的是给计算机看的但是计算机只能识别二进制的1 0所以会有二进制文件那么同理可得文本文件就是给使用者看的如果当你使用了记事本打开一个文件发现全是乱码那么它八九不离十的是二进制文件了。
二进制文件是数据在内存中不加转化直接输出到外存的文件数据文件需要经过ASCII码值的转化再输出到外存所以以ASCII码值存储的文件都是文本文件。
那么一个数据是怎么在内存中存储的呢
字符一律以ASCII码值的形式存储数值类型的数据可以用二进制的形式存储也可以用ASCII码值的形式存储以10000来举个例子10000的二进制是这样的VS2019测试如果直接以二进制的形式存储就只会占4个字节如果是ASCII码值的形式存储那么就会占5个字节1占一个字节其余的每个0占一个字节
00000000 00000000 00100111 00010000
当我们使用二进制存储的时候10000的二进制存储就会变为00 00 27 10那么因为VS是小端存储所以屏幕上看到的就是10 27 00 00
int main()
{int a 10000;FILE* pf fopen(data.txt, wb);fwrite(a, 4, 1, pf);fclose(pf);pf NULL;return 0;
}使用fwrite函数创建该文件并且以二进制的形式存入最后我们使用二进制的方式打开该文件得到的就是 2 文件打开和关闭
i)流和标准流
计算机输出数据和输入数据的时候需要外接到同设备而通过不同的外接设备输入输出的数据的时候操作都不一样那么计算机为了简化这一过程就引用了流的概念可以理解为计算机将数据不管三七二十一的一股脑给丢进去一个地方计算机需要数据的时候就去里面拿就行了那个地方就是流C语言一般画面文件键盘的输入输出的时候都是通过流完成的那么我们要使用流或者是输入数据到流里面就需要打开流使用完再关闭流。
可是实际上我们之前写代码的时候并没有过打开流关闭流这个操作这是因为C语言默认打开了三个流这三个流被称为标准流stdinstdout,stderr
stdin被称为标准输入流大多数情况从键盘输入scanf使用的时候就需要使用到这个流。
stdout被称为标准输出流大多数情况从屏幕输出,printf使用的时候就需要使用到这个流。
stderr被称为标准错误流大多数情况写也是从屏幕输出的。
当然流也是由类型的这三个流的类型是FILE*FILE的英文意思就是文件也就是文件指针C语言中FILE*的使用也是用来维护各种流的使用的。
ii)文件指针
文件类型指针就是FILE*简称文件指针而每个使用的文件在内存中都开辟了一块文件信息区文件的相关信息而这些信息保存在一个结构体变量里面这个结构体变量就是FILE*在vs2013关于FILE*的声明是这样的
struct _iobuf {char *_ptr;int _cnt;char *_base;int _flag;int _file;int _charbuf;int _bufsiz;char *_tmpfname;};
typedef struct _iobuf FILE; 创建了一个结构体变量_iobuf最后重命名为了FILE而在VS2022声明就没有那么详细了VS2022的的声明如下
当然我们只需要关心怎么用FILE*就行至于文件指针的源码是不用了解那么多的我们使用FILE*访问文件信息区的时候就可以找到文件的相关信息也就是说我们可以利用文件指针间接找到与该文件相关的文件。
iii)文件打开和关闭 文件打开的时候会返回一个指针指向该文件的文件信息区也就是建立了指针和文件的关系那么我们使用完之后就应该关闭文件C语言中打开文件使用的是fopen函数关闭文件使用的是fclose函数f就是fileopen就是打开的意思close就是关闭的意思。
//打开⽂件
FILE * fopen ( const char * filename, const char * mode );
//关闭⽂件
int fclose ( FILE * stream );这是他们的参数fopen的第一个参数就是文件名第二个是打开模式fclose参数就是文件指针也就是用来接受fopen函数返回的文件指针。
文件的打开模式有许多种如下 模式有很多种感兴趣可以自行使用一下。注意如果文件不存在的话就会创建一个这样的文件放在该代码的路径下这是相对路径如果想要指定路径放的话就是绝对路径: FILE* pf fopen(C:\\Users\\users\\OneDrive\\data.txt, w);只需要在文件名前面加上文件路径就行了但是要注意的是\要写两个这是前面转义字符的知识为了防止\和其他字符结合形成转义字符。
但是多次是用w打开文件的时候每次打开都会先让文件里面的内容清空这个小现象需要注意一下。 3 文件的顺序读写
i) fgetc fputc
fgetc是字符输入函数fputc是字符输出函数均适用于所有文件输入输出流。
int main()
{FILE* pf fopen(data.txt, w);assert(pf);char array[26] { 0 };for (int i 0; i 26; i){fputc(a i, pf);}for (int j 0; j 26; j){array[j] fgetc(pf);}for (int j 0; j 26; j){printf(%c , array[j]);}fclose(pf);pf NULL;return 0;
} 我们创建好文件指针之后需要判断一下指针是不是空指最后还要关闭指针并且置为空指针这里和动态开辟函数类似我们给文件里面存好数据之后第一遍是打印不出来我们想要的26个字符的因为这里的文件打开模式是w是写入那么运行第二次时我们把w换成r就fgetc函数就开始操作了。
如果fgetc函数读取失败返回或者是读取到了文件尾就会返回eof也就是文件结束标志那么函数就结束了如果fputc写入失败的话也是返回的eof。
在读取文件的时候字符函数都是一个字符一个字符的读取或者写入的当读取完一个字符后光标往后移动指向下一个字符所以如果不用for循环的话想要打印就需要重复写这两行代码 int ch fgetc(pf);printf(%c , ch);
实际上fgetc函数等效于getc函数只是getc函数在库中可以等效为宏。 ii) fgets fputs
fgets是文本行输入函数fputs是文本行输出函数均适用于所有输入输出流文本行其实就是字符串。
fgets和gets函数是非常不像的fgets函数有三个参数
char * fgets ( char * str, int num, FILE * stream );
fgets函数会从流里面读取num -1个字符并将这些字符存到指针str里面且最后一个元素置为\0读取过程中如果碰到了换行符 文件末尾 或者是读取了num - 1个字符以先到者为准读取num - 1个字符第num个字符是\0如果读取失败返回的是空指针如果读着读着莫名读取到了文件尾那么返回的就是eof。
fputs就没有什么好介绍的了就是将字符串放进去而已,puts输出之后会自动换行fputs不会这是他们的区别。
参考代码
int main(){FILE* pf fopen(data.txt, r);assert(pf);char arr[10] { 1,2,3,4,5,6,7,8,9,10 };fputs(xxxxxxx, pf);fgets(arr, 6, pf);for (int i 0; i 6; i){printf(%c , arr[i]);}return 0;
}iii) fscanf fprintf
fscanf fprintf这两个函数是格式化输入输出函数也就是要用到占位符适用于所有流可以这么说scanf能做的fscanf都可以做fprintf同理fscanf就比scanf多了一个参数fscanf是从文件中读取数据如果第一个参数是stdin也就是标准输入流的话就是从键盘里面输入数据了fprintf同理可得如果第一个参数是stdout也就是标准输出流的话就是从屏幕上打印这是函数原型
int fscanf ( FILE * stream, const char * format, ... );
int fprintf ( FILE * stream, const char * format, ... );
int main()
{FILE* pf fopen(data.txt, r);assert(pf);char ch 0;//fputs(1 2 3 4 5 6 7 8 9 10, pf);fscanf(pf, %c,ch);printf(%c, ch);fclose(pf);pf NULL;return 0;
}
这就是从文件里面读取数据如果我们想要从键盘读取只需把流换成stdin就行了那么这是读文件的操作如果是写文件就可以用到fprintf
int main()
{struct St {int age;char name[20];double score;};FILE* pf fopen(data.txt, r);assert(pf);struct St st { 10,zhangsan,99.9};fscanf(pf, %d %s %lf, (st.age), st.name, (st.score));fprintf(stdout, %d %s %.1f, st.age, st.name, st.score);fclose(pf);pf NULL;return 0;
}
iv) fwrite fread
fwrite fread是二进制输入输出函数只适用于文件 这是cplusplus里面的记述fwrite有四个参数意思就是从流里面写进去count个大小为size的元素用到的是指针文章最开始就用了这个函数存的是10000因为只用存一个10000所以第三个参数是1fread同理可得。
因为是二进制输入输出函数所以写入的时候用到的是wb读取的时候用到的是rb
int main()
{FILE* pf fopen(data.txt, wb);struct St{int age;//10char name[20];//zhangsandouble score;//99.9}st {10,zhangsan,99.9};fwrite(st,sizeof(st),1,pf);fclose(pf);pf NULL;return 0;
}
这是我们用二进制的方式写进了文件里面 文本文件写进去是这样的
可以看到有明显的差别因为字符都是以ASCII码值的形式存储所以结果不变但是其他数据就变化大了当然我们是看不懂的计算机看得懂比如用fread函数就看懂了
int main()
{FILE* pf fopen(data.txt, rb);struct St{int age;//10char name[20];//zhangsandouble score;//99.9}st;fread(st, sizeof(st), 1, pf);printf(%d %s %lf, st.age, st.name, st.score);fclose(pf);pf NULL;return 0;
} 4 对比一组函数 scanf/fscanf/sscanf/printf/fprintf/sprintf
scanf printf都是标准流的格式化函数fscanf fprintf是所有流的格式化函数scanf printf能做的
fscanf fprintf都可以做可以理解为fscanf fprintf包含了scanf printf前面介绍了这两组函数这里就不介绍了
sscanf sprintf sprintf的作用是将格式化的数据放到指针指向的空间里面sscanf的作用是从指针指向的空间种读取格式化的数据代码如下
int main()
{struct XY{char name[10];int age;float score;}xy {ziyou,18,100.8};char arr[100] { 0 };sprintf(arr, %s %d %f, xy.name, xy.age, xy.score);printf(%s, arr);return 0;
}
创建好一个结构体变量之后再创建一个字符数组用来存放sprintf输出的数据那么格式化的占位符是必不可少的因为数组名是首元素地址也就是指针所以写上了arr最后打印出来如下
因为我在fprintf写参数的时候已经空格了空格也会输出进去所以打出来也是带空格的。
int main()
{struct XY{char name[10];int age;float score;}xy { zhangsan,12,12.2 }, tem { 0 };char arr1[100] { 0 };sprintf(arr1, %s %d %f, xy.name, xy.age, xy.score);sscanf(arr1, %s %d %f, tem.name, (tem.age), (tem.score));printf(%s %d %f, tem.name,tem.age,tem.score);return 0;
}
sscanf是从arr1里面读取数据然后放在了结构体里面sprintf是输出到arr1里面相较于前面的两组函数这组函数是没有用到文件指针的但是指针是用到了由此可见指针的重要性。 5 文件的随机读写
文件的随机读写介绍三个函数 fseek ftell rewind
int main()
{FILE* pf fopen(data.txt, r);assert(pf);for (int i 0; i 26; i){fputc(a i, pf);}char ch fgetc(pf);printf(%c , ch);//ach fgetc(pf);printf(%c , ch);//bch fgetc(pf);printf(%c , ch);//cch fgetc(pf);printf(%c , ch);//dch fgetc(pf);printf(%c , ch);//efclose(pf);pf NULL;return 0;
}
这段代码的意思就是文件里面有英文字母26个使用fgetc函数一个一个读取最后的打印结果应该是a b c d e运行到d的时候文件指针也就是光标指向的是e那么如果我们想要让文件指针回到最开始的位置使得最后的打印结果还是a就可以用到rewind:
int main()
{FILE* pf fopen(data.txt, r);assert(pf);for (int i 0; i 26; i){fputc(a i, pf);}char ch fgetc(pf);printf(%c , ch);//ach fgetc(pf);printf(%c , ch);//bch fgetc(pf);printf(%c , ch);//cch fgetc(pf);printf(%c , ch);//drewind(pf);ch fgetc(pf);printf(%c , ch);//efclose(pf);pf NULL;return 0;
}
rewind的参数就是一个文件指针所以想要让文件指针回到起始位置用到一个rewind就行了。
那么如果我们想指定位置开始读取呢这里用到的函数就是fseek函数随机读取函数可以
使文件指针指向最开始到结尾的任意位置 它有3个参数第一个参数是文件指针第二个是偏移量第三个是计算偏移量的起始位置偏移量很好理解光标指向第一个字符的时候偏移量就是0往后一次偏移量增加1那么第三个参数写的时候有三个选择SEEK_SET,SEEK_CUR,SEEK_ENDCUR就是当前位置的意思SET就是起始位置的意思END就是末尾的意思所以移动光标的时候需要先确定好计算偏移量的起始位置然后确定偏移量从起始位置开始往左计算偏移量就是负数往右计算就是正数。
那么如果我懒我不想计算偏移量我想直接知道现在的偏移量呢只需要用ftell函数就是参数就是文件指针这个函数的返回值就是当前的偏移量。
如下
int main()
{FILE* pf fopen(data.txt, r);assert(pf);for (int i 0; i 26; i){fputc(a i, pf);}char ch fgetc(pf);printf(%c , ch);//ach fgetc(pf);printf(%c , ch);//bch fgetc(pf);printf(%c , ch);//cch fgetc(pf);printf(%c , ch);//dint num ftell(pf);printf(%d , num);//rewind(pf);fseek(pf, -2, SEEK_CUR);ch fgetc(pf);printf(%c , ch);//cfclose(pf);pf NULL;return 0;
} 6 文件读取结束的判定
文件读取结束分为正常读取到了结尾和读到一半遇到错误了那么我们如何判断文件是不是正常结束呢这里用到的函数是feof但是使用feof的前提是文件已经读取完了不管是哪种结束需要注意的是feof不能用来判断文件是不是读取结束了这是前提不要搞混淆了。
得feof 的作⽤是当⽂件读取结束的时候判断是读取结束的原因是否是遇到⽂件尾结束。
同理得ferror的作用是当⽂件读取结束的时候判断是读取结束的原因是否是遇到错误结束。
当读取文本文件的时候fgetc如果正常读取结束返回的是eoffgets如果正常读取结束返回的是NULL这也是它们的区别。
当读取二进制文件的时候判断fread返回的值是否小于文件中的实际字符数因为fread函数的返回值是读取到的个数。
文本文件代码示范
int main(void)
{int c; // 注意int⾮char要求处理EOFFILE* fp fopen(test.txt, r);if(!fp) {perror(File opening failed);return EXIT_FAILURE;}//fgetc 当读取失败的时候或者遇到⽂件结束的时候都会返回EOFwhile ((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);
}
二进制代码示范
enum { SIZE 5 };
int main(void)
{double a[SIZE] {1.,2.,3.,4.,5.};FILE *fp fopen(test.bin, wb); // 必须⽤⼆进制模式fwrite(a, sizeof *a, SIZE, fp); // 写 double 的数组fclose(fp);double b[SIZE];fp fopen(test.bin,rb);size_t ret_code fread(b, sizeof *b, SIZE, fp); // 读 double 的数组if(ret_code SIZE) {puts(Array read successfully, contents: );for(int n 0; n SIZE; n) printf(%f , b[n]);putchar(\n);} else { // error handlingif (feof(fp))printf(Error reading test.bin: unexpected end of file\n);else if (ferror(fp)) {perror(Error reading test.bin);}}fclose(fp);pf NULL;
}7 文件缓冲区
前面提到程序从计算机中读取数据的时候分为了好几个层次其中包括看文件缓冲区那么缓冲区的作用是是什么呢 程序运行的时候内存会为每个正在使用的文件开辟缓冲区读取数据的时候数据就会往先往里面放直到缓冲区装满了才会一并送到磁盘中相同的如果程序需要输入数据系统会将输入的数据放到输入缓冲区里面区直到输入缓冲区满了才会从磁盘输入到程序里面去。
那么缓冲区存在的意义是什么呢
有人会说为什么不输出一个就给磁盘一个实际上调用函数的时候计算机底层也会被调用起来所以看似工作的有程序内存等实际上还有计算机各个部分都是调用起来的如果输出一个就传送一个出去那其他部分的工作就是进行不下去所以缓冲区存在的意义就是为了提高工作效率。
int main()
{FILE*pf fopen(test.txt, w);fputs(abcdef, pf);//先将代码放在输出缓冲区printf(睡眠10秒-已经写数据了打开test.txt⽂件发现⽂件没有内容\n);Sleep(10000);printf(刷新缓冲区\n);fflush(pf);//刷新缓冲区时才将输出缓冲区的数据写到⽂件磁盘了printf(再睡眠10秒-此时再次打开test.txt⽂件⽂件有内容了\n);Sleep(10000);fclose(pf);//注fclose在关闭⽂件的时候也会刷新缓冲区pf NULL;return 0;
}
我们先給文件一串字符串然后让程序休眠10秒也就是SleepSleep需要引用到的头文件是windows程序休眠的这10秒我们打开文件就会发现abcdef是没有被写进去的这个时候我们使用fflush刷新一下缓冲区再去打开文件就会发现abcdef写进去了这个时候关闭文件需要注意的是关闭文件的时候缓冲区也会被刷新验证的方法就是把fclose提前不使用fflush这个时候也会发现文件过了10秒之后abcdef出现在了文件里面。
正因为缓冲区的存在在进行关于文件类操作的时候需要刷新缓冲区或者是关闭文件不然很可能导致读写文件出现问题。 感谢阅读