【问题标题】:Going through a text file line by line in C在 C 中逐行浏览文本文件
【发布时间】:2012-03-01 15:24:32
【问题描述】:

我一直在为我的 CIS 课程做一个小练习,并且对 C 用于读取文件的方法感到非常困惑。我真正需要做的就是逐行读取文件,并使用从每一行收集的信息进行一些操作。我尝试使用 getline 方法和其他没有运气的方法。 我的代码目前如下:

int main(char *argc, char* argv[]){
      const char *filename = argv[0];
      FILE *file = fopen(filename, "r");
      char *line = NULL;

      while(!feof(file)){
        sscanf(line, filename, "%s");
        printf("%s\n", line);
      }
    return 1;
}

现在我遇到了 sscanf 方法的 seg 错误,我不知道为什么。我是一个总 C 菜鸟,只是想知道我是否遗漏了一些重要的事情。 谢谢

【问题讨论】:

标签: c getline scanf


【解决方案1】:

这么几行就这么多问题。我可能忘记了一些:

  • argv[0]是程序名,不是第一个参数;
  • 如果你想读入一个变量,你必须分配它的内存
  • 一个永远不会在 feof 上循环,一个在 IO 函数上循环直到它失败,然后 feof 用于确定失败的原因,
  • sscanf是用来解析一行的,如果你想解析一个文件,使用fscanf,
  • “%s”将作为 ?scanf 系列的格式在第一个空格处停止
  • 读取一行,标准函数是fgets,
  • 从 main 返回 1 表示失败

所以

#include <stdio.h>

int main(int argc, char* argv[])
{
    char const* const fileName = argv[1]; /* should check that argc > 1 */
    FILE* file = fopen(fileName, "r"); /* should check the result */
    char line[256];

    while (fgets(line, sizeof(line), file)) {
        /* note that fgets don't strip the terminating \n, checking its
           presence would allow to handle lines longer that sizeof(line) */
        printf("%s", line); 
    }
    /* may check feof here to make a difference between eof and io failure -- network
       timeout for instance */

    fclose(file);

    return 0;
}

【讨论】:

  • 在返回之前不要忘记fclose(file)
  • fclose(file) 实际上没有必要,因为它发生在main 中,它会自动关闭所有打开的文件缓冲区。
  • @Leandros 安全总比抱歉好!
  • 对于初学者来说还是不错的,因为有时即使在 main 的末尾也是必要的。 FILE* 对象在 C 中缓冲,因此如果正在将数据写入文件并且未调用 fclose,则某些数据可能不会被刷新。
  • 嗨,@alecRN:你确定吗? AFAIK,当程序通过调用 exit 终止时,流上的缓冲输出会自动刷新(请参阅:gnu.org/software/libc/manual/html_node/Flushing-Buffers.html),并且操作系统将决定何时刷新(可以调用 fsync)。在执行结束时有一个对 exit_group 的隐式调用,您可以使用 strace 和 nm 看到它。我想它不是由 gcc 添加的,因为没有这样的符号,可能是运行时添加的。甚至 _exit 也会关闭打开的文件描述符。无论如何,我同意你的观点,明确关闭打开的文件是一个好习惯 /Ángel
【解决方案2】:

要从文件中读取一行,您应该使用fgets 函数:它从指定文件中读取一个字符串,直到换行符或EOF

在您的代码中使用sscanf 根本不起作用,因为您使用filename 作为格式字符串,用于从line 读取到常量字符串文字%s

SEGV的原因是你写入了line指向的未分配内存。

