閉じる

C言語の1文字入出力を基礎から解説 (fgetc, fputc)

ファイルを1文字ずつ扱えるようになると、テキストのフィルタ処理や簡単なコピー、カウント、置換など幅広い用途に応用できます。

本記事ではC言語のfgetcfputcに絞り、EOFの正しい扱いや戻り値の型、基本ループ、最低限のエラー処理まで、初心者でも安全に使える形で丁寧に解説します。

1文字入出力の基本

fgetcとfputcの役割

関数の概要

fgetcはファイルから1文字を読み取り、fputcはファイルへ1文字を書き込みます。

いずれも標準ヘッダstdio.hに定義されています。

戻り値はいずれもint型である点が重要です。

以下に要点をまとめます。

関数ヘッダプロトタイプ正常時の戻り値エラー時
fgetc#include <stdio.h>int fgetc(FILE *stream);読んだ文字をunsigned charとしてintに拡張した値EOF
fputc#include <stdio.h>int fputc(int c, FILE *stream);書き込んだ文字と同じunsigned char値をintで返すEOF
注意

EOFは多くの処理系で-1ですが、規格上は「負のintの定数」であり、文字そのものではありません

サンプルの最小形

読み取りと書き込みの最小例は次の通りです。

C言語
#include <stdio.h>

void read_one(FILE *fp) {
    // 1文字読む。戻り値はint
    int ch = fgetc(fp);
    if (ch != EOF) {
        // 読めたので標準出力へ出す
        fputc(ch, stdout);
    }
}

void write_A(FILE *fp) {
    // 'A'を書き込む。戻り値もint
    if (fputc('A', fp) == EOF) {
        // 書き込みエラー
        perror("fputc");
    }
}

ファイルを開くと閉じる

fopenとfcloseの基本

入出力にはFILE*が必要です。

ファイルはfopenで開き、最後にfcloseで閉じます。

テキスト処理なら"r""w"、内容を厳密にコピーするなら"rb""wb"を使います。

C言語
#include <stdio.h>

int main(void) {
    FILE *in  = fopen("input.txt", "r");   // テキスト読み取り
    FILE *out = fopen("output.txt", "w");  // テキスト書き込み

    if (!in || !out) {
        perror("fopen");
        if (in)  fclose(in);
        if (out) fclose(out);
        return 1;
    }

    // ... ここでfgetc/fputcを使った処理を行う ...

    fclose(in);
    fclose(out);
    return 0;
}
補足

Windowsで改行の変換を避けたい場合やバイナリを扱う場合は"rb""wb"を使います。

用途の例

典型的な使いどころ

1文字入出力は、次のような小さな「文字フィルタ」を作る際に向いています。

例えば、ファイルのコピー行数や文字数のカウント、特定文字の置換、制御文字の可視化などです。

解析を段階的に行う教材としても適しており、ファイルI/Oの基礎理解に役立ちます。

fgetcの使い方とEOF

戻り値はintとEOF

なぜintで受けるのか

fgetcの戻り値はintです。

これは「すべてのunsigned char値」と「EOF」を区別するためです。

もしcharで受けると、負の値を取りうる処理系ではEOFと混同する危険があります。

C言語
int ch = fgetc(fp);               // 正しい
if (ch == EOF) {
    // ファイル終端かエラー
}
ポイント

実際の文字として使うときは(unsigned char)ch(char)chに明示的に変換します。

読み取りループの基本形

教科書的なパターン

最も安全で簡潔なパターンは次の形です。

C言語
#include <stdio.h>

void echo(FILE *in, FILE *out) {
    int ch;
    while ((ch = fgetc(in)) != EOF) {
        // chはint。文字として使うならキャストする
        unsigned char c = (unsigned char)ch;
        if (fputc(c, out) == EOF) {
            perror("fputc");
            break;
        }
    }
    // ループを抜けたら終端かエラーかを判定可能
    if (ferror(in)) {
        perror("fgetc");
    }
}

この形式なら、EOFと実際の文字値が確実に区別されます。

改行や空白も1文字として読む

空白は「データ」です

fgetcはスペースやタブ、改行'\n'も1文字として返します。

したがって「単語単位」ではなく「文字単位」で確実に処理したい場合に向いています。

次の例は、行数を数えます。

C言語
#include <stdio.h>

long count_lines(FILE *fp) {
    long lines = 0;
    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        if (ch == '\n') {
            lines++;
        }
    }
    if (ferror(fp)) {
        perror("fgetc");
        return -1;
    }
    return lines;
}

読み取り失敗の簡単なチェック

feofとferrorの併用

ループ終了時にfeofが真なら終端、ferrorが真ならエラーです。

初心者のうちは終端とエラーを分けてメッセージを出すだけでも十分役立ちます。

C言語
#include <stdio.h>

void read_all(FILE *fp) {
    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        // 読んだ文字をどこかに使う
        (void)ch;
    }
    if (ferror(fp)) {
        perror("read_all: fgetc");
    } else if (feof(fp)) {
        // 正常に終端まで到達
    }
}

fputcの使い方

基本形と戻り値

書けたかどうかの判定

fputcは、正常時に書き込んだ文字をintで返し、失敗するとEOFを返します。

戻り値のチェックが最低限のエラー検出になります。

C言語
#include <stdio.h>

