C语言 为什么要放置两次SEEK_SET

m2xkgtsf  于 2023-02-03  发布在  其他
关注(0)|答案(4)|浏览(146)

我想用“5”修改一个文件的一些元音。下面的代码可以工作。但是,我不明白为什么要把fseek放两次。

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

void print_file_contents(const char *filename) 
{
    FILE *fp;
    char letter;

    if((fp=fopen(filename,"r+"))==NULL)
    {
        printf("error\n");
        exit(1);
    }

    fseek(fp,0,SEEK_END);
    int size=ftell(fp);
    rewind(fp);

    for(int i=0;i<size;i++)
    {
        fseek(fp,i,SEEK_SET);
        letter=fgetc(fp);
    
        if((letter=='a') || (letter=='e') || (letter=='i'))
        {
            fseek(fp,i,SEEK_SET); // WHY THIS FSEEK ?
            fwrite("5",1,sizeof(char),fp);
        }

    }

    fclose(fp);
}

int main(int argc, char *argv[])
{
    print_file_contents("myfile");
    return 0;
}

在我看来,第一个fseek(fp, i, SEEK_SET)用于将文件位置指示符设置为当前正在处理的字符,以便可以使用fgetc读取该字符,因此,每次都会更新光标,因此不需要添加另一个fseek(fp, i, SEEK_SET);

nsc4cvqm

nsc4cvqm1#

fgetc推进了文件位置;如果你想替换你刚读到的字符,你需要倒带到你读到要替换的字符时所在的位置。

ttcibm8c

ttcibm8c2#

请注意,当您在阅读和写之间(以及在写和读之间)切换时,C标准强制执行类似于seek的操作。
§7.21.5.s fopen函数¶7:
¶7当文件以更新模式打开时(“+”作为上述模式参数值列表中的第二或第三个字符),则输入和输出都可以在关联流上执行。但是,在没有对fflush函数或文件定位函数的中间调用的情况下,输出之后不应直接跟随输入(fseekfsetposrewind),并且除非输入操作遇到文件结束,否则在没有对文件定位函数的中间调用的情况下,输入之后不应直接跟随输出。
此外,调用fgetc()会将文件位置向前移动一个字符;如果写操作成功(如果省略了类似seek的操作,这是未定义的行为),则将覆盖下一个字符,而不是刚刚读取的字符。

pgky5nke

pgky5nke3#

你的直觉是正确的:本程序中三个fseek调用中的 * 两个 * 是不必要的。
必要的fseekif((letter=='a') || (letter=='e') || (letter=='i'))条件中的一个,它用来备份文件位置,这样你就可以覆盖你刚刚读到的字符(即元音),而不是元音后面的字符。
循环内部的fseek(但在if之外)是不必要的,因为fgetcfwrite都将文件位置前移,所以它总是将文件位置设置为它已经拥有的位置;循环之前的fseek是不必要的,因为您不需要知道文件有多大就可以实现此算法。
这段代码可以被压缩得很紧,我会这样写:

#include <stdio.h>

void replace_aie_with_5_in_place(const char *filename) 
{
    FILE *fp = fopen(filename, "r+");  // (1)
 
    if (!fp) {
        perror(filename);  // (2)
        exit(1);
    }

    int letter;
    while ((letter = fgetc(fp)) != EOF) {  // (3)
        if (letter == 'a' || letter == 'e' || letter == 'i') { // (4)
            fseek(fp, -1, SEEK_CUR); // (5)
            fputc('5', fp);
            if (fflush(fp)) { // (6)
                perror(filename);
                exit(1);
            }
    }

    if (fclose(fp)) {  // (7)
        perror(filename);
        exit(1);
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "usage: %s filename\n", argv[0]);
        return 1;
    }
    replace_aei_with_5_in_place(argv[1]); // (8)
    return 0;
}

注:
1.通常(但不总是)最好将具有副作用的操作(如fopen)与检查它们是否成功的条件分开编写。
1.当系统级操作失败时,总是打印所涉及的任何文件的名称和errno的解码值。perror(filename)是一种方便的方法。
1.你不需要知道你正在处理的文件的大小,因为你可以使用这样的循环,这也是(1)的一个例外。
1.为什么'o''u'也不行呢?
1.下面是对fseek的必要调用,以及不需要知道文件大小的 * 其他 * 原因:你可以使用SEEK_CUR来备份一个字符。
1.这个fflush是必要的,因为我们正在从写切换到读,正如Jonathan Leffler的回答中所述,它还消耗了一些(但不是全部)I/O错误的通知,因此您必须检查它是否失败。
1.因为您正在写入文件,所以还必须检查 * delayed * I/O错误,这些错误仅在fclose上报告(这是操作系统中的一个设计错误,但我们永远无法摆脱)。
1.最佳实践是在命令行上将文件名传递给munge,而不是将其硬编码到程序中。

x6h2sr28

x6h2sr284#

@Jonathan Leffler很好地说明了代码使用多个fseek()的原因:以科普阅读和写作之间的变化。
int size=ftell(fp);较弱,因为从ftell()返回的值的范围是long
在文本文件中查找(如OP)也会有未定义行为(UB)的风险。
对于一个文本流,偏移量应该为零,或者偏移量应该是先前成功调用同一文件相关流上的ftell函数所返回的值,因此应该是SEEK_SET. C17 dr §7.21.9.13.
最好使用@zwol之类的方法,做一个小的改动。
不要假设一个平滑的线性Map。相反,注意位置,然后根据需要返回到它。

int replacement = '5';
for (;;) {
    long position = ftell(fp);
    if (ftell == -1) {
        perror(filename);
        exit(1);
    }
    int letter = fgetc(fp);
    if (letter == EOF) {
        break;
    }
    if (letter == 'a' || letter == 'e' || letter == 'i') {
        fseek(fp, position, SEEK_SET);
        fputc(replacement, fp);
        if (fflush(fp)) {
            perror(filename);
            exit(1);
        }
    }
}

研究fgetpos(), fsetpos(),寻找一个更好的解决方案,可以处理所有大小的文件,甚至比LONG_MAX更长的文件。

相关问题