大家号,今天和大家说一说C语言中的文件操作。
在电脑的硬件中有外存和内存之分,外存通常很大,也就是我们常说的磁盘,今天所说的文件就是存储在外存中的。有时在C程序运行过程中,产生的数据我们想永久的保存在电脑中,而不是运行结束后数据就丢失了,比如我们写一个通讯录的程序,程序运行结束后我们想把通讯录中的数据保存下来,以便下次运行程序时可以找到这些数据。而由于外存的访问速度很慢,所以运行程序时数据都是在存储在内存中的(CPU访问内存的速度要比外存快很多),但运行结束后内存就还给了操作系统,使得数据无法永久保存,这时,就要用到今天的知识——文件操作,就是把数据从内存存储到外存中(和从外存中读取数据到内存中的操作),以便数据的保存即后续的使用。
1.关于文件
首先讲一讲文件相关的知识。
在程序设计中,我们通常只区分程序文件和数据文件。程序文件就是保存程序代码的文件,包括了源文件(如test.c),头文件(test.h),目标文件(源代码编译后生成的代码文件,windows中为test.obj)和可执行程序(test.exe)。
而数据文件的内容不一定是程序(也可能是程序),它是指程序运行时从中读写数据的文件,今天讲的文件操作就是指的这部分文件。
文件通常都有文件名,这是文件的唯一标识,用于查找、使用该文件,文件名包含三部分:文件路径、文件名主干和文件后缀,如:c:\c-language\2.28\test.c。其中,c:\c-language\2.28\就是文件路径,test是文件名主干,.c是文件后缀。
根据数据的组织形式,数据文件又被区分为文本文件和二进制文件。在内存中数据都是以二进制形式存储的,如果不经过转换直接存到外存中就是二进制文件,而如果在存到外存的时候加以转换,以ASCII码的形式存储,那么该文件就是文本文件。需要注意的是,如果以二进制文件存储到外存中,我么通常是看不懂的,因为记事本等工具是以文本形式打开文件的,而文件为二进制形式,这是就会的到一堆乱码。
最后讲一件文件缓冲区,在ANSIC标准中,采用缓冲文件系统来操作文件,系统自动地在内存中为程序中每一个正在使用的文件开辟一块文件缓冲区,当从内存向外存输出数据时会先输出到输出文件缓冲区等到缓冲区装满后一次性输出到外存中;从外存中读入数据时是一样的,会先将数据从外存中读入到输入缓冲区,代缓冲区装满后一次性输入到内存中。至于缓冲区的大小则又编译系统决定。缓冲区在外存和内存之间架起了一个桥梁,可以减少磁盘的读取次数,提高计算机的效率。
2.文件的打开和关闭
下面开始介绍文件的具体操作。
在缓冲文件系统中,有一个叫做“文件类型指针”的东东,这是一个结构体指针,指向的结构体称为文件信息区,(每个被使用的文件都在内存中开辟了一个相应的文件信息区,存放了该文件的相关信息),该结构体类型是由系统声明的,取名为FILE。这样我们就可通过该文件指针来维护、操作FILE结构的变量,使用起来更加方便。
例如: FILE* pf;
这里的pf就是一个文件指针变量,可以使pf指向某个文件的文件信息区,通过该文件信息区中的信息就能够访问该文件,即通过文件指针变量能够找到与它关联的文件。
在操作文件前以及结束后需要手动打开和关闭文件,FILE* fopen (const char* filename, const char* mode)函数是用来打开文件的,int fclose( FILE* stream)函数是用来关闭文件的,具体如下:
fopen函数的第二个参数是打开方式的意思,就是我打开该文件要干什么,具体有一下几种:
这里的输入是指从外存中读取数据到内存中,输出是指从内存中输出到外存中。
注意在打开文件的过程中有可能会失败(例如以“r”的方式打开一个不存在的文件),所以在打开文件后我们要对文件指针pf进行判断,判断其是否是NULL,可以使用assert断言,也可以用if语句。
3.从文件中读取数据以及输出数据到文件中
打开文件之后就可以读入数据或将数据输出到文件中了。具体有顺序读写和随机读写两种。
3.1顺序读写
顺序读写就是从前向后顺序读数据或写数据,涉及到顺序读写的函数有如下几对:
同样的,这里的输入是指从外存中读取数据到内存中,输出是指从内存中输出到外存中。
下面我们成对的简单了解一下这些函数。
1.fgetc与fputc(字符)
fgetc: int fgetc (FILE* stream), 该函数从stream流中顺序读取一个字符到内存中。通过上图可知,该stream可以是任何流,也就是说不仅仅可以输出到磁盘中,通过改变该参数,还可以输出到其他流中,如标准输出流stdout(屏幕)。(下同)
fputc: int fputc (int character, FILE* stream), 该函数将一个字符顺序输出到stream流中。
2.fgets与fputs(字符串)
fgets: char* fgets (char* str, int num, FILE* stream), 该函数从stream流中顺序读取一个字符串到内存中(存到str指向的内存),num为读取字符串的最大长度。
fputc: int fputc (const char* str, FILE* stream),该函数将内存中str指向的字符串读入到stream流中。
3.fscanf与fprintf(格式化)
fscanf: int fscanf (FILE* stream, const char* format...), 这个函数和scanf的用法几乎一样,只是多了第一个参数stream,用来规定从哪个流读入数据,也就是说,这个函数比scanf更强大。(scanf只能从标准输入设备(键盘)读入数据)
fprintf: int fprintf (FILE* stream, const char* format), 和fscanf与fscanf的区别一样。
补充两个与文件不相关的函数:sscanf与sprintf.
sscanf: int sscanf (const char* p, const char* format...), 该函数从字符串中读取格式化的数据。
sprintf: int sprintf (const char* p, const char* format...), 该函数将格式化的数据转换成字符串。
(注意比较这三对格式化输入输出函数)
4.fread与fwrite(二进制)
fread:size_t fread (void* ptr, size_t size, size_t count, FILE* stream), 该函数只能从文件中以二进制的形式读取数据,读入count个元素到ptr指向的空间中,每个元素的大小为size个字节。必须注意这两个函数都是以二进制的形式输入和输出。
fwrite: size_t fwrite (const void* ptr, szie_t size, szie_t count, FILE* stream), 该函数将ptr指向的空间中的count个大小为size个字节的元素输出到文件中。
3.2随机读写
文件不仅可以顺序读写,还可以随机读写,这里的随机是说可以从任意位置读写数据。
在文件读写时文件指针是随着移动的,顺序读写就是随着文件指针的移动读写数据,而下面这几个函数可以改变文件指针的位置,达到读写任意位置数据的目的。
1.fseek
函数原型:int fseek (FILE* stream, long int offset, int origin), 该函数是根据文件指针的位置和偏移量来定位文件指针。 第三个参数origin有三个值:(1)SEEK_SET:表示文件起始位置;(2)SEEK_CUR:表示当前文件指针的位置;(3)SEEK_END:表示文件的末尾(最后一个字符的后面)。该函数将文件指针定位到这三个规定的位置(其中之一)开始偏移offset(正数向后,负数向前)个字节的位置。
2.ftell
函数原型:long int ftell (FILE* stream), 该函数可以计算当前文件指针相对于起始位置的偏移量(返回值)。
3.rewind
函数原型:void rewind (FILE* stream), 该函数可以使文件指针的位置回到文件的起始位置。
4.如何判断文件读取正常结束
当文件读取结束时可能是正常结束,也有可能是读到了文件末尾而结束,还有可能是读取错误而结束,第一种称为正常结束,后两种称为非正常结束。
针对文本文件,可以通过判断函数的返回值是否为EOF(fgetc),或NULL(fgets)来判断是否是正常结束,如果是非正常结束,那返回值就是EOF或NULL。这时再用ferror函数判断是否为读取错误,用feof函数判断是否遇到了文件末尾。
而二进制文件读取是否是非正常结束可以判断fread函数的返回值(实际读取到的元素个数)是否小于要读取的元素个数(fread函数的第三个参数),若正常结束这两个值应该相等。
然后再用函数ferror和函数feof判断。
好了,以上就是C语言中关于文件操作的简单介绍,水平有限,如有不足之处,还请大家在评论区留言,如果您觉的文章对您有用,也不妨点歌在加关注,谢谢。