int put_char(FILE *fp, int ch) {
    if (fputc(ch, fp) == EOF) {
        perror("fputc");
        return -1;
    }
    return 0;
}

1文字を書き込む

単純な出力

整数intで渡した値の下位8ビットが文字として書き込まれます。

C言語
#include <stdio.h>

void write_sample(FILE *fp) {
    fputc('H', fp);
    fputc('i', fp);
    fputc('!', fp);
}

改行を書き込む

改行コードの注意

テキストモード("w")では'\n'を書けば、OSに応じた適切な改行に変換されます。

バイナリの厳密コピーが必要なら"wb"を使い、改行の変換を避けてください。

C言語
#include <stdio.h>

void write_lines(FILE *fp) {
    fputc('A', fp);
    fputc('\n', fp);  // テキストモードなら環境に合わせて変換
}

サンプルコードと注意点

1文字ずつコピーする

実用的な最小プログラム

次は、入力ファイルから出力ファイルへ1文字ずつ安全にコピーする完全なサンプルです。

バイナリでも正しく動くよう"rb""wb"を使用します。

C言語
#include <stdio.h>
#include <stdlib.h>

// 使い方: ./copy_char input.txt output.txt
int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <src> <dst>\n", argv[0]);
        return 2; // 使い方エラー
    }

    const char *src_path = argv[1];
    const char *dst_path = argv[2];

    // バイナリモードで開くことで、改行などの変換を避ける
    FILE *in  = fopen(src_path, "rb");
    if (!in) {
        perror("fopen(src)");
        return 1;
    }
    FILE *out = fopen(dst_path, "wb");
    if (!out) {
        perror("fopen(dst)");
        fclose(in);
        return 1;
    }

    long long count = 0; // コピーしたバイト(文字)数
    int ch;

    // 典型的なfgetcループ
    while ((ch = fgetc(in)) != EOF) {
        if (fputc(ch, out) == EOF) {
            perror("fputc");
            fclose(in);
            fclose(out);
            return 1;
        }
        count++;
    }

    // 入力側でエラーがあったか確認
    if (ferror(in)) {
        perror("fgetc");
        fclose(in);
        fclose(out);
        return 1;
    }

    // 後片付け
    if (fclose(in) == EOF) {
        perror("fclose(in)");
        // 続行してoutも閉じる
    }
    if (fclose(out) == EOF) {
        perror("fclose(out)");
        return 1;
    }

    // 結果表示
    printf("Copied %lld bytes from %s to %s\n",
           count, src_path, dst_path);
    return 0;
}

想定するサンプルの動作例を示します。

例えばinput.txtが次の内容だとします。

実行結果
Hello
C

上のプログラムを実行した結果は次のとおりです。

実行結果
$ ./copy_char input.txt output.txt
Copied 7 bytes from input.txt to output.txt

エラー時の最低限の対応

fopenと入出力エラーの扱い

初心者向けには、関数の戻り値を必ず確認することが最重要です。

fopenNULLならperrorで理由を表示し、fgetc/fputcEOFを返したらferrorでエラーかどうかを追加確認します。

上のサンプルは最小限のパターンを含んでいます。

補足

発展的にはclearerrでエラー状態をクリアしたり、errnoを使って詳細な原因分岐を行うこともできます。

EOFは文字ではない

よくある誤解の回避

EOFは文字ではありません

「ファイル終端」または「エラー」を示す特別な負のintです。

次のようにcharで受けて比較すると動きが不定になります。

C言語
// よくある間違い(ダメな例)
char ch;
while ((ch = fgetc(fp)) != EOF) { // chはEOFを表現できない可能性あり
    /* ... */
}

正しくはintで受け、必要に応じて文字型へ変換します。

C言語
// 正しいパターン
int ch;
while ((ch = fgetc(fp)) != EOF) {
    unsigned char c = (unsigned char)ch; // ここで実際の文字として扱う
    /* ... */
}

文字はintで受け取る

ctypeとの連携も安全に

fgetcの戻り値は常にintで受けるのが基本です。

標準ライブラリのctype.h系関数isalphaisspaceなどに渡すときは、(unsigned char)にキャストしてから渡すのが安全です。

これは負のcharを未定義動作にしないためです。

C言語
#include <stdio.h>
#include <ctype.h>

int count_spaces(FILE *fp) {
    int ch, spaces = 0;
    while ((ch = fgetc(fp)) != EOF) {
        if (isspace((unsigned char)ch)) { // unsigned charにする
            spaces++;
        }
    }
    if (ferror(fp)) {
        perror("fgetc");
        return -1;
    }
    return spaces;
}

まとめの要点として、fgetcintで受けてEOFと比較、文字として使う際にキャスト、fputcは戻り値をEOFと比較してエラー検出、という流れを覚えると安全です。

まとめ

1文字入出力はシンプルですが、戻り値の型とEOFの意味を正しく理解することが肝心です。

fgetcintで受けてEOFと比較し、必要なときだけ文字型へ変換します。

fputcは戻り値のEOFでエラー確認を行います。

ファイルはfopenで開いたらfcloseで必ず閉じ、可能ならfeofferrorで終端かエラーかを区別してください。

まずは本記事のコピーサンプルを動かし、次に文字カウントや置換など小さなフィルタを作ってみると、文字単位のI/Oに自信がつきます。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!