https://m./i6847739122979701260/?app=news_article×tamp=1594400967&use_new_style=1&req_id=20200711010927010129040078043AEF9D&group_id=6847739122979701260 前些时候,我们学习的C语言程序都是由输入输出和算法组成的控制台程序。我们在终端上来输入我们提供的数据,然后程序也会通过终端来告诉我们最终运行的结果。 但是,可能有的同学已经观察到了,我们日常使用的别人开发的程序,大多数都是通过文件来提供数据的。比如一个Excel的报表,程序可以直接来分析里面的数据。再比如,一个TXT格式的电子书,程序可以直接分析有多少字、多少个章节,甚至还可以生成出一个目录来。 拥有这样能力的程序,是不是感觉功能强大了许多?这就要用到我们今天要讲到的内容——「文件操作」。 关于文件在我们比较熟悉的Windows系统下,文件类型的区分是用「扩展名」来进行的。但其实扩展名并不是指「文件格式」,它只是一个「门牌号」而已。至于它到底对不对,那系统就不知道了。可能有很多的新手,在遇到格式的问题的时候,会认为直接更改扩展名,就能实现格式转换。不瞒你们说,我小时候也有过这种想法。但是后来发现,不行。举个例子,现在有一个 MP3 的文件,要转成 AAC。这两个文件从编码上来讲,就是不一样的。MP3 只能用 MP3 的方式去读取,AAC 只能用 AAC 的方式去读取。如果你把扩展名直接改成 AAC,那么系统就被你骗了,就会用 AAC 的方式去读取实际还是 MP3 的文件,当然是不行了。 不同的扩展名,就对应了不同的读取方式。「EXE」 就代表 Windows 系统下的可执行二进制文件,「TXT」是纯文本文件,等等。 在 Linux 和 Unix 操作系统下,文件的定义就宽泛多了。不光软件,硬件也可以叫文件。也就是说,硬件实际上也是当做文件的方式来处理的。 在C语言中,文件一般分为两种,一种是二进制文件,就是我们编译出来的那个东西,我们是看不懂的;另一种是文本文件,也就是我们常说的源代码。 打开和关闭文件我们要对一个文件进行操作,首先我们需要把文件打开,然后才能读或者写。对文件操作完成后,我们还要将文件关闭。 C语言中的打开文件使用fopen函数,通式如下: fopen('文件路径', '模式') 如果打开文件成功,则会返回一个FILE结构的指针,通过这个指针,我们就可以对这个文件进行操作;如果打开文件失败,则会返回NULL。 下面是所有的模式: 前面几个都好理解,只是最后一个,为啥要区分一个二进制出来呢? 不加「b」的情况下,就是以文本的形式来打开。因为在不同的操作系统中,换行符是不同的。Unix系统用\n,MacOS用\r,而Windows用的是\r\n,那么在文本模式下打开,C语言会根据系统环境的不同,来转化换行符。而在二进制的模式下,就不会进行任何的转换。 当你对文件操作完毕后,一定要记得把文件用fclose()函数来关闭。其实我们在打开文件后的所有操作,实际上都被记录到了缓存里,只有执行了关闭后,我们的更改才会生效。如果关闭成功,则函数会返回0;失败的话,就会返回EOF。关闭成功后,我们创建的文件指针就会失效。 //Example 01//学习交流群:782648055#include <stdio.h>#include <stdlib.h>int main(void){ FILE* f; int chr; if ((f = fopen('file1.txt', 'r')) == NULL) { printf('打开失败!\n'); exit(EXIT_FAILURE); } while ((chr = getc(f)) != EOF) { putchar(chr); } fclose(f); return 0;}
//Consequence 01C programming makes me happy! 顺序读写文件打开了文件之后,就可以进行我们的操作了。 读写单个字符读取单个字符,我们可以用fgetc和getc这两个来实现。它们的作用,就是读取一个字符,然后将光标移动到下一个位置。
函数的参数,是一个FILE结构体的指针,也就是一个准确读取的文件流。读取成功就会将读取到的unsigned char内容转化为int并返回;文件结束或者读取失败就返回EOF。 这俩函数不同的地方就在于,fgetc是函数实现,而getc是用宏实现。宏会产生大量的代码量,但是没有函数调用堆栈的步骤,所以速度会快很多。但是宏的展开可能会多次调用参数,因此如果参数中含有自增、自减这种副作用的的方法,就只能用函数实现的fgetc了。 写入单个字符,我们可以用fputc和putc,带有f的,就是函数,另一个就是宏的实现的了。 #include <stdio.h>...int fputc(int c, FILE* stream);int putc(int c, FILE* stream); 第一个参数是你要写入的字符,第二个是你要写入的文件流。 读写整个字符串这里就要用到fgets和fputs两个函数了。
其中,fgets有三个参数,第一个是一个字符型指针,用来存放读取的数据;第二个用来指定读取的长度(包含'\0');第三个是用于指定读取的文件流。 函数调用成功后,会返回第一个参数所指向的地址。如果读取到EOF则eof指示器被设置。若一开始就读取到EOF,第一个参数的内容不变,返回NULL。若读取发生错误,则error指示器被设置,函数返回NULL,第一个参数内容可能会被改变。 fputs第一个参数用于存放待写入的数据,第二个是指定待写入的文件流。函数调用成功,返回一个非 0 值,失败则返回EOF。 格式化读写文件在文件里,我们就不能用我们熟悉的scanf和printf了。但是C语言也提供一组类似的函数:fscanf和fprintf。 用法上,第一个参数用于指定文件流,后面的就是照搬的scanf和printf中的参数。 //Example 02#include <stdio.h>#include <stdlib.h>#include <time.h>int main(void){ FILE* fp; struct tm* p; time_t t; time(&t); p = localtime(&t); //写入日期到文件 if ((fp = fopen('date.txt', 'w')) == NULL) { printf('打开文件失败!\n'); exit(EXIT_FAILURE); } fprintf(fp, '%d-%d-%d', 1900 + p -> tm_year, 1 + p -> tm_mon, p -> tm_mday); fclose(fp); //读取文件日期,输出到终端 int year, month, day; if ((fp = fopen('date.txt', 'r')) == NULL) { printf('打开文件失败!\n'); exit(EXIT_FAILURE); } fscanf(fp, '%d-%d-%d', &year, &month, &day); printf('%d-%d-%d\n', year, month, day); fclose(fp); return 0;}
//Consequence 022020-6-15 二进制读写我们用fopen函数可以用二进制的方式来打开一个文件,但实际上我们要用二进制的方式来读写,还得用相应的函数才行。 C语言提供了fread和fwrite两个函数来实现二进制的读取和写入。
首先来看fread。这个函数有四个参数。第一个指向存放数据的地址,第二个指定读取的每个元素的尺寸,第三个指定准备读取的元素个数,最后一个指向待读取的文件流。 函数调用成功,会返回读取到的元素个数,如果实际读取的比第三个参数小,那么可能会一直读取到文件末尾或者发生错误,这种情况就要通过foef和ferror来进一步判断。 然后是fwrite,也是有四个参数。第一个是指向存放数据的地址,第二个是指定待写入的每个元素的尺寸,第三个是指定待写入的元素的个数,最后一个是指向待写入的文件流。 随机读写文件刚刚我们介绍的,都是从文件头开始读写。但是我们实际生产生活中,很多时候我们是需要任意修改的。比如改一个文档,很有可能是中间的什么地方错了,或者是表达有不妥。那么这个时候如果你还要从头开始去检索,那样效率就太低了。 于是,C语言也为我们提供了这个功能,就是随机读写。 首先,我们要了解光标的位置,才能够更好地运用这个功能。C语言为我们提供了ftell函数,它可以告诉我们现在的光标位置。 #include <stdio.h>...long ftell(FILE* stream); 如果将一个文件看成一个数组,那么这个函数返回的就是这个数组的下标。
//data.txt中的内容TechZone
如果你想将光标快速移动到文件头,可以用rewind函数来实现。 ...rewind(fp);fputs('Hello', fp);fclose(fp);...
可以看到,它会覆盖我们前面的数据。 有的同学可能会说了,你这不还是没解决问题吗? 好的,那就来解决下问题吧。C语言给我们提供了一个函数fseek,这个函数可以直接把光标跳转到我们想要的位置。 #include <stdio.h>...int fseek(FILE* stream, long int offset, int whence); 第一个参数是指的我们要读取的文件流,第二个是偏移量(往后走是正数,往前走是负数),第三个是指的开始偏移的位置。 值描述SEEK_SET文件开头SEEK_CUR当前位置SEEK_END文件末尾 如果我要定位到第一百个字符的位置,那么:
倒数第 10 个就要这样: fseek(fp, -10, SEEK_END) 标准流标准输入,标准输出和标准错误输出一般C语言程序在执行的时候,都会有 3 个面向终端的文件流,分别是「标准输入」,「标准输出」和「标准错误输出」。我们之前用printf的时候,其实就是在往标准输出流中写入字符串;用scanf的时候,其实就是函数在从标准输入流中读取字符串。当然,我们写的程序也不可能一直都是正确的,警告和报错的情况时有发生,这个时候其实就是对标准错误输出中写入数据。 这三个流,我们就将它们称为:「标准流」 C语言分别为这三个标准流提供了对应的文件指针:stdin,stdout,stderr 比如打开文件失败的时候,就可以这样显示:
这样就不用printf这种“不专业”的错误指示方法了。 打开文件失败! 错误处理每个流的内部都有两个指示器。一个是「文件结束指示器feof」,当遇到文件末尾时被设置;另一个是「错误指示器ferror」,当读写文件出错时被设置。
而使用clearerr可以人为地清除两个指示器的状态: ... clearerr(fp);... 错误指示器只能判断是否出了错误,但具体是什么错误,那就要看errno和perror了。 首先看errno。这个函数包含在errno.h这个头文件中。它会返回一个错误码。
举个例子: 打开文件失败:2 但是这个错误代码不是所有人都知道它的含义。所以C语言又提供了一个函数perror,它可以直接用文字来提示我们错误的地方。
结果是这样的: 打开文件失败,原因是:No such file or directory 中间的冒号是自动加上的。 或许以后在你的开发生涯中,用的最多的不是C语言,但这门语言对你带来的提升,那是不可忽视的。最后,祝各位学有所成! 关注我,带你遨游代码的世界获取完整视频教程,可以关注B站:https://www.bilibili.com/video/BV1QE411y7v4 |
|
来自: 山峰云绕 > 《c加加c井号面向对象》