文件的输出与读写 2.0

一、文章内容概述

(一)知识要点

  • 文件操作函数概述:介绍了 C 语言中用于文件操作的一系列函数,这些函数是实现文件读写功能的基础工具。
  • 文件流概念
    • 定义与分类FILE* stream这种定义方式包含了各种各样的流。流是一种用于在程序和外部设备(如文件、控制台、网络等)之间进行数据传输的抽象概念。
    • 具体类型
      • 文件流:用于读取与写入在磁盘上的文件。例如,通过文件流可以从硬盘上的文本文件中读取数据,并将其显示在程序中,或者将程序中的数据写入到文件中,以便后续使用或共享。
      • 标准 I/O 流
        • stdin:默认连接到键盘,用于程序输入,通常与scanf_s等函数配合使用,接收用户从键盘输入的数据。
        • stdout:默认连接到控制台或者是屏幕,用于程序输出,常见的使用场景是与printf等函数一起,将程序计算结果或提示信息显示在屏幕上,让用户能够看到程序的运行状态和结果。
        • stderr:默认也连接到控制台或者屏幕,专门用于输出错误信息和警告。这样可以使错误信息和正常的输出信息区分开来,方便用户查看和处理程序中的错误情况。
      • 管道流:用于进程之间的通信(IPC),允许一个进程的输出成为另一个进程的输入。通过管道流,不同的进程可以共享数据,实现进程间的协作和数据交换。
      • 内存流:允许将流与内存缓冲区相关联,使得可以向内存中读写数据,就像操作文件一样。这在一些需要在内存中进行数据处理或缓存的场景中非常有用,可以提高程序的性能和效率。
      • 网络流:基于套接字(Sockets)实现,用于在网络环境中进行数据传输。通过网络流,程序可以与远程服务器或其他计算机进行通信,实现数据的发送和接收。
      • 设备流:与特殊文件或者设备(如打印机)相关联,用于对设备进行操作和数据传输。

(二)重点内容

1. 文件操作函数
(1)基本操作

  • fopen_s函数
    • 功能:用于打开一个文件,并返回一个指向该文件的文件指针。
    • 原型
      errno_t fopen_s(FILE **stream, const char *filename, const char *mode);
      
    • 参数
      • stream:指向一个指针的指针,用于接收打开文件后返回的文件指针。
      • filename:指定要打开的文件的名称和路径。可以是相对路径或绝对路径。
      • mode:指定文件的打开模式,如r(只读)、w(只写)、a(追加)等。
    • 返回值
      • 如果打开成功,返回 0;如果打开失败,返回非 0 值,并可以通过errno获取错误信息。
  • fclose函数
    • 功能:用于关闭一个已打开的文件,释放与该文件相关的资源。
    • 原型
      int fclose(FILE *stream);
      
    • 参数:一个指向已打开文件的文件指针。
    • 返回值
      • 成功关闭文件时,返回 0;如果关闭文件时发生错误,返回非 0 值。
