モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

工作HardwareHub ロゴ画像 (Laptop端末利用時)
工作HardwareHub ロゴ画像 (Mobile端末利用時)

ファイルストリーム (C++をもう一度)

モーダルを閉じる

ステッカーを選択してください

モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2014/12/13
最終更新最終更新
2021/10/07
記事区分記事区分
一般公開

目次

    低レイヤーのプログラミングとOS開発が趣味。C言語を使っています。

    0
    ステッカーを贈るとは?

    テキストモード

    読み込み

    input.txt

    1 10 100
    2 20 200
    

    main.cpp

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <cstdlib> // EXIT_FAILURE のため
    using namespace std;
    
    int main() {
        fstream fs;
    
        fs.open("input.txt", ios::in);
        if(! fs.is_open()) {
            // exit だけでなく return の戻り値としても使用できる。
            return EXIT_FAILURE;
        }
    
        // 文字列として一行読み込む
        string str;
        getline(fs, str); //← cin  ではなく今回は fs
        cout << str << endl; //=> 1 10 100
    
        // cin のように一行読み込む
        int l,m,n;
        fs >> l >> m >> n;
        cout << "l: " << l << ", "
                "m: " << m << ", "
                "n: " << n << endl; //=> l: 2, m: 20, n: 200
    
        fs.close(); // デストラクタでも閉じてくれますが、明示的に閉じる習慣を。
    
        return 0;
    }
    

    書き込み

    main.cpp (UTF-8)

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <cstdlib> // EXIT_FAILURE のため
    using namespace std;
    
    int main() {
        fstream fs;
    
        fs.open("output.txt", ios::out);
        if(! fs.is_open()) {
            return EXIT_FAILURE;
        }
    
        // シンプルに書き出す
        fs << "改行含める" << endl; // 改行。そして書き出す
        fs << "改行含めない" << flush; // 書き出すだけ
    
        // flush() メソッドで書き出す
        fs << "改行含めない";
        fs.flush(); // cout.flush() というのも実はあり、同様の効果です
    
        // close() で暗黙的に書き出す (閉じるときにバッファをすべて書き出してくれる)
        fs << "改行含めない";
        fs.close();
    
        return 0;
    }
    

    output.txt (UTF-8)

    改行含める
    改行含めない改行含めない改行含めない  ← 改行なしでEOF
    

    フラグ

    上記サンプルコード内の

    • 入力: ios::in
    • 出力: ios::out

    という基本のフラグにオプションとなるフラグを追加することで、入出力に関する振舞を変更できます。ちなみに ios::out ではファイルが存在していなくても is_open() は true になりますが ios::in では false になります。

    • 追記(append): ios::app
    • ファイルが存在していれば破棄(truncate): ios::trunc
    • ファイルポインタを末尾(at e(nd))に移動: ios::ate
    • バイナリモードで開く: ios::binary

    組み合わせの例をいくつか示します。

    ios::in | ios::out

    読み込みと書き込みができる。既存のファイル内容は書き込んだ時点で破棄される。

    ios::in | ios::out | ios::app

    読み込みと書き込みができる。書き込んだ内容は既存のファイルがあれば追記される。なければ新規作成される。

    ios::in | ios::app

    エラーです。読み込みのみで追記というオプションは意味をなしません。

    ios::out | ios::app

    書き込みができる。書き込んだ内容は既存のファイルがあれば追記される。なければ新規作成される。

    ios::in | ios::out | ios::trunc

    読み込みと書き込みができる。既存のファイル内容は開いた時点で破棄される。

    ios::in | ios::trunc

    エラーです。何も読み込めないファイルストリームを作成しても無意味です。

    ios::out | ios::trunc

    書き込みができる。既存のファイル内容は開いた時点で破棄される (開いた時点で破棄せずとも out すれば勝手に上書きされるため ios::out だけの場合と実質的には同じです。冗長な表現です)

    バイナリモード

    テキストモードの場合、「fs << "\n" << flush」が例えば Windows だと "\r\n" になりました。読み込みの場合にも "\r\n" は改行として扱われました。一方、バイナリモードの場合、「fs << "\n" << flush」はOSによらず "\n" のまま出力されます。読み込みについても OS によらず "\n" として読み込まれます。

    実は、バイナリモードとテキストモードの違いはこの「改行コードの扱い」だけです。ios::binary を忘れに注意してください。改行コードの点でしか違いがないため不具合が発生する箇所が限定的であり気づきにくいです。

    書き込み

    #include <iostream>
    #include <fstream>
    #include <cstdlib>
    using namespace std;
    
    int main() {
        fstream fs;
    
        fs.open("output.bin", ios::out | ios::binary);
        if(! fs.is_open()) {
            return EXIT_FAILURE;
        }
    
        int n = 0x41424344; // 16進数 (4バイト (4ビット: 2進数1111 == 16進数F なので、32ビットで4バイト))
        // int n = 010120441504; // 8進数
        // int n = 1094861636; // 10進数
        // int n = 'A' * (16*16*16*16*16*16) + 'B' * (16*16*16*16) + 'C' * (16*16) + 'D';
        // ↑0x41, 0x42, 0x43, 0x44 は 'A','B','C','D' のアスキーコード (1バイト x 4)
    
        // int型 sizeof n バイト (通常4バイト) の領域の先頭
        // アドレス &n を write の引数 const char* にキャスト
        fs.write((const char*)&n, sizeof n);
    
        //=> cat output.bin (↓endian: バイトオーダの種類を表す言葉
        //     バイトオーダはCPUに依存して決定されることがほとんどです)
        // - リトルエンディアン "DCBA"
        //   (反転します)
        //   - オフセット(アドレス先頭&nからのバイト差) 0: 0x44
        //   - オフセット 1: 0x43
        //   - オフセット 2: 0x42
        //   - オフセット 3: 0x41
        // - ビッグエンディアン "ABCD"
        //   - オフセット 0: 0x41
        //   - オフセット 1: 0x42
        //   - オフセット 2: 0x43
        //   - オフセット 3: 0x44
        // ↑ファイルとメモリは本質的に同じ。binaryモードはメモリ内容をダンプしている
        //   イメージです。メモリ上でもバイトオーダは等しくなります。
    
        fs.close();
        return 0;
    }
    

    読み込み

    input.bin

    DCBA  ← 改行なしでEOF
    

    main.cpp

    #include <iostream>
    #include <fstream>
    #include <cstdlib>
    using namespace std;
    
    int main() {
        fstream fs;
    
        fs.open("input.bin", ios::in | ios::binary);
        if(! fs.is_open()) {
            return EXIT_FAILURE;
        }
    
        int n;
        fs.read((char*)&n, sizeof n); // 0x41424344
        cout << n << endl; //=> 1094861636 (sizeof n == 4 かつリトルエンディアンの場合)
    
        fs.close();
        return 0;
    }
    

    連続的な読み込みとエラー処理

    #include <iostream>
    #include <fstream>
    #include <cstdlib>
    #include <cstdio> // printf, remove を使用するため
    using namespace std;
    
    const char SRC_FILE[] = "input.bin";
    const char DST_FILE[] = "output.bin";
    const int BUF_SIZE = 16; // バイト
    
    int main() {
        fstream src, dst;
    
        // ファイルを開く
        src.open(SRC_FILE, ios::in | ios::binary);
        if(! src.is_open()) {
            return EXIT_FAILURE;
        }
    
        dst.open(DST_FILE, ios::out | ios::binary);
        if(! dst.is_open()) {
            src.close();
            return EXIT_FAILURE;
        }
    
        bool error = false;
        do {
            char buf[BUF_SIZE];
    
            // 読み込み
            src.read(buf, sizeof buf); // BUF_SIZE バイトずつ
            if(src.fail() && ! src.eof()) { // ! src && ! src.eof としても同じです
                error = true;
                break;
            }
    
            // 内容を16進ダンプ
            for (int i = 0, size = src.gcount(); i < size; ++i) { // get count
                printf("%02X ", (unsigned char)buf[i]); // 2桁の16進数大文字表示 (小文字表示は %x)
                // ↑char型のままでは負の値として扱われる可能性がある
            }
            cout << endl;
    
            // 書き込み
            dst.write(buf, src.gcount());
            if(dst.fail()) { // ! dst としても同じです
                error = true;
                break;
            }
    
        } while (! src.eof()); // eof: ファイル終端にファイルポインタがあってもfalseです。
        // 終端を越えようとしていればtrueです。そのため、input.bin が空であっても最初はfalseです
        // よって、do-while であっても while であっても表示結果は同じで最低1ループは実行されます。
        // ここでは現実をそのまま直感的に表現したdo-whileを採用しました。
    
        src.close();
        dst.close();
    
        if (error) {
            remove(DST_FILE); // ファイル削除 (cstdioより)
            return EXIT_FAILURE;
        }
    
        return 0;
    }
    

    ファイルポインタの移動

    #include <iostream>
    #include <fstream>
    #include <cstdlib>
    using namespace std;
    
    int main() {
        fstream fs;
    
        fs.open("input.bin", ios::in | ios::binary);
        if(! fs.is_open()) {
            return EXIT_FAILURE;
        }
    
        fs.clear(); // fs.fail() が true の状態が発生するとファイル操作できなくなります。これでは
                    // ファイル終端に到達した場合などに不都合であるため seekg() などの直前では毎回
                    // clear() でロック解除するとよいです。今回の例では open 直後なのでなくてもよいですが。
    
        // 絶対的な移動
        fs.seekg(0); // 'g': get 読み込み (ファイル先頭 0バイト目に移動)
        fs.seekp(0); // 'p': put 書き込み (実際には seekg と seekp は同じ関数)
    
        // 相対的な移動
        // - 現在の位置(current): ios::cur
        // - ファイル先頭(beginning): ios::beg
        // - ファイル末尾(end) + 1バイト目: ios::end
        //  (何も読み込むデータがない位置。追記時に書き込む位置)
        fs.seekg(-10, ios::end);
    
        // 現在の位置の取得
        streampos pos = fs.tellg(); // tellp() としても同じです。seekg と seekp の関係と同じです。
    
        // 注意点
        // streampos 型は int 型であるとは限らないため
        // fs.seekg(pos + 10); としてははならず
        fs.seekg(pos);
        fs.seekg(10, ios::cur); // としてください。
        // 環境によってはどちらもコンパイルできてしまうので厄介です。
    
        return 0;
    }
    
    0
    詳細設定を開く/閉じる
    アカウント プロフィール画像 (本文下)

    低レイヤーのプログラミングとOS開発が趣味。C言語を使っています。

    記事の執筆者にステッカーを贈る

    有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

    さらに詳しく →
    ステッカーを贈る コンセプト画像

    Feedbacks

    Feedbacks コンセプト画像

      ログインするとコメントを投稿できます。

      関連記事