文件操作

文件操作

概念

文件

  • 通常就是磁盘上一段命名的存储区
  • 我们更关心的是如何在 C 程序中处理文件

读写文件和 printf、scanf 关联

  • scanf
    • 标准输入 (键盘)
  • printf
    • 标准输出 (屏幕)
  • perror
    • 标准错误 (屏幕)

系统文件

应用程序启动时,自动被打开;程序执行结束时,自动被关闭 (隐式回收)。

  • 标准输入
    • stdin -> 0
  • 标准输出
    • stdout -> 1
  • 标准错误
    • stderr -> 2

文件分类

  • 设备文件
    • 屏幕、键盘、磁盘、网卡、声卡、扬声器 …
  • 磁盘文件
    • 文本文件
      • ASCII 组成
    • 二进制文件
      • 二进制编码组成

流 (stream)

流是一个动态的概念,I/O 操作可以简单的看作是从程序移进或移出字节,这种搬运的过程便称为流。

  • 文本流
    • 以文本模式读取文件
    • 换行符
      • Windows:\r\n
      • Linux:\n
  • 二进制流
    • 适用于非文本数据
    • 和文本文件的打开方式的区别,主要是换行符的处理
      • 文本模式打开,需要进行 \r\n 和 \n 的转换
      • 二进制模式打开,不需要做转换

文件指针

操作系统将我们要打开的文件信息保存起来,并返回一个 FILE 类型指针指向文件的信息。通过文件指针可以对文件进行各种操作。

  • 借助文件操作函数改变 fp 为空、野指针的状况
    FILE *fp = fopen(); //相当于 fp = malloc();
    
  • 操作文件,使用文件读写函数完成
    • fputc、fgetc、fputs、fgets、fread、fwrite
  • 结构体 iobuf
    struct _iobuf
    {
        char* _ptr;    // 文件输入的下一个位置
        int _cnt;      // 剩余多少字符未被读取
        char* _base;   // 基础位置 (文件的起始位置)
        int _flag;     // 文件标志,判断是否读到了文件尾
        int _file;     // 文件的有效性验证
        int _charbuf;  // 检查缓冲区状况,如果无缓冲区则不读取
        int _bufsiz;   // 文件的大小
        int* _tmpfname;  // 临时文件名
    };
    

文件缓冲区

  • 分为
    • 输入缓冲区
    • 输出缓冲区
  • 优势
    • 提高硬件寿命和读写效率

文件路径

  • 绝对路径
    • 从系统磁盘的根目录开始,找到待访问的文件路径

    • Windows 书写方法

      H:\\学习文档\\C++\\项目\\day10\\1.txt
      H:/学习文档/C++/项目/day10/1.txt  # 也适用于 Linux
      
  • 相对路径
    • 如果在 VS 环境下编译执行,相对路径是相对于 .vcxproj 文件的位置
    • 如果双击 .exe 文件执行,文件的相对路径是相对于 .exe 文件

文件操作步骤

  • 打开文件 -> 读写文件 -> 关闭文件
    // 1.打开文件
    FILE *fp = fopen();
    // 2.读写文件
    fputc、fgetc、fputs、fgets、fread、fwrite
    // 3.关闭文件
    fclose()、close()、pclose()
    
  • 注意事项
    • 对打开的文件进行操作时,若文件缓冲区的空间未被写入的内容填满,这些内容不会写入到打开的文件中;只有对打开的文件进行关闭时,停留在文件缓冲区的内容才能写入到该文件中
    • 文件关闭时,也意味着释放了该文件的缓冲区

文件读写

fopen 函数

  • 函数描述
    • 打开文件
  • 函数原型
    FILE *fopen(const char *filename, const char * mode);
    
    • 参数
      • filename
        • 待打开文件的文件名 (访问路径)
      • mode
        • 文件打开权限
        • mode 的选项
          文件操作
    • 返回值
      • 成功,返回打开文件的指针;失败,返回 NULL

fclose 函数

  • 函数描述
    • 关闭文件
  • 函数原型
    int fclose(FILE * stream);
    
    • 参数
      • stream
        • 打开文件的 fp (fopen 的返回值)
    • 返回值
      • 成功,返回 0;失败,返回 -1
  • 示例
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    
    int main(void)
    {
        FILE* fp = NULL;
        fp = fopen("H:\\学习文档\\C++\\项目\\day10\\1.txt", "r");
        if (fp == NULL)
        {
            printf("fopen error");
            return -1;
        }
    
        fclose(fp);
    
        system("pause");
        return 0;
    }
    

