Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

C++でファイル読込み パターン別まとめ

スポンサードリンク



C++でのファイルの読み込み方法のまとめです。
よく使われるCでもできる方法以外の
標準C++ライブラリを使ったファイル読込み方法です。
Visual C++ 2013とg++で確認しました
4パターン用意しました。

1. char *型に一行ずつ読込む
2. string型に一行ずつ読込む
3. char *型に全部読込む
4. string型に全部読込む

テスト用ファイル(test.txt)

123456
89

※このファイルは1行目は改行ありで、2行目は改行なしです
 (ただし、Vimなどのエディタでは改行なしの最終行を作ることはできないことに注意)。

1. char*型に一行ずつ読込む

ソースコード

#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream ifs("test.txt");
    char str[256];
    if (ifs.fail())
    {
        std::cerr << "失敗" << std::endl;
        return -1;
    }
    while (ifs.getline(str, 256 - 1))
    {
        std::cout << "[" << str << "]" << std::endl;
    }
    return 0;
}

実行結果

[123456]
[89]

2. string型に一行ずつ読込む

ソースコード

#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream ifs("test.txt");
    std::string str;
    if (ifs.fail())
    {
        std::cerr << "失敗" << std::endl;
        return -1;
    }
    while (getline(ifs, str))
    {
        std::cout << "[" << str << "]" << std::endl;
    }
    return 0;
}

※こちらはメンバ関数ではありませんので注意

実行結果

[123456]
[89]

2015/03/02 追記
2のパターンでは下記リンクのようにもかけます。
file io - C++: Using ifstream with getline(); - Stack Overflow

3. char*型に全部読込む

ソースコード

#include <fstream>
#include <iostream>
#include <string>
#include <cstring>

int main()
{
    std::ifstream ifs("test.txt");
    if (ifs.fail())
    {
        std::cerr << "失敗" << std::endl;
        return -1;
    }
    int begin = static_cast<int>(ifs.tellg());
    ifs.seekg(0, ifs.end);
    // 一応範囲チェックすべきだけど……
    int end = static_cast<int>(ifs.tellg());
    int size = end - begin;
    ifs.clear();  // ここでclearしてEOFフラグを消す
    ifs.seekg(0, ifs.beg);
    char *str = new char[size + 1];
    str[size] = '\0';  // 念のため末尾をNULL文字に
    ifs.read(str, size);
    std::cout << "[" << str << "]" << std::endl;
    std::cout << "size: " << size << "    strlen: " << std::strlen(str) << std::endl;
    delete[] str;
    return 0;
}

2015/02/20 訂正 null文字を考慮して読込み先の配列のサイズは+1にしました。また、seekgで巻き戻す前にclear()を追加しました。これがないとコケることがあるらしいです。

実行結果

[123456
89]
size: 10    strlen: 9

補足

上記で使用しているreadは
istream& read (char* s, streamsize n);
という定義です。

4. string型に全部読込む(ダメな方法)

ソースコード

#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream ifs("test.txt");
    if (ifs.fail())
    {
        std::cerr << "失敗" << std::endl;
        return -1;
    }
    int begin = static_cast<int>(ifs.tellg());
    ifs.seekg(0, ifs.end);
    int end = static_cast<int>(ifs.tellg());
    ifs.clear();
    ifs.seekg(0, ifs.beg);
    int size = end - begin;
    //std::string str(size, '\0');  // こういう小細工をしてもダメ
    std::string str;
    ifs.read(&str[0], size);        // 無理やりchar *にする…
    std::cout << "[" << str << "]" << std::endl;
    std::cout << "size: " << size << "    strlen: " << str.length() << std::endl;
    return 0;
}

実行結果

[]
size: 10    strlen: 0

※環境によって実行結果が異なることもあるかもしれません。
istream& read (char* s, streamsize n);はstringに対応していないのです。
じゃあ、ファイル一気読みしてstringに入れられないの?と思いましたが、方法はあります。

4. string型に全部読込む

ソースコード

#include <fstream>
#include <iostream>
#include <string>
#include <iterator>

int main()
{
    std::ifstream ifs("test.txt");
    if (ifs.fail())
    {
        std::cerr << "失敗" << std::endl;
        return -1;
    }
    std::string str((std::istreambuf_iterator<char>(ifs)),
        std::istreambuf_iterator<char>());
    std::cout << "[" << str << "]" << std::endl;
    return 0;
}

こちらでも同じです。

#include <fstream>
#include <iostream>
#include <string>
#include <iterator>

int main()
{
    std::ifstream ifs("test.txt");
    if (ifs.fail())
    {
        std::cerr << "失敗" << std::endl;
        return -1;
    }
    // ほとんどさっきと同じですが、こうもかけます。こっちのほうがわかりやすい
    std::istreambuf_iterator<char> it(ifs);
    std::istreambuf_iterator<char> last;
    std::string str(it, last);
    std::cout << "[" << str << "]" << std::endl;
    return 0;
}

実行結果

[123456
89]

さらに、g++(4.8.3)では今回の場合、明示的に

#include <cstdlib>

を書かないとEXIT_FAULUREマクロも使えないんですね。

この方法で使っているstreambuf_iteratorが
どんなものか知りたい場合は下記を参照してみてください。

もちろん、上記で紹介した以外にも標準Cランタイムライブラリを使う方法もあります。
fgets()ですね。事実上現在これが読込みが最速なのですが……。

その他

ここでのサンプルはRAIIという技法を使っています。
なので明示的にファイルのクローズは必要ありません。
デストラクタでクローズします。

RAIIとは

RAII(Resource Acquisition Is Initialization、日本語では「リソースの確保は初期化時に」、「リソースの取得と初期化」など)は、資源(リソース)の確保と解放を変数の初期化と破棄処理に結び付けるというプログラミングのテクニックで、特にC++とDで一般的である。

RAIIでは資源の取得を変数の構築(初期化)時、返却を破壊時に行う。自動変数がスコープを離れるときデストラクタが呼ばれるため、変数の寿命が終わるとすぐに資源が返却されることが保障できるようになった。これは例外が起こったときでも同様であるため、RAIIは例外安全なコードを書くための鍵となる概念となった (Sutter 1999)。

http://ja.wikipedia.org/wiki/RAII

テキストモードとバイナリモードについて

今回はテキストモードのみ使用しています。ifstreamのコンストラクタの第2引数にstd::ios::binaryを指定してあげるとバイナリモードになります。
ちなみにstd::ios_base::binaryとかほかにもいろいろあるんだけど、
どれを使うのが標準的なのか僕は知りません。std::iosが無難なのかな?
テキストモードとバイナリモードの違いですが、まぁ、グーグル先生が割と的確な答えをいってくれるのですが、一言でいうと、改行の扱いを環境(≒OS)によってうまいことやってくれるってやつです。Windowsだと改行はCR+LFで、Linux系(今のMac含む)はLFで違いますよね。
余計なことすんな!なときはstd::ios::binaryを指定してあげましょう。