• C语言文件操作


    目录

    为什么使用文件

    什么是文件

    1.程序文件

    2.数据文件 

    3.文件名

    文件的打开和关闭

    1.文件指针

    2.文件的打开和关闭 

    文件的顺序读写 

    1.对比一组函数 

    文件的随机读写

    1.fseek

             2.ftell

             3.rewind

    文本文件和二进制文件

    文件读取结束的判定

    1.被错误使用的feof

    文件缓冲区 

    结语


    为什么使用文件

      许多程序在实现过程中,依赖于把数据保存到变量中,而变脸是通过内存单元存储数据的,数据的处理完全由程序控制。当一个程序运行完成或终止运行,所有的变量的值不再保存。另外,一般的程序都会有数据的输入与输出,如果输入输出数据不大,通过键盘和显示器即可解决。当输入输出量较大时,就会受到限制,带来不便。

      文件是解决上述问题的有效办法,它通过把数据存储在磁盘文件中,得以长久保存。当有大量数据输入是,可通过编辑工具事先建立输入数据的文件,程序运行时将不再从键盘输入,而从指定的文件上读入,从而实现数据一次输入多次使用。同样,当有大量数据输出时,可以将其输出到指定文件,不受屏幕大小限制,并且任何时候都可以查看结果文件。一个程序的运算结果还可以作为其他程序的输入,进行进一步加工。

    什么是文件

    磁盘上的文件是文件。但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。

    1.程序文件

    包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。

    2.数据文件 

    文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。

      本篇博客讨论的是数据文件。在以前的博客中所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。

    3.文件名

      一个文件要有一个唯一的文件标识,以便用户识别和引用。文件名包含3部分:文件路径+文件名主干+文件后缀,例如: c:\code\test.txt。为了方便起见,文件标识常被称为文件名。

    文件的打开和关闭

    1.文件指针

      缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE。

      例如,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明: 

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

      注意:不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。

      下面我们可以创建一个FILE*的指针变量: 

    FILE* pf;//文件指针变量

      定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。 

     

    2.文件的打开和关闭 

      文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。想要打开文件,我们就不得不要了解一个fopen函数了,其函数原型如下:

    FILE * fopen ( const char * filename, const char * mode );

     

      fclose函数原型如下:

    int fclose ( FILE * stream );

     

    代码示例: 

    1. #include
    2. int main()
    3. {
    4. FILE* pf = fopen("test.txt", "w");
    5. if (pf == NULL)
    6. {
    7. perror("fopen");
    8. return 1;
    9. }
    10. //写文件
    11. //关闭文件
    12. fclose(pf);
    13. pf = NULL;
    14. }

      注意:以"w"的形式打开文件,如果文件不存在,将会新建一个文件;但如果文件存在,将会清空文件的内容。

    代码示例:

    1. #include
    2. int main()
    3. {
    4. FILE* pf = fopen("test.txt", "r");
    5. if (pf == NULL)
    6. {
    7. perror("fopen");
    8. return 1;
    9. }
    10. //写文件
    11. //关闭文件
    12. fclose(pf);
    13. pf = NULL;
    14. }

    输出结果: 

    详细的打开方式: 

      当程序和文件的不在同一个文件夹中,如果你还想打开这个文件的话,就要通过文件的绝对路径来打开文件了。

    相对路径: 

    D:\bite\dont-sleep-until-you-learn\test_7_23\test.txt

    绝对路径: 

    D:\\bite\\dont-sleep-until-you-learn\\test_7_23\\test.txt

      值得注意的是:我们使用相对路径是打不开文件的,因为文件路径的一些部分被解析成转义字符了,所以我们要通过绝对路径来打开文件。

    代码示例1:

    1. #include
    2. int main()
    3. {
    4. FILE* pf = fopen("D:\bite\dont-sleep-until-you-learn\test_7_23\test.txt", "r");
    5. if (pf == NULL)
    6. {
    7. perror("fopen");
    8. return 1;
    9. }
    10. //写文件
    11. //关闭文件
    12. fclose(pf);
    13. pf = NULL;
    14. }

    输出结果:

     代码示例2:

    1. #include
    2. int main()
    3. {
    4. FILE* pf = fopen("D:\\bite\\dont-sleep-until-you-learn\\test_7_23\\test.txt", "r");
    5. if (pf == NULL)
    6. {
    7. perror("fopen");
    8. return 1;
    9. }
    10. //写文件
    11. //关闭文件
    12. fclose(pf);
    13. pf = NULL;
    14. }

    输出结果: 

     

    文件的顺序读写 

     

      

      fputc函数的原型如下:

    int fputc ( int character, FILE * stream );

     

    代码示例1: 

    1. #include
    2. int main()
    3. {
    4. FILE* pf = fopen("test.txt", "w");
    5. if (pf == NULL)
    6. {
    7. perror("fopen");
    8. return 1;
    9. }
    10. //写文件
    11. fputc('z', pf);
    12. fputc('z', pf);
    13. fputc('h', pf);
    14. fputc('a', pf);
    15. fputc('n', pf);
    16. fputc('g', pf);
    17. fputc(' ', pf);
    18. fputc('j', pf);
    19. fputc('o', pf);
    20. fputc('y', pf);
    21. //关闭文件
    22. fclose(pf);
    23. pf = NULL;
    24. return 0;
    25. }

    输出结果: 

    代码示例2: 

    1. #include
    2. int main()
    3. {
    4. fputc('z', stdout);
    5. fputc('z', stdout);
    6. fputc('h', stdout);
    7. fputc('a', stdout);
    8. fputc('n', stdout);
    9. fputc('g', stdout);
    10. fputc(' ', stdout);
    11. fputc('j', stdout);
    12. fputc('o', stdout);
    13. fputc('y', stdout);
    14. return 0;
    15. }

    输出结果:

      fgetc函数的原型如下: 

    int fgetc ( FILE * stream );

     

    代码示例1: 

    1. #include
    2. int main()
    3. {
    4. FILE* pf = fopen("test.txt", "r");
    5. if (pf == NULL)
    6. {
    7. perror("fopen");
    8. return 1;
    9. }
    10. //使用fgetc函数从文件读取数据
    11. int ch = 0;
    12. while ((ch = (fgetc(pf)))!=EOF)
    13. {
    14. printf("%c", ch);
    15. }
    16. //关闭文件
    17. fclose(pf);
    18. pf = NULL;
    19. return 0;
    20. }

    输出结果: 

    代码示例2:  

    1. #include
    2. int main()
    3. {
    4. int ch = 0;
    5. //使用fgetc函数从标准输入流中读取信息
    6. ch = fgetc(stdin);
    7. printf("%c\n", ch);
    8. ch = fgetc(stdin);
    9. printf("%c\n", ch);
    10. ch = fgetc(stdin);
    11. printf("%c\n", ch);
    12. return 0;
    13. }

    输出结果: 

      fputs函数的原型如下:

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

     

    代码示例: 

    1. #include
    2. int main()
    3. {
    4. FILE* pf = fopen("test.txt", "w");
    5. if (pf == NULL)
    6. {
    7. ferror("fopen");
    8. return 1;
    9. }
    10. //写文件-按照行来写
    11. fputs("zhangjoy\nzhangjoy\n", pf);
    12. //关闭文件
    13. fclose(pf);
    14. pf = NULL;
    15. return 0;
    16. }

    输出结果: 

      fgets函数的原型如下:

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

     

      注意: fgets函数遇到'\0'将不会继续往下读取。

    代码示例:

    1. #include
    2. int main()
    3. {
    4. char arr[20] = { 0 };
    5. FILE* pf = fopen("test.txt", "r");
    6. if (pf == NULL)
    7. {
    8. ferror("fopen");
    9. return 1;
    10. }
    11. //读文件-按照行来读
    12. fgets(arr, 7, pf);
    13. printf("%s\n", arr);
    14. fgets(arr, 9, pf);
    15. printf("%s\n", arr);
    16. //关闭文件
    17. fclose(pf);
    18. pf = NULL;
    19. return 0;
    20. }

    输出结果: 

      fprintf函数的原型如下: 

    1. //可变参数
    2. int fprintf ( FILE * stream, const char * format, ... );

     

      printf的函数原型如下: 

    1. //可变参数
    2. int printf ( const char * format, ... );

     

    代码示例: 

    1. #include
    2. struct S
    3. {
    4. char arr[10];
    5. int num;
    6. float f;
    7. };
    8. int main()
    9. {
    10. struct S s = { "zhangjoy",710,5.2f };
    11. //对格式化的数据进行写文件
    12. FILE* pf = fopen("test.txt", "w");
    13. if (pf == NULL)
    14. {
    15. perror("fopen");
    16. return 1;
    17. }
    18. //写文件
    19. fprintf(pf, "%s %d %f", s.arr, s.num, s.f);
    20. //关闭文件
    21. fclose(pf);
    22. pf = NULL;
    23. return 0;
    24. }

    输出结果: 

      fscanf函数的原型如下:

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

      scanf函数的原型如下:

    int scanf ( const char * format, ... );

    代码示例:

    1. #include
    2. struct S
    3. {
    4. char arr[10];
    5. int num;
    6. float f;
    7. };
    8. int main()
    9. {
    10. struct S s = { 0 };
    11. //对格式化的数据进行写文件
    12. FILE* pf = fopen("test.txt", "r");
    13. if (pf == NULL)
    14. {
    15. perror("fopen");
    16. return 1;
    17. }
    18. //读文件
    19. fscanf(pf, "%s %d %f", s.arr, &(s.num), &(s.f));
    20. //打印
    21. printf("%s %d %f", s.arr, s.num, s.f);
    22. //关闭文件
    23. fclose(pf);
    24. pf = NULL;
    25. return 0;
    26. }

    输出结果: 

     

      fprintf函数和printf函数的转化 fscanf函数和scanf函数的转化

    1. struct S
    2. {
    3. char arr[10];
    4. int num;
    5. float f;
    6. };
    7. fscanf(stdin, "%s %d %f", s.arr, &(s.num), &(s.f));
    8. <= = > scanf("%s %d %f", s.arr, &(s.num), &(s.f));
    9. fprintf(stdout, "%s %d %f", s.arr, s.num, s.f);
    10. <= = > printf("%s %d %f", s.arr, s.num, s.f);

      fwrite函数的原型如下:

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

     

    代码示例:

    1. //二进制的读写
    2. #include
    3. struct S
    4. {
    5. char arr[10];
    6. int num;
    7. float f;
    8. };
    9. int main()
    10. {
    11. struct S s = { "zhangjoy",710,5.2f };
    12. //以二进制的形式写
    13. FILE* pf = fopen("test.txt", "w");
    14. if (pf == NULL)
    15. {
    16. perror("fopen");
    17. return 1;
    18. }
    19. //写文件
    20. fwrite(&s, sizeof(struct S), 1, pf);
    21. //关闭文件
    22. fclose(pf);
    23. pf = NULL;
    24. return 0;
    25. }

    输出结果:

     

      fread函数的原型如下:

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

     

    代码示例:

    1. //二进制的读写
    2. #include
    3. struct S
    4. {
    5. char arr[10];
    6. int num;
    7. float f;
    8. };
    9. int main()
    10. {
    11. struct S s = { 0 };
    12. //以二进制的形式读
    13. FILE* pf = fopen("test.txt", "r");
    14. if (pf == NULL)
    15. {
    16. perror("fopen");
    17. return 1;
    18. }
    19. //读文件
    20. fread(&s, sizeof(struct S), 1, pf);
    21. //打印
    22. printf("%s %d %f", s.arr, s.num, s.f);
    23. //关闭文件
    24. fclose(pf);
    25. pf = NULL;
    26. return 0;
    27. }

    输出结果: 

    1.对比一组函数 

    scanf / fscanf / sscanf
    printf / fprintf / sprintf

      sprintf函数的原型如下:

    int sprintf ( char * str, const char * format, ... );

      

    代码示例:

    1. #include
    2. struct S
    3. {
    4. char arr[10];
    5. int age;
    6. float f;
    7. };
    8. int main()
    9. {
    10. struct S s = { "zhangjoy",23,5.2f };
    11. char buf[100] = { 0 };
    12. //sprintf函数将格式化的数据转化成字符串
    13. sprintf(buf, "%s %d %f", s.arr, s.age, s.f);
    14. printf("%s\n", buf);
    15. return 0;
    16. }

    输出结果: 

      sscanf函数的原型如下:

    int sscanf ( const char * s, const char * format, ...);

      

    代码示例:

    1. #include
    2. struct S
    3. {
    4. char arr[10];
    5. int age;
    6. float f;
    7. };
    8. int main()
    9. {
    10. struct S s = { "zhangjoy",23,5.2f };
    11. struct S temp = { 0 };
    12. char buf[100] = { 0 };
    13. //sprintf函数将格式化的数据转化成字符串
    14. sprintf(buf, "%s %d %f", s.arr, s.age, s.f);
    15. printf("%s\n", buf);
    16. //从字符串buf中还原出一个结构体数据
    17. sscanf(buf, "%s %d %f", temp.arr, &(temp.age), &(temp.f));
    18. printf("%s %d %f\n", temp.arr, temp.age, temp.f);
    19. return 0;
    20. }

    输出结果:

    文件的随机读写

    1.fseek

      如果我们向随机读写文件中的数据,是否可以呢?答案是可以的,我们需要借助一个函数fseek,它可以根据文件指针的位置和偏移量来定位文件指针,其函数原型如下:

    int fseek ( FILE * stream, long int offset, int origin );

     

    1. SEEK_CUR //文件指针目前的位置
    2. SEEK_END //文件末尾的位置
    3. SEEK_SET //文件起始的位置

      

    代码示例:

    1. #include
    2. int main()
    3. {
    4. FILE* pf = fopen("test.txt", "r");
    5. if (pf == NULL)
    6. {
    7. perror("fopen");
    8. return 1;
    9. }
    10. //读取文件
    11. int ch = 0;
    12. while ((ch = fgetc(pf)) != EOF)
    13. {
    14. printf("%c", ch);//zhangjoy
    15. //此时文件指针pf指向y后面的位置
    16. }
    17. //调整文件指针
    18. fseek(pf, -1, SEEK_CUR);
    19. ch = fgetc(pf);
    20. printf("\n%c\n", ch);//y
    21. fseek(pf, -1, SEEK_END);
    22. ch = fgetc(pf);
    23. printf("%c\n", ch);//y
    24. fseek(pf, 0, SEEK_SET);
    25. while ((ch = fgetc(pf)) != EOF)
    26. {
    27. printf("%c", ch);//zhangjoy
    28. }
    29. //关闭文件
    30. fclose(pf);
    31. pf = NULL;
    32. return 0;
    33. }

    输出结果:

     

    2.ftell

    返回文件指针相对于起始位置的偏移量

      函数原型如下: 

    long int ftell ( FILE * stream );

     

    代码示例:

    1. #include
    2. int main()
    3. {
    4. FILE* pf = fopen("test.txt", "r");
    5. if (pf == NULL)
    6. {
    7. perror("fopen");
    8. return 1;
    9. }
    10. //读取文件
    11. int ch = 0;
    12. //调整文件指针
    13. //zhangjoy
    14. fseek(pf, 0, SEEK_END);
    15. int ret = ftell(pf);
    16. printf("%d\n", ret);//8
    17. //关闭文件
    18. fclose(pf);
    19. pf = NULL;
    20. return 0;
    21. }

    输出结果: 

    3.rewind

    让文件指针的位置回到文件的起始位置

      rewind函数的原型如下: 

    void rewind ( FILE * stream );

     

    代码示例:

    1. #include
    2. int main()
    3. {
    4. FILE* pf = fopen("test.txt", "r");
    5. if (pf == NULL)
    6. {
    7. perror("fopen");
    8. return 1;
    9. }
    10. //读取文件
    11. int ch = 0;
    12. //调整文件指针
    13. //让文件指针指向文件末尾的位置
    14. fseek(pf, 0, SEEK_END);
    15. //让文件指针pf回到起始位置
    16. rewind(pf);
    17. while ((ch = fgetc(pf)) != EOF)
    18. {
    19. printf("%c", ch);//zhangjoy
    20. }
    21. //关闭文件
    22. fclose(pf);
    23. pf = NULL;
    24. return 0;
    25. }

    输出结果: 

    文本文件和二进制文件

      在C语言中,按数据存储的编码形式,数据文件可分为文本文件和二进制文件咯昂中。文本文件是以字符的ASCII码值进行存储与编码的文件,其文件的内容是字符。二进制文件是存储二进制数据的文件。

      例如对于整数1234,如果存放到文本文件中,文件内容将包含四个字符:49、50、51、52,它们分别是'1'、'2'、'3'、'4'的ASCII码值;如果把整数1234存放到二进制文件中去,文件内容将为1234对应的二进制数0x04D2,共两个字节。对于具体的数据选择哪一类文件进行存储,应该由需要解决的问题来决定,并在程序的一开始就定义好。

     

    文件读取结束的判定

    1.被错误使用的feof

      feof函数的原型如下:

    int feof ( FILE * stream );

       

      牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。

    1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
    例如:

    • fgetc 判断是否为 EOF .
    • fgets 判断返回值是否为 NULL .

    2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
    例如:

    • fread判断返回值是否小于实际要读的个数。

     

    代码示例1:

    1. #include
    2. int main()
    3. {
    4. FILE* pfread = fopen("test.txt", "r");
    5. if (pfread == NULL)
    6. {
    7. return 1;
    8. }
    9. FILE* pfwrite = fopen("test2.txt", "w");
    10. if (pfwrite == NULL)
    11. {
    12. perror("pfwrite");
    13. fclose(pfread);
    14. pfread = NULL;
    15. return 1;
    16. }
    17. //文件打开成功
    18. //读文件
    19. int ch = 0;
    20. while ((ch = fgetc(pfread)) != EOF)
    21. {
    22. //写文件
    23. fputc(ch, pfwrite);
    24. }
    25. //关闭文件
    26. fclose(pfread);
    27. pfread = NULL;
    28. fclose(pfwrite);
    29. pfwrite = NULL;
    30. return 0;
    31. }

    输出结果: 

      ferror函数的原型如下:

     

    int ferror ( FILE * stream );

    代码示例2:

    1. #include
    2. int main()
    3. {
    4. FILE* pfread = fopen("test.txt", "r");
    5. if (pfread == NULL)
    6. {
    7. return 1;
    8. }
    9. FILE* pfwrite = fopen("test2.txt", "w");
    10. if (pfwrite == NULL)
    11. {
    12. perror("pfwrite");
    13. fclose(pfread);
    14. pfread = NULL;
    15. return 1;
    16. }
    17. //文件打开成功
    18. //读文件
    19. int ch = 0;
    20. while ((ch = fgetc(pfread)) != EOF)
    21. {
    22. //写文件
    23. fputc(ch, pfwrite);
    24. }
    25. if (feof(pfread))
    26. {
    27. printf("遇到文件介绍标志,文件正常结束\n");
    28. }
    29. else if (ferror(pfread))
    30. {
    31. printf("文件读取失败结束\n");
    32. }
    33. //关闭文件
    34. fclose(pfread);
    35. pfread = NULL;
    36. fclose(pfwrite);
    37. pfwrite = NULL;
    38. return 0;
    39. }

    输出结果: 

    文件缓冲区 

      ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。

      下面的一段代码,可以证明文件缓冲区的存在。

    代码示例: 

    1. #include
    2. #include
    3. int main()
    4. {
    5. FILE* pf = fopen("test.txt", "w");
    6. fputs("abcdef", pf);//先将代码放在输出缓冲区
    7. printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
    8. Sleep(10000);
    9. printf("刷新缓冲区\n");
    10. fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
    11. //注:fflush 在高版本的VS上不能使用了
    12. printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
    13. Sleep(10000);
    14. fclose(pf);
    15. //注:fclose在关闭文件的时候,也会刷新缓冲区
    16. pf = NULL;
    17. return 0;
    18. }

    输出结果: 

      这里可以得出一个结论:因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件的问题。 

      以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个赞支持一下!

    结语

      每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根。

  • 相关阅读:
    推荐一种更加便捷的 Python 数据处理方式
    排序算法-----冒泡排序与选择排序
    羧甲基荧光素6-FAM修饰聚缩醛Polyacetal/HA透明质酸纳米载体6-FAM-Polyacetal|6-FAM-HA(齐岳)
    力扣第20题记录
    helm一键部署grafana
    2023-09-16 LeetCode每日一题(打家劫舍)
    spring boot过滤器实现项目内接口过滤
    VMware虚拟机安装Windows 10
    第四章. Pandas进阶—数据分组统计
    minio拉取的时候报错了
  • 原文地址:https://blog.csdn.net/m0_63639164/article/details/125947485