按字符读写

  • fputc 函数
    • 函数描述
      • 按字符写
    • 函数原型
      int fputc(int ch, FILE * stream);
      
      • 参数
        • ch
          • 待写入的字符
        • stream
          • 打开文件的 fp (fopen 返回值)
      • 返回值
        • 成功,则写入文件中字符对应的 ASCII 码;失败,返回 -1
  • fgetc 函数
    • 函数描述
      • 按字符读
    • 函数原型
      int fgetc(FILE * stream);
      
      • 参数
        • stream
          • 待读取的文件 fp (fopen 的返回值)
      • 返回值
        • 成功,则读到文件中字符对应的 ASCII 码;失败,返回 -1
  • 示例
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    
    void write_file(char *filename)
    {
        FILE* fp = NULL;
        fp = fopen(filename, "w");
        if (fp == NULL)
        {
            printf("fopen error");
            return -1;
        }
    
        fputc('a', fp);
        fputc('b', fp);
        fputc('c', fp);
        fputc('d', fp);
        fputc('e', fp);
    
        fclose(fp);
    }
    
    void read_file(char* filename)
    {
        FILE* fp = NULL;
        fp = fopen(filename, "r");
        if (fp == NULL)
        {
            printf("fopen error");
            return -1;
        }
    
        while (1)
        {
            char ch = fgetc(fp);
            if (ch == -1)
            {
                break;
            }
            printf("%c ", ch);
        }
    
        fclose(fp);
    }
    
    int main(void)
    {
        write_file("3.txt");
        read_file("3.txt");
    
        system("pause");
        return 0;
    }
    
    • 不需要挪动光标

feof 函数

  • 函数描述
    • 判断是否到达文件结尾 (可以判断文本文件,或二进制文件)

    • 文本文件的结束标记 (EOF -> -1)

      #define EOF (-1)
      
  • 函数原型
    int feof(FILE * stream);
    
    • 参数
      • stream
        • fopen 的返回值
    • 返回值
      • 到达文件结尾,返回非 0;否则,返回 0
  • 示例
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    
    void write_file(char *filename)
    {
        FILE* fp = NULL;
        fp = fopen(filename, "w");
        if (fp == NULL)
        {
            printf("fopen error");
            return -1;
        }
    
        fputc('a', fp);
        fputc('b', fp);
        fputc('c', fp);
        fputc('d', fp);
        fputc('e', fp);
    
        fclose(fp);
    }
    
    void read_file(char* filename)
    {
        FILE* fp = NULL;
        fp = fopen(filename, "r");
        if (fp == NULL)
        {
            printf("fopen error");
            return -1;
        }
    
        while (1)
        {
            char ch = fgetc(fp);
            //if (ch == -1)
            if(feof(fp))  // 判断文件的结束标记
            {
                break;
            }
            printf("%c ", ch);
        }
    
        fclose(fp);
    }
    
    int main(void)
    {
        write_file("3.txt");
        read_file("3.txt");
    
        system("pause");
        return 0;
    }
    

按行读写

  • fgets 函数
    • 函数描述
      • 获取一个字符串 (按行读),以 ‘\n’ 作为结束标记
      • 自动添加 ‘\0’;如果空间足够大,会读 ‘\n’;空间不足,会舍弃 ‘\n’
    • 函数原型
      char * fgets(char * str, int size, FILE * stream);
      
  • fputs 函数
    • 函数描述
      • 按行写
      • 写一个字符串,如果字符串中没有 ‘\n’,不会写 ‘\n’
    • 函数原型
      int fputs(const char * str, FILE * stream);
      
      • 返回值
        • 成功,返回 0;失败,返回 -1

二进制文件的读取和写入 (按块读写)

  • fwrite 函数

    既可以处理文本文件,也可以处理二进制文件。

    • 函数描述
      • 按块写 (二进制文件)
    • 函数原型
      size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
      
      • 参数
        • ptr
          • 待写出的数据的地址
        • size
          • 待写出的数据大小
        • nmemb
          • 每次写出的个数 (size * nmemb = 写出数据的总大小)
          • 通常将 size 传 1,nmemb 传实际写出的字节数
        • stream
          • 文件
      • 返回值
        • 成功,返回 nmemb 的值;失败,返回 0
  • fread 函数
    • 函数描述
      • 按块读 (二进制文件)
    • 函数原型
      size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
      
      • 参数
      • 返回值
        • 成功,返回 nmemb 的值;失败,返回 0
        • 和 feof(fp),都可以用来判断是否到达文件结尾
  • 示例
    • fwrite 和 fread
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      
      typedef struct Person
      {
          char name[12];
          int age;
      }person;
      
      int fwrite_struct(const char * filename)
      {
          FILE* fp = fopen(filename, "w+");
          if (fp == NULL)
          {
              perror("fopen error");
              return -1;
          }
      
          person p = { "aaa", 10 };
          int ret = fwrite(&p, 1, 1 * sizeof(person), fp);
          if (ret == 0)
          {
              perror("fwite error");
              return -1;
          }
      
          fclose(fp);
          return 0;
      }
      
      int fread_struct(const char* filename)
      {
          FILE* fp = fopen(filename, "r");
          if (fp == NULL)
          {
              perror("fopen error");
              return -1;
          }
      
          person p;
          int ret = fread(&p, 1, 1 * sizeof(person), fp);
          if (ret == 0)
          {
              perror("fread error");
              return -1;
          }
      
          printf("name = %s, age = %d\n", p.name, p.age);
      }
      
      int main()
      {
          fwrite_struct("./fwrite.txt");
          fread_struct("./fwrite.txt");
      
          return 0;
      }
      
    • 大文件拷贝 (需要按块读写)
      #define _CRT_SECURE_NO_WARNINGS
      #include 
      
      int file_cpy(const char *src_file, char* dest_file)
      {
          FILE* rfp = fopen(src_file, "rb");
          if (!rfp)
          {
              perror("src_file fopen error");
              return -1;
          }
      
          FILE* wfp = fopen(dest_file, "wb");
          if (!wfp)
          {
              perror("dest_file fopen error");
              return -1;
          }
      
          char buf[4096] = {0};  // 这里定义成 char *buf = NULL 会报错,数组是变量,指针是常量
          int ret = 0;
          while (1)
          {
              memset(buf, 0x00, sizeof(buf));  // buf 使用前缓冲区清零
              ret = fread(buf, 1, 1 * sizeof(buf), rfp);
              if (ret == 0)
              {
                  perror("fread error or file read over");
                  break;
              }
      
              fwrite(buf, 1, ret, wfp);
          }
      
          fclose(rfp);
          fclose(wfp);
      
          return 0;
      }
      
      int main()
      {
      file_cpy("C:\\Users\\WangHaiying_GamePC\\Desktop\\webserver_epoll.jpg", "C:\\Users\\WangHaiying_GamePC\\Desktop\\webserver_epoll_2.jpg");
      
          return 0;
      }
      
      • 已知一个任意类型的文件,对该文件复制,产生一个相同的新文件
      • 在 Windows 下,打开二进制文件 (mp3、mp4、avi、jpg …) 时,需要使用 “b”,例如:rb、wb

