• C语言文件操作


    目录

    1.什么是文件

            1.1文件名

            1.2程序文件

            1.3数据文件

    2.二进制文件和文本文件

            二进制文件的写入测试

    3.文件的打开和关闭

            4.1流和标准流

            4.1.1流

            4.1.2标准流

            4.2文件指针

            4.3文件的打开和关闭

    5.文件的顺序读写

            5.1文件的顺序读写函数

            5.2文件读写函数的使用

            5.2.1  fgetc和fputc函数

            5.2.2 fgets和fputs函数

            5.2.3 fscanf和fprintf函数

            5.2.4 fread和fwrite函数

    6.文件的随机读写

            6.1 fseek 函数

            6.2 ftell函数

            6.3 rewind函数


    1.什么是文件

            一般的说,我们磁盘(硬盘)上的文件就是我们常说的文件。

            但是在程序设计中,我们所说的文件有更加精细的划分,根据文件功能来划分,可以分成程序文件和数据文件两种。

            1.1文件名

            像我们人一样,为了区分文件,程序一般也有它们的文件名,并且在同一路径下,是不允许存在一模一样的文件名(路径不一样另说)。

            文件名的组成是  文件路径+文件名主干+文件后缀

            例如:c: \ code \ today \main.txt

            像 c: \ code \ today  这样的就叫做文件路径,文件路径一般是不限制长度的,所以你要把文件路径写很长也是可以的,c: 表示的就是它在c盘的路径下,剩下的都是这个路径下的文件夹名称。

            像 main 就是这个文件的文件名主干,而 txt 就是这个文件的文件后缀,与main之间用 .隔开,表示这个文件的性质,txt就表示这个main文件是文本文件。

            一般来说,每个文件都有它的文件后缀,没有文件后缀的一般只有文件夹了,文件夹不被称为我们这里提到的“文件”,它算是一种存放文件的集合

            1.2程序文件

            一般我们在写c语言代码时候,会创建一个源文件(后缀一般为 .c),编译我们的源文件之后会产生目标文件(windows环境下为.obj),最后产生可执行程序(windows环境下后缀为.exe)。上面所说的全部文件都称为我们的程序文件

            1.3数据文件

            我们所写的程序在运行时读写的数据都可以称作是数据文件,比如程序运行时需要从这个文件里读取数据或者输出内容,那么这个文件就是我们所说的数据文件。这里我们主要就对这个数据文件进行讨论。

    2.二进制文件和文本文件

            根据数据的组织形式,数据文件又细分为文本文件和二进制文件。

            数据在内存中以二进制的形式存储,它就叫做二进制文件;如果以ASCII码的形式存储,那么这个文件就叫做文本文件。

            这里用一个整数10000来举例,用ASCII码的形式存储,就是把10000分成1 0 0 0 0 五个数字分别用ASCII 码形式存储,一个字符占一个字节,用二进制形式存储,就占4个字节(一个 int 的大小)

            二进制文件的写入测试

    这里用一个测试代码可以写出一个把整数10000以二进制存储到文件中:

    1. #include
    2. int main()
    3. {
    4. int a = 10000;
    5. FILE* pf = fopen("text.txt", "wb");
    6. fwrite(&a, 4, 1, pf);
    7. fclose(pf);
    8. pf = NULL;
    9. return 0;
    10. }

    产生的文件会在main.c源文件的同一文件夹中

    然后查看一下我们写的文件:右击源文件,点击添加->现有项

    添加之后就右击test.txt->打开方式->二进制编辑器 就可以看到我们的文件内容了

    这里10 27 00 0

      0就是存储的10000的二进制值,顺序是反过来的,因为采用的是小端存储的方式。

    3.文件的打开和关闭

            4.1流和标准流

            4.1.1流

            我们的程序需要外部设备输入信息,同时也会向外部设备发送信息,这个其实是一个很复杂的过程,为了方便我们写代码,我们抽象出一个流的概念,类似于你在影视作品中看到的场景,一个黑客,戴着一个面具,面前是一个流淌的数据之河。我们可以把流看成一个个数据在流淌的河,C语言对于文件、画面、键盘的数据输入都是用流操作的。

            一般来说,我们要在流里面写数据,或者从流中读取数据,都要打开流,然后再操作。

            4.1.2标准流

            我们一般使用scanfprintf 两个函数在屏幕上进行数据的输入和输出的时候,好像也没有打开什么流啊?那是因为c语言在启动的时候就默认帮我们打开了3个流:

            这三个流就是我们所说的标准输入流,这个时候我们才可以使用scanfprintf 两个函数直接使用输入和输出操作。其中,stdin 、stdout 、stderr 三个流的类型都是FILE* ,通常称为文件指针

    C语言中我们就是使用FILE*指针来维护流的各种操作的。

            4.2文件指针

            在头文件封装的还没有那么复杂的VS2013中,在stdio.h的头文件中对 FILE 有下面的定义:

    1. struct _iobuf{
    2. char* _ptr;
    3. int _cnt;
    4. char* _base;
    5. int _falg;
    6. int _file;
    7. int _charbuf;
    8. int _bufsiz;
    9. char* _tmpfname;
    10. };
    11. typedef struct _iobuf FILE;

            上面这段代码其实就是对FILE的一个定义,它先是定义了一个结构体_iobuf,然后再把它重定义成FILE类型,所以,我们其实可以知道,FILE其实是一个结构体类型,它的内部储存的是一串数据。在我们创建一个文件date.txt的时候,我们就可以创建一个 FILE 类型的变量用来把date.txt文件的信息存在这个FILE类型结构体中,然后我们再定义一个指向这个FILE类型的指针,就可以管理这个结构体,同时也就管理了整个date.txt文件。

            而我们一般在使用时不用关心这些细节,我们创建文件的时候就会产生FILE类型的结构体,这个结构体会把这个文件的所有信息都存储起来,我们就可以使用FILE* 类型的文件指针对这个文件进行管理。

            上面我们使用一个图示可以对这个过程进行一个大致的观察,我们一般就只需要使用文件指针直接管理这个文件,中间的过程我们省略掉,也同时被抽象成一个“流”的概念,也就是说,我们只这样通过一个文件指针pf就可以直接找到和它相关联的文件,然后再进行维护。

            4.3文件的打开和关闭

            我们读写一个文件的时候,就类似于我们喝一瓶饮料的过程,我们要喝饮料,第一步,打开瓶盖,第二步,进行喝饮料的操作,第三步,盖上瓶盖。同样的,我们读写文件也可以分成这样的操作,第一步,打开文件,第二步,进行读写操作,第三步,关闭文件。

            然后再具体到每个操作。标准规定,我们需要使用fopen函数打开文件,使用fclose函数关闭文件。

    1. //打开文件
    2. FILE * fopen ( const char * filename, const char * mode );
    3. //关闭文件
    4. int fclose ( FILE * stream );

            其中,mode表示打开文件的模式,有下面的几种打开文件的模式:

    我们可以总结一下,方便记忆:有三个位可以考虑:

    第一个位:r \ w \ a 

                    r  表示读,w 表示写 , a表示在文件尾追加(r和w都是针对整个文件,a偏向于对文件尾部进行操作)

                  首位为  r :看文件是否存在,文件不存在就报错,否则就可以正常进行读取操作

                  首位为 w \ a :看文件是否存在,文件不存在就新建文件,否则就进行正常写 \ 追加操作

    第二个位:无 \ b

                    第二个位如果没有就表示默认为文本文件,如果有就应该是b,表示文件是二进制文件

    第三个位:无 \ +

                    第三个位用来表示是否通讯进行读写,如果第三位没有就只能进行第一位的操作,如果第三位有+就表示可以对文件一次性进行 读、写 两种操作

    就大概是上面的几种操作了,下面我们可以使用实例进行演示:

    1. #include
    2. int main()
    3. {
    4. //创建一个文件指针
    5. FILE* pf;
    6. //打开文件,并使用文件指针pf接收fopen函数返回值
    7. pf = fopen("data.txt", "w");//这里是进行对文本文件只写的操作,由于没有创建过,这里会创建一个新的文本文件
    8. if (pf != NULL)//再次判断是不是成功创建了这个文件
    9. {
    10. fputs("Hello World!", pf);//写入数据
    11. fcolse(pf);//关闭文件
    12. pf = NULL;//把指针置为空,防止它成为野指针
    13. }
    14. return 0;
    15. }

    程序运行成功之后我们就可以在和main.c源文件所在的同一文件夹中找到data.txt文件了。

    5.文件的顺序读写

            5.1文件的顺序读写函数

            刚刚我们介绍了怎么打开文件和关闭文件,但是没有讲打开文件和关闭文件中间我们需要的操作:写入和读取文件,刚刚的举例中,我们也使用了一个函数:fputs,用来放入“Hello World”这个字符串,类似于我们平时在控制台上使用的函数,我们对文件的函数其实就是大同小异的,只是我们在控制台上使用的函数一般只能用来使用控制台上的操作(标准输入流)。这里我们使用的对文件操作函数大多是适用范围更广的函数:

    所有输入输出流是包含标准输入输出流的,所以我们也可以在标准输入流里使用这些函数

            5.2文件读写函数的使用

            5.2.1  fgetc和fputc函数

           1) fgetc函数的原型如下:

    int fgetc(FILE *stream);

            函数返回一个整数,如果成功读取,这个函数会返回所读到的字符的ASCII码值,如果读取失败(来到文件末尾),就返回值EOF。

           2) fputc函数的原型如下:

    int fputc(int c, FILE *stream);

            fputc函数的输入除了流,还有一个输入的字符(用ASCII码表示),返回值就是这个输入成功的字符的ASCII码值,如果发生错误,比如文件已关闭,就返回-1。

            5.2.2 fgets和fputs函数

            1)fgets函数的原型如下:

    char *fgets(char *str, int n, FILE *stream);

            其中这个str是是指向存储读取的字符串的指针,n是最大读取的字符数量(这里包含‘\0’终止符,所以其实最大获取字符数量应该是n-1)

            函数停止的条件有两个,达到最大字符数或遇到换行符

            函数成功读取就会返回字符串的首元素地址,否则返回空指针NULL。

           2)fputs函数的原型如下:

    int fputs(const char *str, FILE *stream);

            fputs函数停止的条件是遇到终止符'\0' ,或者遇到文件末尾或换行符‘\n’,所以使用fputs的时候要确认字符串是不是有‘\0’终止符。

            fputs有很多需要注意的点,比如在“w”只写模式下使用fputs会覆盖之前的内容,所以需要使用追加模式“a”防止把之前的内容覆盖。

    fputs函数的返回值有下面4种:

    • 成功写入:如果fputs函数成功地将字符串写入文件,并且文件没有被关闭或写入失败,则返回一个非负值。这个非负值通常表示写入的字符数。
    • 文件打开失败:如果文件打开失败,fputs函数将返回一个负值。通常,这表示出现了错误或异常情况。
    • 写入空字符串:如果写入空字符串,fputs函数将返回0,表示已经成功写入空字符串。
    • 遇到文件末尾:fputs函数遇到文件末尾返回值为EOF,这个EOF的值一般为-1

            5.2.3 fscanf和fprintf函数

           1) fscanf函数的原型如下:

    int fscanf(FILE *stream, const char *format, ...);

            这个函数的和scanf函数很类似,只是在前面加上流的类型这个变量,比如,你可以类比函数scanf,基本上是一样的。

            其中format是一个格式化字符串,用于指定要读取的数据的类型和格式,其实就是我们所说的占位符,比如整形%d,字符串%s...

    (两个相邻的占位符之间一般用空字符隔开,否则需要输入内容也存在该字符\字符串)

            返回值一般是一个正整数,表示成功读取的元素数量,或者是输入结束(也可能是发生错误)返回EOF

            2)fprintf函数的原型如下:

    int fprintf(FILE *stream, const char *format, ...);

            同样的,这个函数比printf函数只是多了一个流,format依然是格式化字符串,只是还可以有其他的字符串(也就是说可以是字符串+占位符)。返回值依旧是正整数和EOF两个情况。

            5.2.4 fread和fwrite函数

            不同于上面的函数,fread和fwrite函数只能对文件有作用,并且一般用在后缀为bin的二进制文件,不然可能会出现编码错误的问题;然后就是打开文件的时候要使用带b的二进制模式。

            1)fread函数原型如下:

    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

    参数:

    • ptr:指向要读取数据的缓冲区的指针。这个缓冲区通常是一个数组,用于存储从文件中读取的数据。
    • size:要读取的数据的大小(即缓冲区类型大小)。
    • nmemb:要读取的数据项的数量。
    • stream:指向要读取的文件的指针。

    函数返回值:

    • 如果成功,fread 返回实际读取的数据项的数量。
    • 如果出现错误,返回 0 或比实际读取数量少的值。

            注意如果算读取二进制数据,就要把文件名后缀改成.bin,并且在打开文件时就要使用“rb”等模式。

            2)fwrite函数原型如下:

    size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

            这里的参数和fread函数的基本一致,返回值也是参考fread函数

    6.文件的随机读写

            准确的说,这个应该不算是随机读写,更像是指定读写

            这里介绍下面几个函数:fseek ,ftell ,rewind 

            6.1 fseek 函数

            fseek 函数的原型如下:

    int fseek(FILE *stream, long offset, int whence);
    • stream参数是一个指向已打开文件指针,该文件指针应使用fopen函数打开。
    • offset参数是偏移量,表示要移动的字节数。
    • whence参数指定了起始位置,可以是SEEK_SET(从文件开头)、SEEK_CUR(从文件开头)或SEEK_END(文件末尾)。

            6.2 ftell函数

            ftell 函数原型如下:

    long ftell(FILE *stream);

            ftell函数返回当前文件指针在文件中的位置(以字节数为单位)。通常情况下,ftell函数可以帮助确定文件指针的当前位置,以便进一步进行文件操作或记录文件的读写位置。

            其次,ftell 函数的返回值类型是long (等价于 long int),这里在定义变量接受函数返回值的时候需要注意。

            6.3 rewind函数

            rewind函数原型如下:

    void rewind(FILE *stream);

            rewind函数会将文件指针移动到文件的起始位置,即相当于调用了fseek(stream, 0, SEEK_SET);。它的作用是将文件指针重置为文件开头,以便重新开始对文件的读取或写入。

  • 相关阅读:
    MATLAB算法实战应用案例精讲-【图像处理】目标检测(附实战案例及代码实现)
    Redis学习笔记①基础篇_Redis快速入门
    手把手改进yolo训练自己的数据(坑洼路面识别)
    37岁生日快乐哦:说点大实话
    压缩包密码可以删除吗?
    4年用户数破亿,孙哥带领波场再创新高
    【计算机网络】HTTP(上)
    Java开发者的Python快速实战指南:探索向量数据库之图像相似搜索-文字版
    c语言进阶部分详解(详细解析字符串常用函数,并进行模拟实现(下))
    个性化纹身设计,Midjourney带你探索独一无二的艺术之美
  • 原文地址:https://blog.csdn.net/2302_80442755/article/details/136312831