web-dev-qa-db-ja.com

cのファイルの既存の内容を変更する

int main()
{
    FILE *ft;
    char ch;
    ft=fopen("abc.txt","r+");
    if(ft==NULL)
    {
        printf("can not open target file\n");
        exit(1);
    }
    while(1)
    {
        ch=fgetc(ft);
        if(ch==EOF)
        {
            printf("done");
            break;
        }
        if(ch=='i')
        {
            fputc('a',ft);
        }
    }
    fclose(ft);
    return 0;
}

iaに置き換えられるようにabc.txtを編集したいことがわかります。
プログラムは正常に動作しますが、外部でabc.txtを開くと、編集されていないように見えました。
その理由として考えられるものはありますか?

この場合、回答が示すように、iの後の文字がaに置き換えられないのはなぜですか?

6
zee

分析

複数の問題があります:

  1. fgetc()は、intではなくcharを返します。すべての有効なchar値に加えて個別の値EOFを返す必要があります。書かれているように、EOFを確実に検出することはできません。 charが符号なし型の場合、EOFは見つかりません。 charが符号付きタイプの場合、有効な文字(多くの場合、ÿ、y-umlaut、U + 00FF、分音付きのラテン文字Y)をEOFと誤認します。

  2. 更新モードで開いたファイルの入力と出力を切り替える場合は、読み取りの間にファイル配置操作(fseek()rewind()、名目上fsetpos())を使用する必要があります。と書く;また、書き込みと読み取りの間に位置決め操作またはfflush()を使用する必要があります。

  3. 開いたものを閉じることをお勧めします(現在はコードで修正されています)。

  4. 書き込みが機能した場合は、iの後の文字をaで上書きします。

合成

これらの変更により、次のことが起こります。

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

int main(void)
{
    FILE *ft;
    char const *name = "abc.txt";
    int ch;
    ft = fopen(name, "r+");
    if (ft == NULL)
    {
        fprintf(stderr, "cannot open target file %s\n", name);
        exit(1);
    }
    while ((ch = fgetc(ft)) != EOF)
    {
        if (ch == 'i')
        {
            fseek(ft, -1, SEEK_CUR);
            fputc('a',ft);
            fseek(ft, 0, SEEK_CUR);
        }
    }
    fclose(ft);
    return 0;
}
_

さらにエラーチェックの余地があります。

釈義

入力とそれに続く出力にはシークが必要です

fseek(ft, 0, SEEK_CUR);ステートメントは、C標準で必要です。

ISO/IEC 9899:2011§7.21.5.3fopen関数

¶7ファイルが更新モード(上記のモード引数値のリストの2番目または3番目の文字として「+」)で開かれると、入力と出力の両方が関連するストリームで実行される場合があります。 ただし、fflush関数またはファイル配置関数(fseekfsetpos、またはrewind)への呼び出しが介在しない限り、出力の直後に入力が続くことはありません。入力操作でファイルの終わりが検出されない限り、ファイル配置関数への介在呼び出し。更新モードでテキストファイルを開く(または作成する)と、一部の実装では代わりにバイナリストリームを開く(または作成する)場合があります。

(強調が追加されました。)

fgetc()intを返します

現在のC標準であるISO/IEC 9899:2011からの引用。

§7.21入力/出力_<stdio.h>_

§7.21.1はじめに

EOFは、int型と負の値を持つ整数定数式に展開され、ファイルの終わり、つまりストリームからの入力がなくなったことを示すためにいくつかの関数によって返されます。

§7.21.7.1fgetc関数

int fgetc(FILE *stream);

¶2streamが指す入力ストリームのファイルの終わりインジケーターが設定されておらず、次の文字が存在する場合、fgetc関数はその文字を_unsigned char_としてintに変換して取得し、関連するファイルを進めますストリームの位置インジケーター(定義されている場合)。

返品

¶3ストリームのファイルの終わりインジケーターが設定されている場合、またはストリームがファイルの終わりにある場合、ストリームのファイルの終わりインジケーターが設定され、fgetc関数はEOFを返します。それ以外の場合、fgetc関数は、streamが指す入力ストリームから次の文字を返します。読み取りエラーが発生すると、ストリームのエラーインジケーターが設定され、fgetc関数がEOFを返します。289)

