目录
Hello,everbody!两日不见甚是想念,嘿嘿。今天本打算和大家分享c语言的数据存储和位操作符的,但是有很多宝子对文件操作相关函数很是头疼。那c语言的数据存储和位操作符这个话题就留在下一期更新叭。那好,让我们进入今天的主题——【C语言】文件操作。
1.前言:
文件操作这一部分的知识点比较多,特别是与其相关的函数有很多相似的,容易混淆。所以建议大家:这篇文章收藏后反复认真浏览\(0^◇^0)/。并且在自己的电脑上多动手敲一敲代码才可以记住呦!
2.什么是文件?
计算机磁盘上的文件是文件,但是程序设计中,我们说的文件一般分为两种:程序文件,数据文件。
2.1程序文件
包括源程序文件(后缀为.c) ,目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)
2.2数据文件
文件的内容不一定是程序,而是程序运行时读写的数据。比如程序运行时需要从中读取数据的文件,或者输出内容的文件。
咱们这篇文章讨论的是数据文件。
3.文件名
一个文件要有一个唯一的文件标识(文件名),以便用户识别和引用。
文件名包含三部分:文件路径+文件名主干+文件后缀。
例如:c:\code\test.txt(c盘,code文件夹,test.txt文件) c:\code\是文件路径 test是文件名主干 .txt是文件后缀.
4.文件类型
根据数据的组织形式,数据文件被分为文本文件和二进制文件。字符一律以ASCII值形式存储,而数字可以以ASCII值形式存储,也可以以二进制形式存储。
4.1 二进制文件
数值在内存中以二进制的形式存储,如果不加转换的输出到磁盘上,就是二进制文件。例如:
这个文件就是二进制文件,打开后是乱码,咱们是看不懂的。如果想要看懂,则需要专门读二进制文件的工具才可以。
4.2 文本文件
所谓的文本文件就是打开后咱们可以看懂的文件,里面放的是字符。换句话说:如果要求在磁盘上以ASCII值的形式存储,则需要在存储前转换。数值以ASCII字符的形式存储的文件就是文本文件。例如:
这个文件咱们可以看懂,是文本文件。
5.文件缓冲区
为了避免在使用文件时,反复调用操作系统(调用次数过多会减低计算机的性能)完成数据的输入和输出。ANSIC标准采用“缓冲文件系统”处理数据文件的,所谓缓冲文件系统是指系统自动地在内存中为每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,在刷新缓冲区(关闭文件或是调用刷新缓冲区的函数fflush)或是装满缓冲区后操作系统才把这些数据一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后操作系统再从缓冲区逐个地将数据送到程序数据区。缓冲区的大小根据C编译系统决定的。
要强调的一点是,途中的输入和输出是相对于程序数据区的。程序数据区称为内存,磁盘称为外存。数据从程序数据区传到磁盘上叫输出,从磁盘上传到程序数据区中叫输入。搞懂了这一点那么接下来与文件操作相关的函数就更容易理解一些。
6.文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中。该结构体类型是由系统声明的,取名FILE
例如:typedef struct _iobuf FILE
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
一般这个结构的起始地址通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
例如:FILE* pf;
7.文件的打开和关闭 fopen&&fclose
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC规定使用fopen函数来打开文件,fclose来关闭文件。
7.1fopen函数
函数定义:FILE* fopen(const char* filename,const char* mode);
在fopen打开文件的时候会返回一个FILE*类型的指针。它的第一个参数是 const char* filename 是一个内容为文件名的字符串。如“test.txt"。 第二个参数是 const char* mode 是一个内容为mode的字符串。 如"w"。 mode的含义是打开方式。
7.2相对路径
举个栗子:我们以相对路径,只写的方式打开文件test.txt
那么什么叫相对路径呢?相对路径就是相对于目前的这个代码文件的路径。这个代码文件的路径就是:
D:\崽崽C语言\daily-brush\data.cpp 意思就是D盘,崽崽C语言文件夹,daily-brush文件夹,data.cpp文件。而我们以相对路径打开的test.txt文件就是相对于data.cpp文件路径。换句话说就是把test.txt文件创建在data.cpp文件所在的文件夹中。
在程序运行前,我们可以看到,在daily-brush文件夹中有data.cpp文件没有test.txt文件。因为以"w"只写的方式打开的话,若文件夹中没有这个文件,操作系统会自动创建这个文件。若磁盘上已经有这个文件,并且里面有数据,系统会把原来的数据全部销毁。
而运行这段代码后,daily-brush文件夹会多出一个test文件,该文件的大小为0KB,是空文件,说明这是操作系统刚创建的文件。
7.3绝对路径
绝对路径更好理解一些,就是把要打开的文件路径完整的写出来。但是用绝对路径写的话要把\改成\\因为\往往会和其他字符组合成一个字符。如:\n \t \r 等。否则编译器会报警告!
我们再加上一个\后就可以消除警告。
当然,操作系统会在相应的路径下创建一个文件。
同样的,如果我们想把test.txt文件创建在data文件的上一级路径下呢?那我们直接给出具体的创建路径即可。
在这里可以看到,test.txt文件没有创建在daily-brush文件夹中,而于daily-brush文件夹同级别。创建在了崽崽C语言文件夹中。
7.4上一级路径&当前路径
.. 表示上一级路径。
. 表示当前路径。
这是相对路径写法中很重要的两个点。
7.4.1上一级路径
这种写法是把test.txt文件创建在了代码文件(data.cpp)的上一级路径底下。
程序运行后,我们发现,当前路径下(daily-brush文件中)没有test.txt文件。那我们往上一级路径查找。
在上一级路径中,找到了test.txt 文件。
7.4.2当前路径
当前路径就很容易理解,我就简单介绍一下叭。
与
的含意完全相同(表示代码文件的上一级路径的当前路径)。
与
的含义完全相同(表示代码文件的当前路径)。
7.5文件打开成功与失败&fclose函数
当我们以"r"只读的方式打开文件,而磁盘上又没有这个文件时,会打开失败,fopen会返回一个空指针NULL。所以我们每次调用fopen函数时,需要判断文件是否打开成功。
当文件打开并且执行完相关操作后,需要用fclose关闭文件并把指针pf置为空指针。
8.文件操作相关函数
8.1 fputc&fgetc
其实c语言程序只要运行起来就会默认打开三个流:
1.标准输出流:stdout(屏幕)
2.标准输入流:stdin(键盘)
3.标准错误流:stderr
本文章只讨论标准输出流与标准输入流
所有输入流分为文件流和标准输入流,所以输出流分为文件流与标准输出流。
我们常见的printf与scanf函数是针对标准输出流(屏幕)与标准输入流(键盘)的。
上文中也提到过,所谓的输入与输出是相对与内存的。磁盘与屏幕都叫外存。所以我们把数据传到屏幕上或磁盘上都叫输出。
8.1.1 fputc
fputc 是 file output char 的所以,意思是向指定的文件(或标准输出流--键盘)中写入一个字符。
fputc适用于所有流,每次只能写入一个字符。
它有两个参数,第一个参数是要写入的字符,第二个参数是对应的流(文件流或标准输出流)。返回值是该字符的ASCII值。
当我们把字符写入文件流时:
当fputc每次写入一个字符时,光标会向后移动一位,然后继续写入字符。后面要讲的函数都是如此。
当我们把字符写入标准输出流(屏幕)时:
8.1.2 fgetc
fgetc 是 file get char 的缩写,意思是从指定的文件中(当然也可以是标准输入流--键盘)读取一个字符。
该函数只有一个参数 就是对应的流。返回值是对应字符的ASCII值。
如图,这是从文件流中输入数据到a中。
如图,这是从标准输入流(键盘)中输入数据到a中。
8.2 fputs&fgets
8.2.1 fputs
这是fputs的原型,适用于所有流,作用是读取一行字符串。
它有两个参数第一个参数是一个字符串的首字符地址,第二个参数是相应的流(文件流,标准输出流)。把字符串输出到对应的流中去。
若字符串输出成功,则返回一个非负数。若字符串输出失败,则返回EOF(本质为-1),并且设置一个错误状态可用ferror来检测。
先介绍把字符串输出到标准输出流是怎样的情形叭:
下面是把字符串输出到文件流中:
8.2.2 fgets
这是fgets的原型,作用是在对应的流中(文件流,标准输入流)读取一个字符串并输入到str中。
它有三个参数:第一个参数是字符指针str。第二个参数是最多读入的字符个数,如果为n的话则读入n-1个字符,并且最多读一行,遇\n停止。第三个参数是相应的流(文件流,标准输入流)。
返回值:读取成功时返回字符数组首地址,也即 str;读取失败时返回 NULL;如果开始读取时文件内部指针已经指向了文件末尾,那么将读取不到任何字符,也返回 NULL。
那我们来动手敲一敲叭,标准输入流:
用标准输入流(键盘)输入, 我们可以看到,我用键盘输入了11个字符I love bit.(空格也算一个字符),但只9(10-1)个字符输入到了ch中。
文件流:
我在test.txt文件中,把hello和world分别放到两行中,再运行代码。
我们可以看到,程序只是把hello还有\n(文件第一行最后有换行发\n)写入了ch中。
8.3 fprintf&fscanf
8.3.1fprintf
为了很好的理解fprintf这个函数,我们把它和printf做对比,发现fprintf只比printf多一个参数。就是第一个参数:流。当把标准输出流传参给fprintf的时候,其作用与printf作用相同。
fprintf&printf返回值:正确返回输出的字符总数,错误返回负值,与此同时,输入输出流错误标志将被置值,可由指示器ferror来检查输入输出流的错误标志。
标准输出流:
如图,加上换行符就是11个字符。
文件流:
8.3.2 fscanf
同样的,我们把fscanf和scanf做对比。其实,依然只有流不一样。
标准输入流:
标准输出流:
将文件中的内容输入到结构体中。
8.4 fread&fwrite
值得注意的是:这两个是专门针对文件流的二进制的读和写的函数。不适用于标准输入和输出流!
8.4.1 fwrite
该函数有四个参数。
const void *ptr : 指针指向要写出数据的内存首地址 ;
size_t size : 要写出数据的基本单元的字节大小 ;
size_t nmemb : 要写出数据的基本单元的个数 ;
FILE *stream : 打开的文件指针 ;
返回值说明 : size_t返回值返回的是实际写出到文件的基本单元个数 ;
给大家来个简单的应用:
我们发现以二进制的形式读入这个数组咱们看不懂。没有关系,我们再用二进制的方式把它读出来就好了。
8.4.2 fread
其实fread和fwrite的参数与返回值是一样的。只不过fread是从文件中读取数据到buffer中。
虽然看不到文件中的内容,但是我们以二进制读的形式把文件中的内容转换为我们可以看懂的数据。
8.5 sprintf&sscanf
这两个函数就和文件没有半毛钱关系了。前者是把格式化的数据转换成字符串,后者是把字符串转换为格式话的数据。
8.5.1 sprintf
它和printf 只有一处不一样,就是它的第一个参数是一个字符指针char* str。作用是把格式化数据转换成字符串。
以下是该函数的简单应用:
8.5.2 sscanf
同样的,sscanf与scanf依然只有一处区别:sscanf的第一个参数是字符串const char* str,作用是把字符串转换成格式化数据。
下面是该函数的简单应用:
但是要注意的是字符串中的内容要与结构体成员相匹配:
%f %d %c的顺序不可以调换。
8.6 fseek
fseek函数的讲解用下面一张图片便可以解决:
我们来简单得用一用这个函数:
先把test.txt文件里面放abcde五个字符备用。
通过代码的简单应用,我们发现fseek函数就是用来改变文件中光标的位置的。
8.7 ftell&rewind
8.7.1 ftell
ftell函数比较简单,只有一个参数。它的作用是求出文件中光标已经向后移动了几位。因为比较简单,我就不再给大家演示了。相信聪明的宝子们可以自己动手搞懂这个函数!
8.7.2 rewind
而rewind函数也是很简单的。它的作用是将文件中的光标回到起始位置。咱们可以拿它和fseek与ftell函数做对比,这三者有很大的关联性。
9.结语:
有关文件操作的内容到这里大致结束了,考虑到大家对本章相关的概念理解的不是很清楚,我会在接下来的几篇文章中围绕文件操作的相关知识进行讲解来帮助大家理解并掌握文件以及相关的函数!
当然,若本文有表达不清晰或是逻辑错误的地方欢迎各位技术大佬指正!