(2)读写函数

  • fgets函数
    • 功能:从指定的文件流中读取最多num - 1个字符,并将其存储到字符数组str中,直到遇到换行符\n或者文件结束符EOF为止。读取完成后,会在字符串末尾自动添加字符串结束符'\0'
    • 原型
      char *fgets(char *str, int num, FILE *stream);
      
    • 参数
      • str:指向用于存储读取数据的字符数组的指针。
      • num:指定要读取的最大字符数,包括字符串结束符'\0'
      • stream:指向要读取数据的文件流的指针。
  • fputs函数
    • 功能:将字符串写入指定的文件流中。
    • 原型
      int fputs(const char *str, FILE *stream);
      
    • 参数
      • str:指向要写入文件的字符串的指针。
      • stream:指向要写入数据的文件流的指针。
  • fprintf_s函数
    • 功能:按照指定的格式将数据写入文件。
    • 原型
      int fprintf_s(FILE *stream, const char *format,...);
      
    • 参数
      • stream:指向要写入数据的文件流的指针。
      • format:指定数据的输出格式,类似于printf函数中的格式字符串。
      • ...:根据格式字符串指定的格式,依次列出要写入的数据。
  • fscanf_s函数
    • 功能:从文件中按照指定的格式读取数据。
    • 原型
      int fscanf_s(FILE *stream, const char *format,...);
      
    • 参数
      • stream:指向要读取数据的文件流的指针。
      • format:指定数据的输入格式,类似于scanf函数中的格式字符串。
      • ...:指向用于存储读取数据的变量的地址。
  • fread函数
    • 功能:从指定的文件流中读取数据,并将数据存储到指定的内存位置。
    • 原型
      size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
      
    • 参数
      • ptr:指向用于存储读取数据的内存块的指针。该内存块的大小至少应该为size * nmemb字节,以确保能够容纳读取的数据。
      • size:每个数据项的大小(以字节为单位)。例如,如果要读取整数,size可以设置为sizeof(int)
      • nmemb:要读取的数据项的数量。函数将尝试读取nmemb个大小为size字节的数据项。
      • stream:指向要从中读取数据的文件流。
    • 返回值
      • 返回实际成功读取的数据项的数量。如果发生错误或到达文件末尾,返回值可能小于nmemb
  • fwrite函数
    • 功能:将数据写入指定的文件流中。
    • 原型
      size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
      
    • 参数
      • ptr:指向要写入的数据的指针。该指针指向的数据块将被写入到文件流中。
      • size:每个数据项的大小(以字节为单位)。例如,如果要写入整数,size通常设置为sizeof(int);若写入字符,size则为sizeof(char)
      • nmemb:要写入的数据项的数量。函数会尝试将nmemb个大小为size字节的数据项写入文件流。
      • stream:指向FILE对象的指针,表示数据要写入的目标文件流。
    • 返回值
      • 返回实际成功写入的数据项的数量。如果发生错误,返回值可能小于nmemb
(3)文件指针操作

  • fseek函数
    • 功能:用于移动文件指针的位置,从而实现对文件的随机访问。
    • 原型
      int fseek(FILE *stream, long int offset, int whence);
      
    • 参数
      • stream:指向要操作的文件流。
      • offset:指定要移动的字节数。可以是正数、负数或 0。
      • whence
        • SEEK_SET:从文件开头开始计算偏移量。
        • SEEK_CUR:从当前文件指针的位置开始计算偏移量。
        • SEEK_END:从文件末尾开始计算偏移量。
    • 返回值
      • 如果移动成功,返回 0;如果移动失败,返回非 0 值。
  • ftell函数
    • 功能:获取文件指针的当前位置,即相对于文件开头的偏移量(以字节为单位)。
    • 原型
      long int ftell(FILE *stream);
      
    • 参数:一个指向已打开文件的文件指针。
    • 返回值
      • 如果获取成功,返回当前文件指针的偏移量;如果获取失败,返回-1L
2. 错误处理

  • 错误函数
    • ferror函数
      • 功能:检查文件操作是否发生错误。
      • 原型
        int ferror(FILE *stream);
        
      • 参数:一个指向已打开文件的文件指针。
      • 返回值
        • 如果在文件操作过程中发生错误,返回非 0 值;如果没有发生错误,返回 0。
    • feof函数
      • 功能:检查文件是否到达末尾。
      • 原型
        int feof(FILE *stream);
        
      • 参数:一个指向已打开文件的文件指针。
      • 返回值
        • 如果文件指针已经到达文件末尾,返回非 0 值;如果文件指针没有到达文件末尾,返回 0。
    • clearerr函数
      • 功能:清除文件流的错误标志。
      • 原型
        void clearerr(FILE *stream);
        
      • 参数:一个指向已打开文件的文件指针。