289) ファイルの終わりと読み取りエラーは、feof関数とferror関数を使用して区別できます。

したがって、EOFは負の整数です(通常は-1ですが、標準ではそれは必要ありません)。 fgetc()関数は、EOFまたは文字の値を_unsigned char_(0..UCHAR_MAXの範囲、通常は0..255)として返します。

§6.2.5タイプ

¶3タイプcharとして宣言されたオブジェクトは、基本的な実行文字セットのメンバーを格納するのに十分な大きさです。基本実行文字セットのメンバーがcharオブジェクトに格納されている場合、その値は負でないことが保証されます。他の文字がcharオブジェクトに格納されている場合、結果の値は実装によって定義されますが、そのタイプで表現できる値の範囲内でなければなりません。

¶5タイプ_signed char_として宣言されたオブジェクトは、「プレーン」なcharオブジェクトと同じ量のストレージを占有します。

§6符号付き整数型ごとに、同じ量のストレージ(符号情報を含む)を使用し、同じ配置要件を持つ、対応する(ただし異なる)符号なし整数型(キーワードunsignedで指定)があります。

§153つのタイプchar、_signed char_、および_unsigned char_は、まとめて文字タイプと呼ばれます。実装は、_signed char_または_unsigned char_と同じ範囲、表現、および動作を持つようにcharを定義する必要があります。45)

45) _CHAR_MIN_で定義された_<limits.h>_は、値_0_または_SCHAR_MIN_のいずれかを持ち、これを使用して2つのオプションを区別できます。選択に関係なく、charは他の2つのタイプとは別のタイプであり、どちらとも互換性がありません。

これは、プレーンなcharが符号付きまたは符号なしの型である可能性があるという私の主張を正当化します。

今考えてみましょう:

_char c = fgetc(fp);
if (c == EOF)
   …
_

fgetc()がEOFを返し、プレーンcharが符号なし(8ビット)タイプであり、EOF is _-1_であるとします。割り当てにより、値0xFFがcに配置されます。 、これは正の整数です。比較が行われると、cint(したがって、値255)にプロモートされ、255は負ではないため、比較は失敗します。

逆に、プレーンcharが符号付き(8ビット)タイプであり、文字セットがISO8859-15であると仮定します。 fgetc()がÿを返す場合、割り当てられる値はビットパターン0b11111111になります。これは、_-1_と同じであるため、比較では、cは_-1_に変換され、比較されます。 _c == EOF_は、有効な文字が読み取られた場合でもtrueを返します。

詳細を微調整することはできますが、基本的な引数はsizeof(char) < sizeof(int)の間有効です。それが当てはまらないDSPチップがあります。ルールを再考する必要があります。それでも、基本的なポイントは残ります。 fgetc()は、intではなくcharを返します。

データが本当にASCII(7ビットデータ)の場合、すべての文字は0..127の範囲にあり、ÿ問題の誤解に遭遇することはありません。ただし、 charタイプは署名されていませんが、「EOFを検出できません」という問題がまだ残っているため、プログラムは長時間実行されます。移植性を考慮する必要がある場合は、これを考慮に入れます。これらは、必要なプロフェッショナルグレードの問題です。 Cプログラマーとして扱います。これらすべてのニュアンスを考慮せずに、比較的簡単にデータのシステムで動作するプログラムにたどり着くことができます。ただし、プログラムは他の人のシステムでは動作しません。

18

_abc.txt_の「i」を変更するのではなく、「i」の次の文字を変更します。 fseek(ft, -1, SEEK_CUR);の前にfputc('a', ft);を配置してみてください。

'i'文字を読み取った後、ftのファイル位置インジケータはこの 'i'の後の文字になり、fputc()で文字を書き込むと、この文字は書き込まれます。現在のファイル位置、つまり「i」の後の文字。詳細については、 fseek(3) を参照してください。

1
Lee Duhem

'i'を読んだ後、正しい場所に書き込むために「ステップバック」する必要があります。

if(ch=='i')
{
    fseek(ft, -1, SEEK_CUR);
    fputc('a',ft);
}
0
OregonTrail