在C语言中同时读写一个文件

7

需要交换文件中每两行的内容,直到只剩下一行或所有行都被处理完。我不想使用另一个文件。

以下是我的代码:

#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;
}

content in 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循环运行两次后(即前四行已被处理),光标在第17个字节处是正确的(如调用ftell(fp)所返回的那样),并且第4行之后的文件内容没有改变,但由于某种原因,当第三次循环运行时调用fgets时,读入数组line1和line2的内容分别为"c\n"和"ddd\n"。再次强调,我不想使用另一个文件来完成这个任务,我只需要找出屏幕背后到底发生了什么问题。如果您有任何线索,请告诉我,谢谢!

fputs(line1,fp); --> fputs(line1,fp);fflush(fp); - BLUEPIXY
@BLUEPIXY 是的,现在它可以工作了。但是我不明白为什么需要显式刷新?为什么它不能自动刷新,在什么情况下会自动刷新?我刚刚读到另一个SO问题,其中有一个被接受的答案说,在结尾处添加换行符会自动刷新。但他们谈论的是stdout流,这是否适用于文件流? - ps_
如果在系统上使用缓冲区进行输出,则取决于实现何时刷新。即使程序从内存中刷新,系统仍可能对其进行缓冲。 - BLUEPIXY
无论如何,在移动当前位置之前,这是一种保护性代码,用于清空文件。 - BLUEPIXY
@BLUEPIXY:从技术上讲,移动位置就足以清除已写入的内容,但是OP并没有移动位置,他只是读取了新位置。 - chqrlie
显示剩余3条评论
3个回答

4
你的代码存在多个问题:
  • 你没有检查fopen()是否成功,可能会导致未定义的行为。

  • 用于确定总行数的循环是不正确的。
    在这里了解原因: 为什么“while ( !feof (file) )”总是错误的?

  • 实际上,你并不需要计算总行数。

  • 在从写入切换到读取之前,应该调用fflush()将内容写回文件。

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

7.21.5.3 fopen函数

[...] 输出不能直接跟随输入而没有中间调用fflush函数或文件定位函数(fseek, fsetposrewind),除非输入操作遇到文件结束。输入不能直接跟随输出而没有中间调用文件定位函数,除非输入操作遇到文件结束。

这就解释了为什么只是按相反顺序写入行后读取文件位置会导致问题。调用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)完成它,所以我计算了总行数,但似乎那样做也不可接受。 - ps_
“……在将行按相反顺序写入后读取文件位置会导致问题。”但是,文件位置已经被正确读取了,不是吗?调用ftell(fp)返回了光标的适当位置,正好是在调用fputs之后应该出现的计数。实际上是fgets读取了错误的内容。我一直以为问题出在fgets上,认为它读取了未刷新的数据。我错在哪里了? - ps_
1
@subzero:仅仅读取文件位置并不能从写入切换到读取,因此实际上fgets()没有读取正确的内容。使用fflush()可以解决这个问题。你也可以使用fgetpos(fp, &pos); fsetpos(fp, &pos);来实现相同的效果,但是我觉得在循环的顶部只使用一次fgetpos(fp, &pos)更加一致。 - chqrlie
明白了。谢谢。 - ps_

3

当改变文件的当前位置时,缓冲区不一定会被刷新。因此必须显式地刷新缓冲区。

例如使用fflush(fp);

更改

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

to

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

我对C文件处理不是很熟悉,但为什么需要刷新它呢?无论是否刷新,输入缓冲区都应该保持不变,对吗?由于您不读取已写入的内容,因此不应产生影响。请解释一下。 - kaetzacoatl
你说的话似乎有歧义。 作为一个要点, 修改必须在文件中得到体现。 - BLUEPIXY

2
为什么不使用两个文件指针,都指向同一个文件,一个用于读取,一个用于写入?无需跟踪文件位置,无需搜索,也无需刷新。这种方法可以节省很多复杂的工作。那些不必要的努力最好投资于一些复杂的错误检查/日志记录,如下所示;-):
#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;
}

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - ps_
@subzero:“只有一件小事……”:绝对没错。感谢您指出这个草率、未经测试的、晚期更改……已修复。 - alk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接