【问题标题】:Reading and writing to a file at the same time in C在 C 中同时读取和写入文件
【发布时间】:2017-06-22 03:28:44
【问题描述】:

假设在文件中每两行交换一次,直到只剩下一行或所有行都用完。我不想在这样做时使用另一个文件。

这是我的代码:

#include <stdio.h>

int main() {
    FILE *fp = fopen("this.txt", "r+");
    int i = 0;
    char line1[100], line2[100];
    fpos_t pos;
    fgetpos(fp, &pos);

    //to get the total line count
    while (!feof(fp)) {
        fgets(line1, 100, fp);
        i++;
    }

    i /= 2;  //no. of times to run the loop
    rewind(fp);

    while (i-- > 0) {  //trying to use !feof(fp) condition to break the loop results in an infinite loop
        fgets(line1, 100, fp);
        fgets(line2, 100, fp);

        fsetpos(fp, &pos);

        fputs(line2, fp);
        fputs(line1, fp);

        fgetpos(fp, &pos);
    }

    fclose(fp);
    return 0;
}

this.txt 中的内容:

aaa
b
cc
ddd
ee  
ffff
gg
hhhh
i
jj

程序运行后的内容

b
aaa
ddd
cc
ddd
c
c

c


i
jj

我什至尝试使用fseek 代替fgetpos 只是为了得到同样的错误结果。

据我所知,在第二个 while 循环运行了两次 (即前四行已被处理) 之后,光标正确地位于应该是 (由对ftell(fp) 的调用返回) 甚至第四行之后的文件内容都没有改变,并且由于某种原因,当循环第三次运行时调用fgets 时,内容读入数组 line1 和 line2 分别是 "c\n" 和 "ddd\n"。

再次,我不想使用其他文件来完成此操作,我只需要弄清楚屏幕后面到底出了什么问题

任何线索将不胜感激。谢谢你。

【问题讨论】:

  • fputs(line1,fp); --> fputs(line1,fp);fflush(fp);
  • 为什么不将文件加载到内存中,然后在换行时将其写回?或者如果这不可能,重命名文件并同时读写
  • @kaetzacoatl,将其加载到 ram 中是指将所有内容存储到一个大字符数组中,对吗?还是您在谈论其他可能性?一个大字符数组绝对是一种选择,但我只是想知道我的代码有什么问题。如果我只使用一个文件,同时重命名和读取和写入不会有任何区别,对吗?
  • @BLUEPIXY 是的,它现在可以工作了。但我不明白为什么有这种明确的需要 flush ?为什么它不会自动刷新,在什么情况下会?我刚刚阅读了另一个这样的问题,其中一个接受的答案是最后的换行符会自动刷新内容。但是他们在谈论标准输出流,这不适用于文件流吗?
  • 如果缓冲区用于输出(在系统上),它取决于刷新时的实现。即使程序从内存中刷新,系统也可能正在缓冲它。

标签: c file-handling fgets fgetpos


【解决方案1】:

您的代码中存在多个问题:

  • 您不检查fopen() 是否成功,冒着未定义行为的风险。

  • 确定总行数的循环不正确。
    在此处了解原因:Why is “while ( !feof (file) )” always wrong?

  • 您实际上不需要计算总行数。

  • 您应该调用fflush() 将内容写回文件,然后再从写回变为读取。

C 标准对以更新模式打开的文件指定了此限制:

7.21.5.3 fopen 函数

[...] 如果没有对fflush 函数或文件定位函数(fseekfsetposrewind)的干预调用,则输出不应直接跟随输入,并且输入应在没有对文件定位函数的介入调用的情况下,不能直接跟在输出之后,除非输入操作遇到文件结尾。

这解释了为什么在以相反顺序写入行后仅读取文件位置会导致问题。调用fflush() 应该可以解决这个问题。

这是一个更正的版本:

#include <stdio.h>

int main(void) {
    FILE *fp;
    char line1[100], line2[100];
    fpos_t pos;

    fp = fopen("this.txt", "r+");
    if (fp == NULL) {
        fprintf(stderr, "cannot open this.txt\n");
        return 1;
    }

    while (fgetpos(fp, &pos) == 0 &&
           fgets(line1, sizeof line1, fp) != NULL &&
           fgets(line2, sizeof line2, fp) != NULL) {

        fsetpos(fp, &pos);
        fputs(line2, fp);
        fputs(line1, fp);
        fflush(fp);    
    }

    fclose(fp);
    return 0;
}