按格式化方式读写

  • fprintf 函数
    • 函数描述
      • 格式化写
    • 函数原型
      int fprintf(FILE * stream, const char * format, ...);
      
  • fscanf 函数
    • 函数描述
      • 格式化读
    • 函数原型
      int fscanf(FILE * stream, const char * format, ...);
      
    • 注意事项
      • 边界溢出
        • 场景复现
          #define _CRT_SECURE_NO_WARNINGS
          #include 
          #include 
          #include 
          
          // 输出到文件
          void file_write(char *filename)
          {
              FILE* fp = NULL;
              fp = fopen(filename, "w");
              if (!fp)
              {
                  perror("fopen error");
                  return -1;
              }
          
              fprintf(fp, "%d + %d = %d\n", 10, 20, 10 + 20);
          
              fclose(fp);
          }
          
          // 从文件输入
          void file_read(char* filename)
          {
              int a, b, c;
          
              FILE* fp = NULL;
              fp = fopen(filename, "r");
              if (!fp)
              {
                  perror("fopen error");
                  return -1;
              }
          
              //fscanf(fp, "%d + %d = %d\n", &a, &b, &c);
              //printf("%d + %d = %d\n", a, b, c);
          
              fscanf(fp, "%d\n", &a);
              printf("%d\n", a);
          
              fscanf(fp, "%d\n", &a);
              printf("%d\n", a);
          
              fscanf(fp, "%d\n", &a);
              printf("%d\n", a);
          
              fscanf(fp, "%d\n", &a);
              printf("%d\n", a);  // 最终输出 1 2 3 3
          
              fclose(fp);
          }
          
          int main(void)
          {
              file_write("06.txt");
              file_read("066.txt");
          
              system("pause");
              return 0;
          }
          
          • getchar() 可以暂停一下,用于观察和调试
          • 故意输出 4 次,最后一次输出与上次相同
        • 存储读取的数据空间应该在使用前清空 (memset )
          #define _CRT_SECURE_NO_WARNINGS
          #include 
          #include 
          #include 
          
          // 输出到文件
          void file_write(char *filename)
          {
              FILE* fp = NULL;
              fp = fopen(filename, "w");
              if (!fp)
              {
                  perror("fopen error");
                  return -1;
              }
          
              fprintf(fp, "%d + %d = %d\n", 10, 20, 10 + 20);
          
              fclose(fp);
          }
          
          // 从文件输入
          void file_read(char* filename)
          {
              int a, b, c;
          
              FILE* fp = NULL;
              fp = fopen(filename, "r");
              if (!fp)
              {
                  perror("fopen error");
                  return -1;
              }
          
              //fscanf(fp, "%d + %d = %d\n", &a, &b, &c);
              //printf("%d + %d = %d\n", a, b, c);
          
              //fscanf(fp, "%d\n", &a);
              //printf("%d\n", a);
          
              //fscanf(fp, "%d\n", &a);
              //printf("%d\n", a);
          
              //fscanf(fp, "%d\n", &a);
              //printf("%d\n", a);
          
              //fscanf(fp, "%d\n", &a);
              //printf("%d\n", a);  // 最终输出 1 2 3 3
          
              //while (1)  // 上面的输入使用循环方式书写
              //{
              //  fscanf(fp, "%d\n", &a);
              //  if (feof(fp))
              //  {
              //      break;
              //  }
              //  printf("%d\n", a);  // 输出 1 2,当 a = 3 时,会将 fp 置为 0
              //}
          
              //while (1)  // 上面的输入使用循环方式书写
              //{
              //  fscanf(fp, "%d\n", &a);
              //  printf("%d\n", a);  // 输出 1 2,当 a = 3 时,会将 fp 置为 0
              //  
              //  if (feof(fp))
              //  {
              //      break;
              //  }
              //}
          
              char buf[1024];
              while (1)
              {
                  memset(buf, 0, 1024);  // 清空缓冲区
                  fgets(buf, 1024, fp);
                  printf("%s", buf);  // 输出 1 2 3
          
                  if (feof(fp))
                  {
                      break;
                  }
          
              }
          
              fclose(fp);
          }
          
          int main(void)
          {
              file_write("06.txt");
              file_read("066.txt");
          
              system("pause");
              return 0;
          }
          
      • fscanf() 每次调用时都会判断下一次调用是否匹配参数 2,如果不匹配,提前结束文件读操作
        #define _CRT_SECURE_NO_WARNINGS
        #include 
        #include 
        #include 
        
        // 输出到文件
        void file_write(char *filename)
        {
            FILE* fp = NULL;
            fp = fopen(filename, "w");
            if (!fp)
            {
                perror("fopen error");
                return -1;
            }
        
            fprintf(fp, "%d + %d = %d\n", 10, 20, 10 + 20);
        
            fclose(fp);
        }
        
        // 从文件输入
        void file_read(char* filename)
        {
            int a, b, c;
        
            FILE* fp = NULL;
            fp = fopen(filename, "r");
            if (!fp)
            {
                perror("fopen error");
                return -1;
            }
        
            //fscanf(fp, "%d + %d = %d\n", &a, &b, &c);
            //printf("%d + %d = %d\n", a, b, c);
        
            //fscanf(fp, "%d\n", &a);
            //printf("%d\n", a);
        
            //fscanf(fp, "%d\n", &a);
            //printf("%d\n", a);
        
            //fscanf(fp, "%d\n", &a);
            //printf("%d\n", a);
        
            //fscanf(fp, "%d\n", &a);
            //printf("%d\n", a);  // 最终输出 1 2 3 3
        
            //while (1)  // 上面的输入使用循环方式书写
            //{
            //  fscanf(fp, "%d\n", &a);
            //  if (feof(fp))
            //  {
            //      break;
            //  }
            //  printf("%d\n", a);  // 输出 1 2,当 a = 3 时,会将 fp 置为 0
            //}
        
            while (1)  // 上面的输入使用循环方式书写
            {
                fscanf(fp, "%d\n", &a);
                printf("%d\n", a);  // 输出 1 2,当 a = 3 时,会将 fp 置为 0
        
                if (feof(fp))
                {
                    break;
                }
            }
        
            fclose(fp);
        }
        
        int main(void)
        {
            file_write("06.txt");
            file_read("066.txt");
        
            system("pause");
            return 0;
        }
        
  • 对比
    • printf -> sprintf -> fprintf
      • 示例
        // printf
        printf("hello");
        printf("%s", "hello");
        printf("%d + %d = %d", a, b, a+b);
        
        // sprintf
        char buf[1024];
        sprintf(buf, "%d + %d = %d", a, b, a+b);
        
        // fprintf
        FILE * fp = fopen();
        fprintf(fp, "%d + %d = %d", a, b, a+b);
        
    • scanf -> sscanf -> fscanf
      • 举例
        // scanf
        scanf("%d", &m);  // 键盘 -> m
        
        // sscanf
        char str[] = "98";
        sscanf(str, "%d", &m);  // str -> m
        
        // fscanf
        FILE * fp = fopen();
        fscanf(fp,"%d", &m);  // fp 指向的文件中 -> m
        
  • 变参函数
    • 参数形参中,有 “…”,最后一个固参通常是格式描述串 (包含格式匹配符),函数的参数个数、类型、顺序,由这个固定参数决定
  • 示例
    • 文件版排序:生成随机数,写入文件,排序后再重新写入

      “`c

      <h1>define _CRT_SECURE_NO_WARNINGS</h1>

      <h1>include</h1>

      <h1>include</h1>

      <h1>include</h1>

      // 冒泡排序
      void bubble_sort(int* str, int n)
      {
      int temp;

      <pre><code>for (int i = 0; i < n; i++)
      {
      for (int j = 0; j str[j + 1])
      {
      temp = str[j];
      str[j] = str[j + 1];
      str[j + 1] = temp;
      }
      }
      }
      </code></pre>

      }

      // 生成随机数并写入文件
      int f_rand(char* filename)
      {
      int a;
      srand(time(NULL)); // 生成随机数种子

      <pre><code>FILE* fp = NULL;
      fp = fopen(filename, "w"); // 创建空文件
      if (!fp)
      {
      perror("fopen error"); // 判断是否创建文件成功
      return -1;
      }

      for(size_t i = 0; i< 10; i++) // 写入 10 次
      {
      a = rand() % 100; // 随机数范围是 0-99
      fprintf(fp, "%d\n", a);
      }

      fclose(fp);
      return 0;
      </code></pre>

      }

      // 读取文件中的随机数
      int f_read(char *filename)
      {
      int i = 0;
      int buf[10];

      <pre><code>FILE* fp = NULL;
      fp = fopen(filename, "r");
      if (!fp)
      {
      perror("fopen error");
      return -1;
      }

      while (1)
      {
      fscanf(fp, "%d\n", &buf[i]);
      printf("%d\n", buf[i]); // 验证

      i++;

      if (feof(fp))
      {
      break;
      }
      }

      fclose(fp);

      // 排序
      bubble_sort(buf, i);

      // 将排序后的数字重新写入文件
      fp = fopen(filename, "w"); // 不需要重复定义 fp

      if (!fp)
      {
      perror("fopen error");
      return -1;
      }

      for (size_t k = 0; k < 10; k++) // 验证
      {
      fprintf(fp, "%d\n", buf[k]); // 写入文件
      }

      fclose(fp);
      return 0;
      </code></pre>

      }

      int main(void)
      {
      f_rand("07.txt");
      f_read("07.txt");

      <pre><code>// 验证冒泡排序
      /*
      int str[5] = { 1, 2, 5, 4, 6 };

      bubble_sort(str, 5);

      for (size_t i = 0; i 向后,- -> 向前

      – whence

      – 指针位置
      – 选项

      – SEEK_SET

      – 自定义文件位置

      – SEEK_CUR

      – 当前位置

      – SEEK_END

      – 文件结尾位置
      </code></pre>

      <ul>
      <li>返回值

      <ul>
      <li>成功,返回 0;失败,返回 -1</li>
      </ul></li>
      </ul></li>
      </ul></li>
      <li>ftell 函数

      <ul>
      <li>函数描述

      <ul>
      <li>获取文件读写指针位置</li>
      </ul></li>
      <li>函数原型

      <pre><code class="language-c line-numbers">long ftell(FILE *stream);
      </code></pre>

      <ul>
      <li>返回值

      <ul>
      <li>从文件当前读写位置到起始位置的偏移量 (字节数)</li>
      </ul></li>
      </ul></li>
      </ul></li>
      <li>rewind 函数

      将读写指针移动到起始位置。

      <ul>
      <li>函数描述

      <ul>
      <li>回卷文件读写指针</li>
      </ul></li>
      <li>函数原型

      <pre><code class="language-c line-numbers">void rewind(FILE * stream);
      </code></pre></li>
      </ul></li>
      <li>获取文件大小

      <pre><code class="language-c line-numbers">fseek(SEEK_END) + ftell()
      </code></pre></li>
      <li>示例

      <pre><code class="language-c line-numbers">#define _CRT_SECURE_NO_WARNINGS
      #include

      typedef struct Person
      {
      char name[16];
      int age;
      }person;

      int read_random(const char* filename)
      {
      person p[3] = { "aa", 10, "bb", 20, "cc", 30 };
      int ret = 0;
      person newp;

      FILE* fp = fopen(filename, "w+");
      if (!fp)
      {
      perror("fopen error");
      return -1;
      }

      // 结构体写入文件
      ret = fwrite(&p, 1, 1 * sizeof(person), fp);
      if (ret == 0)
      {
      perror("fwrite error");
      return -1;
      }

      // 偏移文件指针
      fseek(fp, sizeof(person) * 2, SEEK_SET);

      // 读取文件
      ret = fread(&newp, 1, sizeof(person), fp);
      printf("name: %s, age: %d\n", newp.name, newp.age); // 乱码,因为上面只写入了 p[0],而想要读取 p[2]

      // 获取当前文件指针位置
      int len = ftell(fp);
      printf("offset = %d\n", len); // offset = 40

      // 回卷指针
      rewind(fp);

      // 读取文件
      ret = fread(&newp, 1, sizeof(person), fp);
      printf("name: %s, age: %d\n", newp.name, newp.age); // name: aa, age: 10

      return 0;
      }

      int main()
      {
      read_random("./read_random.txt");

      return 0;
      }
      </code></pre></li>
      </ul>

      <h3>error 宏</h3>

      “`c++
      FILE* f_write = fopen(“./02.txt”, “w”);
      if (f_write == NULL)
      {
      perror(“fopen error”); // error 宏
      return;
      }
      “`

      • 会额外打印 error 宏的错误信息

      示例

      • 获取用户键盘输入,写入文件
        #define _CRT_SECURE_NO_WARNINGS
        #include 
        #include 
        #include 
        
        int main(void)
        {
            FILE* fp = NULL;
            fp = fopen("04.txt", "w");
            if (fp == NULL)
            {
                printf("fopen error");
                return -1;
            }
        
            char buf[4096] = {0};
        
            while (1)
            {
                fgets(buf, 4096, stdin);
        
                if (strcmp(buf, ":wq\n") == 0)
                {
                    break;
                }
        
                fputs(buf, fp);
            }
        
            fclose(fp);
        
            system("pause");
            return 0;
        }
        
        • 当用户输入 “:wp” 时,中止用户输入,并将之前的数据保存为文件
        • 实际的终止符是 “:wq\n”
      • 文件版四则运算
        #define _CRT_SECURE_NO_WARNINGS
        #include 
        #include >
        #include 
        
        // 创建文件,内容由键盘输入
        void writefile(char* filename)
        {
            FILE* fp = NULL;
            fp = fopen(filename, "w");
            if (fp == NULL)
            {
                printf("fopen error");
                return -1;
            }
        
            char buf[4096] = { 0 };
        
            while (1)
            {
                fgets(buf, 4096, stdin);
        
                if (strcmp(buf, ":wq\n") == 0)
                {
                    break;
                }
        
                fputs(buf, fp);
            }
        
            fclose(fp);
        }
        
        // 读取文件
        void readfile(char* filename1, char *filename2)  // filename1: 原文件; filename2: 最终写入的文件
        {
            FILE* fp = NULL;
            fp = fopen(filename1, "r");
            if (fp == NULL)
            {
                printf("fopen error");
                return -1;
            }
        
            char buf1[4096] = { 0 };
            int n = sizeof(buf1);
            int a, b, c=0;
            char ch;
        
            char result[1024] = { 0 };
            char buf2[4096] = { 0 };
        
            while (1)
            {
                fgets(buf1, n, fp);
        
                if (feof(fp))
                {
                    break;
                }
        
                sscanf(buf1, "%d%c%d=\n", &a, &ch, &b);
        
                switch (ch)
                {
                case '+':
                    c = a + b;
                    break;
                case '-':
                    c = a - b;
                    break;
                case '*':
                    c = a * b;
                    break;
                case '/':
                    c = a / b;
                    break;
                default:
                    printf("ch is %c, error!", ch);
                    break;
                }
        
                //printf("%d%c%d=%d\n", a, ch, b, c);  // 用于验证
        
                // 字符串拼接
                sprintf(result, "%d%c%d=%d\n", a, ch, b, c);
                strcat(buf2, result);
            }
        
            fclose(fp);
        
            printf("%s\n", buf2);
        
            // 重新打开文件 (清空文件,重新写入)
            FILE* fp2 = NULL;
            fp2 = fopen(filename2, "w");
        
            fputs(buf2, fp2);
        
            fclose(fp2);
        }
        
        int main(void)
        {
            //writefile("05.txt");
            readfile("05.txt", "055.txt");
        
            system("pause");
            return 0;
        }
        

      文件读写的注意事项

      • 不要用 feof() 按照字符的方式读取文件
        • 滞后性,多读出一个 EOF 字符
      • 如果属性开辟到了堆区,不要存指针到文件中,要将指针指向的内容存放到文件

      • 示例

        #define _CRT_SECURE_NO_WARNINGS
        #include 
        #include 
        #include 
        
        // 注意事项1:不要用 feof() 按照字符的方式读取文件
        void test01()
        {
            FILE* fp = fopen("./03.txt", "w");
            if (fp == NULL)
            {
                perror("fopen error");
                return;
            }
        
            fprintf(fp, "%s\n%s", "第一行数据", "第二行数据");
            fclose(fp);
        
            fp = fopen("./03.txt", "r");
        
            char ch;
        
        #if 0
            while (!feof(fp))
            {
                ch = fgetc(fp);
                printf("%c", ch);  // aaaa||||| 后面多读了一个 EOF 字符
            }
            printf("###");
        /*
        第一行数据
        第二行数据###
        */
        #else
            while ((ch = fgetc(fp)) != EOF)
            {
                printf("%c", ch);
            }
            printf("###");
        /*
        第一行数据
        第二行数据###
        */
        #endif  // 自定义条件编译,0 表示不跑这段代码
        
            fclose(fp);
        }
        
        // 注意事项2
        struct Hero
        {
            char* name; //如果属性开辟到堆区,不要存指针到文件中,要将指针指向的内容存放到文件中
            int age;
        };
        
        int main(void)
        {
            test01();
        
            system("pause");
            return 0;
        }
        

      其它文件操作

      Linux 和 Windows 文件的区别

      • 对于二进制文件操作
        • Windows 下
          • 使用 “b”
        • Linux 下
          • 二进制和文本没区别
      • 回车和换行
        • Windows 下
          • 回车 ‘\r’,换行 ‘\n’,所以每行结尾是 “\r\n”
        • Linux 下
          • 回车、换行都是 ‘\n’
      • 示例
        #define _CRT_SECURE_NO_WARNINGS
        #include 
        
        typedef struct Person
        {
            char name[16];
            int age;
        }person;
        
        int read_random(const char* filename)
        {
            person p[3] = { "aa", 10, "bb", 20, "cc", 30 };
            int ret = 0;
            person newp;
        
            FILE* fp = fopen(filename, "a+");  // 追加,不清空文件
            if (!fp)
            {
                perror("fopen error");
                return -1;
            }
        
            // 偏移文件指针
            //fseek(fp, 0, SEEK_CUR);
        
            // 读取文件
            ret = fread(&newp, 1, sizeof(person), fp);
            printf("name: %s, age: %d\n", newp.name, newp.age);  //
        
            // 结构体写入文件
            ret = fwrite(&p[1], 1, 1 * sizeof(person), fp);
            if (ret == 0)
            {
                perror("fwrite error");
                return -1;
            }
        
            // 回卷指针
            rewind(fp);
        
            // 读取文件
            ret = fread(&newp, 1, sizeof(person), fp);
            printf("name: %s, age: %d\n", newp.name, newp.age);  // name: aa, age: 10
        
            return 0;
        }
        
        int main()
        {
            read_random("./read_random.txt");
        
            return 0;
        }
        

      stat 函数

      打开文件,对系统资源的消耗较大。

      • 函数描述
        • 获取文件属性
      • 函数原型
        #include 
        #include 
        #include 
        int stat(const char *path, struct stat * buf);
        
        • 参数
          • path
            • 访问文件的路径
          • buf
            • 文件属性结构体 stat
              struct stat{
              dev     st_dev;    // 文件的设备编号
              ino_t   st_ino;    // 节点
              mode_t  st_mode;   // 文件类型和存取权限
              nlink_t st_nlink;  // 连到该文件的硬链接数目,刚建立的文件值为1
              uid_t   st_uid;    // 用户 id
              gid_t   st_gid;    // 组 id
              dev_t   st_rdev;   // 设备类型,若此文件为设备文件,则为设备编号
              off_t   st_size;   // 文件字节数 (文件大小)
              unsigned long st_blksize;  // 块大小 (文件系统的 I/O 缓冲区大小)
              unsigned long st_blocks;   // 块数
              time_t  st_atime;  // 最后一次访问时间
              time_t  st_mtime;  // 最后一次修改时间
              time_t  st_ctime;  // 最后一次属性改变的时间
              };
              
        • 返回值
          • 成功,返回 0;失败,返回 -1
      • 示例
        #define _CRT_SECURE_NO_WARNINGS
        #include 
        #include 
        #include 
        
        void get_file_stat(const char* filename)
        {
            struct stat s;
            stat(filename, &s);
            printf("st_uid: %d, st_gid: %d, st_size: %d\n", s.st_uid, s.st_gid, s.st_size);
        }
        
        int main()
        {
        get_file_stat("C:\\Users\\WangHaiying_GamePC\\Desktop\\webserver_epoll.jpg");
        
            return 0;
        }
        
        • 示意图:对应文件属性
          文件操作

      remove 函数

      • 函数描述
        • 删除文件
      • 函数原型
        int remove(const char *pathname);
        

      rename 函数

      • 函数描述
        • 重命名文件
      • 函数原型
        int rename(const char *oldpath, const char *newpath);
        

      fflush 函数

      • 函数描述
        • 手动刷新缓冲区
      • 函数原型
        int fflush(FILE *stream);
        
        • 返回值
          • 成功,返回 0;失败,返回 -1
      • 示例
        #define _CRT_SECURE_NO_WARNINGS
        #include 
        
        int flush_buf(const char* filename)
        {
            FILE* fp = fopen(filename, "w+");
            if (!fp)
            {
                perror("fopen error");
                return -1;
            }
        
            char str[64] = { 0 };
            //char* str = NULL;  // error!
            while (1)
            {
                // 从屏幕获取字符串
                scanf("%s", str);
                printf("str = [%s]\n", str);
                if (strcmp(str, ":wq") == 0)
                {
                    break;
                }
        
                // 写入文件
                fwrite(str, 1, sizeof(str), fp);
        
                // 刷新缓冲区
                fflush(fp);
            }
        
            fclose(fp);
        
            return 0;
        }
        
        int main()
        {
            flush_buf("./flush_buf.txt");
        
            return 0;
        }
        
      • 扩展
        • 缓冲区
          • 标准输入 (stdin) -> 标准输入缓冲区

            从键盘读取的数据,直接读到缓冲区中,由缓冲区给程序提供数据。

          • 标准输出 (stdout) -> 标准输出缓冲区

            写给屏幕的数据,先存到缓冲区中,由缓冲区一次性刷新到物理设备 (屏幕)。

          • 预读入,缓输出

            • 行缓存 (printf())
              • 遇到 ‘\n’ 就会将缓冲区中的数据刷新到物理设备上
            • 全缓存 (文件)
              • 缓冲区存满,数据刷新到物理设备上
            • 无缓存 (perror)
              • 缓冲区中只要有数据,就立即刷新到物理设备
        • 自动刷新缓冲区
          • 文件关闭时,缓冲区会被自动刷新
          • 隐式回收时:关闭文件、刷新缓冲区、释放 malloc

      配置文件读写案例

      需求

      • 文件中按照键值对的方式存放了有效的信息,需要解析出来

      配置文件

      • 内容
        #英雄id
        heroid:1
        
        # 英雄姓名
        heroName:aaaa
        
        # 攻击力
        heroAtk:9999
        
        # 防御力
        heroDef:1000
        
        # 英雄简介
        heroInfo:无敌
        
        
        aaaaaaaaaaa
        
        bbbbbbbb
        ccccccccc
        dsfasdfaf
        ewrqerqwcxvasf
        // 根据 key 值访问对应的 value 值
        
      • 编码
        • 编码要求使用 936 (ANSI/OEM-简体中文)
        • 使用 UTF-8 编码,在打印时中文会显示乱码

      实现

      • config.h

        定义结构体、函数声明。

        • getFileLines
          • 获取文件的有效行数
        • isValidLines
          • 检测当前行是否是有效信息
          • 有冒号就是真的
        • parseFile
          • 解析配置文件,将文件的键值信息存放到结构体中
        • getInfoByKey
          • 根据 key 值查找对应的 value 值
        • freeConfigInof
          • 释放内存
      • config.c
        • 函数定义
      • main.c
        • 主函数中测试

      代码

      • config.h
        #define _CRT_SECURE_NO_WARNINGS
        #pragma once
        
        #include
        #include
        #include
        
        // 配置信息 结构体
        struct ConfigInfo
        {
            char key[64];
            char value[64];
        };
        
        // 获取有效行数
        int getFileLines(char* filePath);
        
        // 检测当前行是否是有效信息
        int isValidLines(char* str);
        
        // 解析文件
        void parseFile(char* filePath, int lines, struct  ConfigInfo** configinfo);
        
        // 根据 key 获取对应 value
        char* getInfoByKey(char* key, struct ConfigInfo* configinfo, int len);
        
        // 释放内存
        void freeConfigInfo(struct ConfigInfo* configinfo);
        
        
      • config.c
        #include "config.h"
        
        // 获取有效行数
        int getFileLines(char* filePath)
        {
            FILE* file = fopen(filePath, "r");
            if (file == NULL)
            {
                return -1;
            }
        
            char buf[1024] = { 0 };
            int lines = 0;
            while (fgets(buf, 1024, file) != NULL)
            {
                if (isValidLines(buf))
                {
                    lines++;
                }
                memset(buf, 0, 1024);
            }
        
            fclose(file);
        
            return lines;
        }
        
        // 检测当前行是否是有效信息
        int isValidLines(char* str)
        {
            if (strchr(str, ':') == NULL)
            {
                return 0; //返回假 代表无效行
            }
        
            return 1;
        }
        
        // 解析文件
        void parseFile(char* filePath, int lines, struct  ConfigInfo** configinfo)
        {
            struct ConfigInfo* info = malloc(sizeof(struct ConfigInfo) * lines);
            if (info == NULL)
            {
                return;
            }
        
            FILE* file = fopen(filePath, "r");
            if (file == NULL)
            {
                return;
            }
        
            char buf[1024] = { 0 };
            int index = 0;  // 有效信息的行数
            while (fgets(buf, 1024, file) != NULL)
            {
                // 有效信息才去解析,例如 heroName:aaaa\n
                if (isValidLines(buf))
                {
                    memset(info[index].key, 0, 64);  // 清空 key 和 value 数组
                    memset(info[index].value, 0, 64);
                    char* pos = strchr(buf, ':');
                    strncpy(info[index].key, buf, pos - buf);  // 截取 key
                    strncpy(info[index].value, pos + 1, strlen(pos + 1) - 1);  // 截取 value
                    index++;
                }
                memset(buf, 0, 1024);
            }
            *configinfo = info;
        }
        
        // 根据 key 获取对应 value
        char* getInfoByKey(char* key, struct ConfigInfo* configinfo, int len)
        {
            for (int i = 0; i < len; i++)
            {
                if (strcmp(key, configinfo[i].key) == 0)
                {
                    return configinfo[i].value;
                }
            }
        
            return NULL;
        }
        
        // 释放内存
        void freeConfigInfo(struct ConfigInfo* configinfo)
        {
            if (configinfo != NULL)
            {
                free(configinfo);
                configinfo = NULL;
            }
        }
        
      • main.c
        #define _CRT_SECURE_NO_WARNINGS
        #include
        #include
        #include
        #include "config.h"
        
        int main() {
            char* filePath = "./config.txt";
            int len = getFileLines(filePath);
            printf("文件的有效行数为:%d\n", len);  // 
        
            struct ConfigInfo* configInfo = NULL;
            parseFile(filePath, len, &configInfo);
        
            // 根据 key 获取 value
            printf("heroid = %s\n", getInfoByKey("heroid", configInfo, len));
            printf("heroName = %s\n", getInfoByKey("heroName", configInfo, len));
            printf("heroAtk = %s\n", getInfoByKey("heroAtk", configInfo, len));
            printf("heroDef = %s\n", getInfoByKey("heroDef", configInfo, len));
            printf("heroInfo = %s\n", getInfoByKey("heroInfo", configInfo, len));
        
            // 释放内存
            freeConfigInfo(configInfo);
        
            system("pause");
            return 0;
        }
        
      • config.txt
        # 英雄id
        heroid:1
        
        # 英雄姓名
        heroName:aaaa
        
        # 攻击力
        heroAtk:9999
        
        # 防御力
        heroDef:1000
        
        # 英雄简介
        heroInfo:无敌
        
        
        
        
        aaaaaaaaaaa
        
        bbbbbbbb
        ccccccccc
        dsfasdfaf
        ewrqerqwcxvasf
        // 根据 key 值访问对应的 value 值
        

      文件加密

      • 原理示意图
        文件操作
      • 代码
        • code.h
        • code.c
        • main.c
          • 这个 main.c 文件和读配置文件的 main.c 是同一个文件
          • 优化:添加随机数种子
英仔
版权声明:本站原创文章,由 英仔 2022-08-13发表,共计18393字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)