【问题标题】:Why does fputc() append to my file and not overwrite it?为什么 fputc() 附加到我的文件而不覆盖它?
【发布时间】:2021-03-06 08:31:30
【问题描述】:

我正在编写一个 C 程序,将某个文件中的所有大写字符更改为小写。问题是,在将所有字符(已处理)存储在字符串中并尝试打印该文件中的所有字符之后,它们被附加到该文件中,而不是被覆盖。我使用"r+" 作为权限。

这是我的代码:

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

#define MAX 1024

int main(int argc, char **argv)
{
    if (argc != 2) {
        printf("Invalid number of arguments.\n");
        exit(1);
    }

    FILE *f = fopen(argv[1], "r+");
    if (f == NULL)
    {
        printf("Could not open specified file.\n");
        exit(2);
    }

    char buf[MAX];
    int len = 0;
    
    while(!feof(f))
    {
        int c = fgetc(f);
        if (c >= 'A' && c <= 'Z')
            c = tolower(c);
        if (c != EOF) {
            buf[len] = c;
            len++;
        }
    }

    for (int i = 0; buf[i] != '\0'; i++)
        fputc(buf[i], f);

    fclose(f);

    return 0;
}

fputs(buf, f) 也有同样的问题。即使在附加之后,一些“奇怪”的字符也会出现在所选文件的末尾。

到底是什么问题?我该如何解决?谢谢你。帮助将不胜感激。

【问题讨论】:

  • 因为你用r+打开文件
  • @PaulOgilvie 打开它的正确方法是什么?我想读它,也想写它。
  • 读取文件后,关闭并用w重新打开覆盖。
  • 注意:while(!feof(f)) 不会按照你的想法做。查找文档。
  • 注意:char buf[MAX]; 未初始化。 for (...buf[i] != '\0' 将不起作用。

标签: c file fgetc


【解决方案1】:

由于多种原因,您的程序存在错误甚至未定义的行为:

  • while(!feof(f)) 无法可靠地检测文件结尾,feof() 设置为 尝试从f 读取失败。你应该写:

      while ((c = getc(f)) != EOF) {
          if (isupper(c))
              c = tolower(c);
          ...
    
  • 使用if (c &gt;= 'A' &amp;&amp; c &lt;= 'Z') 测试大写字母适用于 ASCII,但对于 EBCDIC 会失败。您可以改用isupper(c) 或无条件使用tolower(c)。

  • 读取和写入同一个文件,使用"r+" 打开读取和更新模式,需要调用fseek() 或rewind() 才能从读取更改为写入,反之亦然。

  • 您没有检查len 是否位于buf 的边界内,这会在尝试写入超出数组末尾以获得足够长的输入时导致未定义的行为。

  • 您没有在buf[len] 处设置空终止符,因此调用fputs(buf, f) 具有未定义的行为,因为buf 不是正确的C 字符串。同样,发布的代码中的循环迭代 while buf[i] != '\0' 这会导致未定义的行为,因为该空终止符尚未设置在从文件读取的数据的末尾。

这是修改后的版本:

#include <ctype.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    FILE *f;
    int c;

    if (argc != 2) {
        printf("Invalid number of arguments.\n");
        return 1;
    }

    if ((f = fopen(argv[1], "r+")) == NULL) {
        printf("Could not open file %s.\n", argv[1]);
        return 2;
    }

    while ((c = getc(f)) != EOF) {
        if (isupper(c) {
            fseek(f, -1L, SEEK_CUR);
            putc(tolower(c), f);
            fseek(f, 0L, SEEK_CUR);
        }
    }
    fclose(f);
    return 0;
}

请注意,从文件中读取并写入不同的文件会更加高效和可靠。你可以试试这个简单的过滤器,并与上面的代码比较大文件:

#include <ctype.h>
#include <stdio.h>

int main() {
    int c;
    while ((c = getchar()) != EOF) {
        putchar(tolower(c));
    }
    return 0;
}

【讨论】:

  • 鉴于他们在添加到 buf 之前明确检查了 c != EOF,那么文件结尾处理在这里实际上是错误的吗?不过,删除对EOF 的检查会在输出中带来额外的\377。
  • @ilkkachu:你是对的,输出中虚假字节的原因在其他地方,在输出循环中测试从未设置的空终止符。读取循环逻辑有缺陷,但在这种特殊情况下不应引起问题,但应标记 feof() 的每次无效使用:stackoverflow.com/questions/5431941/…
  • 好吧。它在这里看起来并不完全有缺陷。据我所见,在所有情况下,它都会根据fgetc() 的返回值正确测试EOF。 (EOF 可能不会在A 和Z 之间的范围测试中命中,据我所知,tolower() 无论如何都应该将其原封不动地返回。)当然循环可能很多结构更清晰,例如break'ing out if c == EOF.虽然使用 feof() 错误似乎是一个常见错误,但我认为我们不应该过于严厉地批评那些碰巧真正正确使用它的人。
  • @ilkkachu:我希望我没有像 harsh 那样通过......正如我在上面写的那样,循环有效,但逻辑有缺陷,需要双倍测试以避免错误的输出。在读取文件之前测试feof(f) 是一个常见错误,表明对流有一些误解。应该教初学者阅读和测试错误的返回值:while ((c = getc(f)) != EOF) 是这种情况下的经典习语,while (!feof(f)) is always wrong :)
  • 好吧,“苛刻”可能有点过分...... harsh 一个词的选择 :P 如果初学者得到这样的陷阱,我可能会很高兴,即使如果它没有尽可能干净,因为在那之后清理会更容易,而不是如果它是错误。如果他们能理解为什么,那就更好了,这可能是指关联的问题。 (除了我认为最好的答案开始于跑题。)但也许我们在这一点上得到的回报相当减少。 :)
猜你喜欢
  • 2015-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-07
  • 2015-03-21
  • 2013-02-09
  • 2012-06-18
  • 2016-04-04
相关资源
最近更新 更多