【讨论】:

    【解决方案3】:

    除了其他答案之外,在最近的 C 库(符合 Posix 2008)上,您可以使用getline。请参阅this answer(相关问题)。

    【讨论】:

      【解决方案4】:

      假设您正在处理一些其他分隔符,例如 \t 制表符,而不是 \n 换行符。

      一种更通用的分隔符方法是使用getc(),它一次抓取一个字符。

      请注意,getc() 返回一个int,以便我们可以测试与EOF 是否相等。

      其次,我们定义了一个char 类型的数组line[BUFFER_MAX_LENGTH],以便在堆栈上最多存储BUFFER_MAX_LENGTH-1 个字符(我们必须将最后一个字符保存为\0 终止符)。

      使用数组避免了使用mallocfree 在堆上创建正确长度的字符指针的需要。

      #define BUFFER_MAX_LENGTH 1024
      
      int main(int argc, char* argv[])
      {
          FILE *file = NULL;
          char line[BUFFER_MAX_LENGTH];
          int tempChar;
          unsigned int tempCharIdx = 0U;
      
          if (argc == 2)
               file = fopen(argv[1], "r");
          else {
               fprintf(stderr, "error: wrong number of arguments\n"
                               "usage: %s textfile\n", argv[0]);
               return EXIT_FAILURE;
          }
      
          if (!file) {
               fprintf(stderr, "error: could not open textfile: %s\n", argv[1]);
               return EXIT_FAILURE;
          }
      
          /* get a character from the file pointer */
          while(tempChar = fgetc(file))
          {
              /* avoid buffer overflow error */
              if (tempCharIdx == BUFFER_MAX_LENGTH) {
                  fprintf(stderr, "error: line is too long. increase BUFFER_MAX_LENGTH.\n");
                  return EXIT_FAILURE;
              }
      
              /* test character value */
              if (tempChar == EOF) {
                  line[tempCharIdx] = '\0';
                  fprintf(stdout, "%s\n", line);
                  break;
              }
              else if (tempChar == '\n') {
                  line[tempCharIdx] = '\0';
                  tempCharIdx = 0U;
                  fprintf(stdout, "%s\n", line);
                  continue;
              }
              else
                  line[tempCharIdx++] = (char)tempChar;
          }
      
          return EXIT_SUCCESS;
      }
      

      如果你必须使用char *,那么你仍然可以使用这个代码,但是你strdup()line[]数组,一旦它被填满了一行的输入。完成后必须free这个重复的字符串,否则会出现内存泄漏:

      #define BUFFER_MAX_LENGTH 1024
      
      int main(int argc, char* argv[])
      {
          FILE *file = NULL;
          char line[BUFFER_MAX_LENGTH];
          int tempChar;
          unsigned int tempCharIdx = 0U;
          char *dynamicLine = NULL;
      
          if (argc == 2)
               file = fopen(argv[1], "r");
          else {
               fprintf(stderr, "error: wrong number of arguments\n"
                               "usage: %s textfile\n", argv[0]);
               return EXIT_FAILURE;
          }
      
          if (!file) {
               fprintf(stderr, "error: could not open textfile: %s\n", argv[1]);
               return EXIT_FAILURE;
          }
      
          while(tempChar = fgetc(file))
          {
              /* avoid buffer overflow error */
              if (tempCharIdx == BUFFER_MAX_LENGTH) {
                  fprintf(stderr, "error: line is too long. increase BUFFER_MAX_LENGTH.\n");
                  return EXIT_FAILURE;
              }
      
              /* test character value */
              if (tempChar == EOF) {
                  line[tempCharIdx] = '\0';
                  dynamicLine = strdup(line);
                  fprintf(stdout, "%s\n", dynamicLine);
                  free(dynamicLine);
                  dynamicLine = NULL;
                  break;
              }
              else if (tempChar == '\n') {
                  line[tempCharIdx] = '\0';
                  tempCharIdx = 0U;
                  dynamicLine = strdup(line);
                  fprintf(stdout, "%s\n", dynamicLine);
                  free(dynamicLine);
                  dynamicLine = NULL;
                  continue;
              }
              else
                  line[tempCharIdx++] = (char)tempChar;
          }
      
          return EXIT_SUCCESS;
      }
      

      【讨论】:

      • 我会对任何while(!feof(file)) 投反对票,即使是在不可损坏的蓝月亮出现的情况下(请注意,这里可能永远不会是真的,有一个休息时间可以离开在这种情况下,循环 while (true) 也可以。)有太多人认为这是正确的习惯用法。
      • 我不知道这是个问题。老实说,我很想了解更多有关这方面的信息。这种用法有什么问题?
      • 出现了很多问题,例如stackoverflow.com/questions/5431941/…
      • 好的,我修复了循环。感谢您的指点。我每天都能学到新东西。
      猜你喜欢
      • 1970-01-01
      • 2012-10-26
      • 1970-01-01
      • 2012-10-11
      • 1970-01-01
      • 2020-09-05
      • 2021-12-30
      • 2020-09-11
      • 1970-01-01
      相关资源
      最近更新 更多