【讨论】:

  • 感谢您的回答。对所有帐户都正确。我计算了总行数,因为我无法使用 !feof(fp) 完成它,但似乎这样做也是不可接受的。
  • "...在以相反顺序写入行后读取文件位置会导致问题。"但是当前读取了文件位置,不是吗?调用ftell(fp) 返回了一个适当的光标位置,正是调用fputs 之后的计数。是fgets 读错了东西。我在想问题出在fgets,它正在读取未刷新的数据。我哪里弄错了?
  • @subzero: just 读取文件位置不允许从写入变为读取,所以确实fgets() 没有读取正确的内容. fflush() 解决了这个问题。你也可以写fgetpos(fp, &amp;pos); fsetpos(fp, &amp;pos);,但我发现在循环顶部有一个fgetpos(fp, &amp;pos) 更一致。
  • 明白。谢谢
【解决方案2】:

更改文件的当前位置时,可能不一定会刷新缓冲区。所以必须显式刷新。

例如使用fflush(fp);

改变

fputs(line2,fp);
fputs(line1,fp);

fputs(line2,fp);
fputs(line1,fp);
fflush(fp);

【讨论】:

  • 我对C文件处理不是很熟悉,但是为什么需要刷新它呢?无论是否刷新,输入缓冲区都不应该保持不变吗?由于您没有阅读您所写的内容,因此它应该没有效果。请解释一下。
  • 你所说的似乎模棱两可。作为一点,更改必须反映在文件中。
【解决方案3】:

为什么不使用两个文件指针,都指向同一个文件,一个读一个写?无需跟踪文件位置,无需四处寻找,无需刷新。

这种方法可以省去很多复杂的东西。这些不必要的努力最好投资于一些复杂的错误检查/记录,如下所示;-):

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void) 
{
  int result = EXIT_SUCCESS;

  size_t blocks = 0;

  int l1_done = 0;
  int l2_done = 0;

  FILE *fpin = fopen("this.txt", "r");
  FILE *fpout = fopen("this.txt", "r+");

  if (NULL == fpin)
  {
    result = EXIT_FAILURE;
    perror("fopen() to for reading failed");
  }    

  if (NULL == fpout)
  {
    result = EXIT_FAILURE;
    perror("fopen() for writing failed");
  }    

  while (EXIT_SUCCESS == result && !l1_done && !l2_done)
  {
    result = EXIT_FAILURE;

    char line1[100];
    char line2[100];

    if ((l1_done = (NULL == fgets(line1, sizeof line1, fpin))))
    {
      if (ferror(fpin))
      {
        fprintf(stderr, "Reading line %zu failed.\n", 2*blocks);
        break;
      }
    }

    if ((l2_done = (NULL == fgets(line2, sizeof line2, fpin))))
    {
      if (ferror(fpin))
      {
        fprintf(stderr, "Reading line %zu failed.\n", 2*blocks + 1);
        break;
      }
    }

    {
      size_t len = strlen(line1);

      if (((sizeof line1 - 1) == len) && ('\n' != line1[len]))
      {
        fprintf(stderr, "Line %zu too long or new-line missing.\n", 2*blocks);
        break;
      } 
    }

    {
      size_t len = strlen(line2);

      if (((sizeof line2 - 1) == len) && ('\n' != line2[len]))
      {
        fprintf(stderr, "Line %zu too long or new-line missing.\n", 2*blocks + 1);
        break;
      }
    } 

    if (!l2_done)
    {
      if (EOF == fputs(line2, fpout))
      {
        fprintf(stderr, "Writing line %zu as line %zu failed.\n", 2*blocks + 1, 2*blocks);
        break;
      }
    } 

    if (!l1_done)
    {
      if (EOF == fputs(line1, fpout))
      {
        fprintf(stderr, "Writing line %zu as line %zu failed.\n", 2*blocks, 2*blocks + 1);
        break;
      } 
    }

    ++blocks;

    result = EXIT_SUCCESS;
  }

  if (EXIT_SUCCESS == result && !ll_done && l2_done)   
  {
    fprintf(stderr, "Odd number of lines.\n");
  }

  fclose(fpin);  /* Perhaps add error checking here as well ... */
  fclose(fpout);  /* Perhaps add error checking here as well ... */

  return result;
}

【讨论】:

  • 感谢您的回答。很好的解决方法和非常优雅的错误检查。只是一件小事,在最底部的if 语句中,f1_done &amp;&amp; f2_done 不应该是l1_done &amp;&amp; l2_done
  • @subzero:“只是一件小事……”:当然。感谢您向我指出这个草率、未经测试、迟到的变化...... - 已修复。
猜你喜欢
  • 2010-10-10
  • 1970-01-01
  • 1970-01-01
  • 2013-12-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多