3. 示例代码

  • 整体示例结构
    • 文章通过多个示例代码详细展示了文件操作的各种功能,包括不同的文件模式、读写操作、文件指针移动和错误处理等。
  • 具体示例代码
    • scanf_s的返回值示例
      int main(void) {
          int number;
          int result;
      
          puts("Enter an integer:");
      
          result = scanf_s("%d", &number);
      
          if (result == 1) {
              printf("You entered thr integer: %d", number);
          } else if (result == EOF) {
              // End of file -1
              // 专门用来指示文件读取或输入操作已经到达了数据源的末尾
              printf("An error occurred or end of file was reached.\n");
              return 1;                    // 直接退出函数
          } else {
              printf("Invalid input for integer.\n");
              return 1;
          }
      
          return 0;
      }
      
    • 文件流操作示例
      #include 
      #include 
      #include 
      #include 
      
      int main(void) {
          FILE *fileStream = NULL;
          char buffer[1023] = { 0 };
      
          // fopen_s()
      
          // 打开文件,设定文件路径要读取的文件,设定文件的操作模式
          errno_t err = fopen_s(&fileStream, "C:\\Users\\yangy\\Desktop\\Hello!.txt", "r");
      
          // 这里对返回值进行处理,!= 0说明读取失败或者是读取的流是一个NULL
          if (err!= 0 || fileStream == NULL) {
              // 这是专门用来错误处理的函数,正确地反馈错误
              perror("Error opening file");
              return EXIT_FAILURE;
          }
      
          // 需要对fopen进行处理,可能会出现文件路径不对,文件不存在,文件出现了异常,文件没有权限访问
      
          // 令fgets!= NULL即可一直读取下去
          while (fgets(buffer, sizeof(buffer), fileStream)!= NULL) {
              printf("%s", buffer);
          }
      
          // 之前用的字符串buffer此时里面是有东西的,之后我们不再使用了,就得确保他是空的,不然是一件非常不文明的事情
          memset(buffer, 0, sizeof(buffer));
      
          // 如果需要读取两次的话,第一次读的文件末尾是没有换行符的,需要手动加
          printf("\n");
      
          // 这个函数不管多长都能直接定位到文件开头
          fseek(fileStream, 0, SEEK_SET);
      
          // 也可以一个字符的读,一个字一个字往出蹦,不需要缓冲区但是比较慢
          int ch = 0;
      
          while ((ch = fgetc(fileStream))!= EOF) {
              putchar(ch);
          }
      
          // 最后一定要关闭文件流,关闭也可能出现问题,需要对fclose进行处理
          if (fclose(fileStream)!= 0) {
              perror("Error closing file");
              return EXIT_FAILURE;
          }
      
          return 0;
      }
      
    • fseek功能描述示例
      // 示例1:移动到文件开头
      FILE *fp = fopen("data.txt", "r");
      if (fp == NULL) {
          perror("文件打开失败");
          exit(EXIT_FAILURE);
      }
      // 将指针移动到文件开头(等价于rewind(fp))
      fseek(fp, 0, SEEK_SET);
      
      // 示例2:跳过前N字节
      // 跳过前100字节(从开头向后移动)
      fseek(fp, 100, SEEK_SET);
      // 读取后续内容
      char buffer[1024];
      fread(buffer, sizeof(char), 1024, fp);
      
      // 示例3:从当前位置移动
      // 假设当前指针在位置200
      // 向后移动50字节(正偏移)
      fseek(fp, 50, SEEK_CUR);    // 新位置 = 250
      // 向前移动30字节(负偏移)
      fseek(fp, -30, SEEK_CUR);    // 新位置 = 220
      
      // 示例4:移动到文件末尾
      // 移动到文件末尾
      fseek(fp, 0, SEEK_END);    // 用于追加数据或计算文件大小
      // 在末尾追加数据
      fputs("追加的内容", fp);
      
      // 示例5:读取倒数第N字节
      // 读取文件的最后10字节
      fseek(fp, -10, SEEK_END);    // 从末尾向前移动10字节
      char last10[11];
      fread(last10, sizeof(char), 10, fp);
      last10[10] = '\0';    // 添加字符串结束符
      printf("最后10字节: %s\n", last10);
      
      // 示例6:修改文件中间内容
      FILE *fp = fopen("data.bin", "r+"); // 读写模式
      if (fp == NULL)
          exit(EXIT_FAILURE);
      
      fseek(fp, 49, SEEK_SET);    // 移动到第50字节(索引从0开始)
      fputc('X', fp);    // 修改该字节
      fclose(fp);
      
      // 示例7:动态计算文件大小
      FILE *fp = fopen("data.bin", "rb");
      if (fp == NULL)
          exit(EXIT_FAILURE);
      
      fseek(fp, 0, SEEK_END);    // 移动到文件末尾
      long file_size = ftell(fp);    // 获取文件大小(字节数)
      rewind(fp);    // 回到开头
      
      printf("文件大小: %ld字节\n", file_size);
      
      // 示例8:复杂偏移组合
      // 假设文件指针初始位置为100
      fseek(fp, 100, SEEK_SET);
      
      // 从当前位置向前移动50字节
      fseek(fp, 50, SEEK_CUR);    // 新位置 = 150
      // 从文件末尾向前移动200字节
      fseek(fp, -200, SEEK_END);    // 适用于大文件末尾操作
      
    • fwritefread示例
      // 示例1:写入整数数组到二进制文件
      #include 
      
      #define ARRAY_SIZE 5
      
      int main() {
          FILE *file = fopen("data.bin", "wb");
          if (file == NULL) {
              perror("无法打开文件");
              return 1;
          }
      
          int numbers[ARRAY_SIZE] = {1, 2, 3, 4, 5};
          size_t items_written = fwrite(numbers, sizeof(int), ARRAY_SIZE, file);
      
          if (items_written < ARRAY_SIZE) {
              if (ferror(file)) {
                  perror("写入文件时发生错误");
              }
          } else {
              printf("成功写入 %zu个整数到文件。\n", items", items_written);
          }
      
          fclose(file);
          return 0;
      }
      
      // 示例2:写入字符串到文件
      int main() {
          FILE *file = fopen("text.txt", "w");
          if (file == NULL) {
              perror("无法打开文件");
              return 1;
          }
      
          char str[] = "Hello, World!";
          size_t items_written = fwrite(str, sizeof(char), sizeof(str), file);
      
          if (items_written < sizeof(str)) {
              if (ferror(file)) {
                  perror("写入文件时发生错误");
              }
          } else {
              printf("成功写入字符串到文件。\n");
          }
      
          fclose(file);
          return 0;
      }
      
    • 复制文件示例
      #include 
      #include 
      #include 
      
      int main(void) {
          FILE *source_file = NULL;
          FILE *target_file = NULL;
          char source_path[] = "C:\\Users\\yangy\\Desktop\\abc.log";
          char target_path[] = "C:\\Users\\yangy\\Desktop\\log.txt";
      
          errno_t sourerr = fopen_s(&source_file, source_path, "rb");
          if (sourerr!= 0 || source_file == NULL) {
              perror("Error to open source file");
              return EXIT_FAILURE;
          }
      
          errno_t tarerror = fopen_s(&target_file, target_path, "wb");
          if (tarerror!= 0 || target_file == NULL) {
              perror("Error to open target file");
              return EXIT_FAILURE;
          }
      
          size_t byte_read = 0;
          char buffer[256] = { 0 };
      
          while ((byte_read = fread(buffer, 1, sizeof(buffer), source_file)) > 0) {
              fwrite(buffer, 1, byte_read, target_file);
          }
      
          _fcloseall();
      
          printf("succussfully");
          return 0;
      }
      
    • 其他相关示例
      • 保存游戏设置
        #include 
        #include 
        #include 
        
        typedef enum {
            ESAY,
            MIDDLE,
            HARD
        } Difficulty;
        
        typedef struct {
            float volumn;
            int resolution_x;
            int resolution_y;
            Difficulty Difficulty;
        } Settings;
        
        // 读取设置
        void load_game_settings(const Settings *settings, const char *filename);
        // 保存设置
        void save_game_settings(const Settings *settings, const char *filename);
        
        int main(void) {
            Settings settings = { 0.75, 1920, 1080, MIDDLE };
        
            save_game_settings(&settings, "C:\\Users\\yangy\\Desktop\\log.txt");
        
            Settings load_setting = { 0 };
        
            load_game_settings(&load_setting, "C:\\Users\\yangy\\Desktop\\log.txt");
        
            printf("volunm : %f\nresolution_x : %d\nresolution_y : %d\ndifficulty : %d", load_setting.volumn, load_setting.resolution_x, load_setting.resolution_y, load_setting.Difficulty);
        
            return 0;
        }
        
        void save_game_settings(const Settings *settings, const char *filename) {
            FILE *file_stream = NULL;
        
            errno_t err = fopen_s(&file_stream, filename, "wb");
            if (err!= 0 || file_stream == NULL) {
                char config_msg[256] = { 0 };
        
                strerror_s(config_msg, sizeof(config_msg), errno);
        
                fprintf(stderr, "Error to open the file: %s", config_msg);
        
                exit(EXIT_FAILURE);
            }
        
            // 总共有四个参数,分别是指向缓冲区的指针,所读取数据的大小,每次读取的数目
            fwrite(settings, sizeof(Settings), 1, file_stream);
        
            fclose(file_stream);
        }
        
        void load_game_settings(const Settings *settings, const char *filename) {
            FILE *file_stream = NULL;
        
            errno_t err = fopen_s(&file_stream, filename, "rb");
            if (err!= 0 || file_stream == NULL) {
                char config_msg[256] = { 0 };
        
                strerror_s(config_msg, sizeof(config_msg), errno);
        
                fprintf(stderr, "Error to open the file: %s", config_msg);
        
                exit(EXIT_FAILURE);
            }
        
            // 总共有四个参数,分别是指向缓冲区的指针,所读取数据的大小,每次读取的数目
            fread(settings, sizeof(Settings), 1, file_stream);
        
            fclose(file_stream);
        }
        

二、相关函数和注意事项

(一)fopen_s的不同模式

1. 基础模式

模式 描述 文件存在 文件不存在 文件指针位置 操作权限
r 只读 打开文件 报错 开头 只能读
w 只写 清空 创建文件 开头 只能写
a 追加 打开文件 创建文件 末尾 只能写
2. 组合模式(读写模式)

模式 描述 文件存在 文件不存在 文件指针位置 操作权限
r+ 读写(更新模式) 打开文件 报错 开头 可读可写
w+ 读写(清空并创建) 清空 创建文件 开头 可读可写
a+ 读写(追加模式) 打开文件 创建文件 末尾 可读可写

  • a+模式下,写入总是追加到末尾,但读取可以定位到任意位置(需先调用fseek调整指针)。
  • r+w+的主要区别:r+不删除原内容,w+会清空原内容。
3. 二进制模式

在模式后添加b(如rbwb+),用于处理二进制文件。

  • 文本模式 vs 二进制模式
    • 文本模式:自动处理换行符(如 Windows 将\r\n转为\n)。
    • 二进制模式:直接操作原始字节(无转换)。

(二)scanf的格式控制符处理

1. 使用%s读取字符串时

当使用%s格式化控制符时,scanf不会读取空格、制表符(\t)和换行符(\n),它会将这些字符视为字符串的分隔符。一旦遇到这些空白字符,scanf就会停止读取,并在字符串末尾自动添加字符串结束符'\0'

2. 使用%c读取字符时

当使用%c格式化控制符时,scanf会读取包括空格、制表符和换行符在内的任意字符。也就是说,空格对于%c来说就是一个普通的字符。

3. 使用其他格式控制符(如%d%f等)读取数值时

对于读取整数(%d)、浮点数(%f)等数值类型的格式控制符,scanf会自动跳过前导的空白字符(空格、制表符、换行符),直到遇到有效的数字字符才开始读取。

(三)文本文件和二进制文件的区别

  • 文本文件
    • 在文本文件中,某些系统的换行符可能会被转换,导致fseek偏移量与实际字节数不一致。
  • 二进制文件
    • 直接操作原始字节,不会进行换行符的转换。

from micro_frank

你可能感兴趣的:(c语言)