C++ では、ファイル入出力のためのストリーム<fstream> ヘッダで提供されている。

主に使うクラスは次の3つである。

  • std::ifstream ファイル入力用
  • std::ofstream ファイル出力用
  • std::fstream 入出力の両方に使える

出力

#include <fstream>
#include <iostream>
 
int main()
{
    std::ofstream outf { "Sample.txt" };
 
    if (!outf)
    {
        std::cerr << "Sample.txt を開けなかった\n";
        return 1;
    }
 
    outf << "This is line 1\n";
    outf << "This is line 2\n";
}

std::ofstream は、デフォルトでは書き込み用にファイルを開く。

入力

#include <fstream>
#include <iostream>
#include <string>
 
int main()
{
    std::ifstream inf { "Sample.txt" };
 
    if (!inf)
    {
        std::cerr << "Sample.txt を開けなかった\n";
        return 1;
    }
 
    std::string word {};
    while (inf >> word)
        std::cout << word << '\n';
}

>> は空白文字で区切って読むため、単語ごとに読み込まれる。

1行ずつ読む

1行全体を読みたいときは、getline(C++) を使う。

#include <fstream>
#include <iostream>
#include <string>
 
int main()
{
    std::ifstream inf { "Sample.txt" };
 
    std::string line {};
    while (std::getline(inf, line))
        std::cout << line << '\n';
}

close と flush

ファイルは、close() を明示的に呼んで閉じることもできる。 ただし、多くの場合はスコープを抜けるとデストラクタによって自動で閉じられる。

出力はバッファされることがあるため、すぐに書き込みを反映したいときは flush()std::flush を使える。 また、std::endl は改行に加えてフラッシュも行う。

ファイルモード

ファイルを開くときには、モードを指定できる。

  • std::ios::in 読み込み
  • std::ios::out 書き込み
  • std::ios::app 末尾への追加
  • std::ios::binary バイナリモード
  • std::ios::trunc 既存内容を消して開く

たとえば追記したい場合は次のように書く。

std::ofstream outf { "Sample.txt", std::ios::app };

open

コンストラクタで開かずに、あとから open() で開くこともできる。

std::ofstream outf {};
outf.open("Sample.txt");

ランダムアクセス

ファイルストリームでは、ファイル位置を動かして任意の場所を読むこともできる。

ファイルストリームは、現在どこを読んでいるか、どこへ書いているかを表す位置情報を持つ。 これを動かすことで、特定の場所へ飛んで読み書きできる。

  • seekg() 読み取り位置を動かす
  • seekp() 書き込み位置を動かす
  • tellg() 現在の読み取り位置を返す
  • tellp() 現在の書き込み位置を返す

オフセットの基準には次を使う。

  • std::ios::beg ファイル先頭から
  • std::ios::cur 現在位置から
  • std::ios::end ファイル末尾から
inf.seekg(0, std::ios::beg);  // 先頭へ
inf.seekg(5, std::ios::beg);  // 先頭から 5 バイト
inf.seekg(-2, std::ios::end); // 末尾の 2 バイト手前

位置を変えて読む

#include <fstream>
#include <iostream>
#include <string>
 
int main()
{
    std::ifstream inf{ "Sample.txt", std::ios::binary };
    if (!inf)
        return 1;
 
    inf.seekg(5, std::ios::beg);
 
    std::string str {};
    std::getline(inf, str);
    std::cout << str << '\n';
}

tellg() / tellp()

現在位置を調べるには tellg()tellp() を使う。 ファイルサイズを大まかに知りたいときにも使える。

std::ifstream inf{ "Sample.txt", std::ios::binary };
inf.seekg(0, std::ios::end);
std::streampos size { inf.tellg() };

テキストファイルでの注意

テキストモードでは改行の表現が環境ごとに異なるため、途中位置への seekg() / seekp() は期待どおりにならないことがある。 ランダムアクセスを正確に行いたいなら、基本的にはバイナリモードの方が安全。

fstream で読み書き両方を行う

std::fstream は、1 つのファイルに対して読み書きの両方を行える。

std::fstream io{ "Sample.txt", std::ios::in | std::ios::out };

ただし、読み取りのあとに書き込み、または書き込みのあとに読み取りへ切り替えるときは、間にファイル位置を変更する操作を挟む必要がある。

io.seekg(io.tellg(), std::ios::beg);

位置変更を挟まずに読み書きを切り替えると、期待しない動作になることがある。

その他

  • is_open() ストリームが現在ファイルを開いているか調べる
  • remove() ファイルを削除する

また、ポインタの値そのものはメモリアドレスなので、ファイルへ保存してあとで再利用する用途には向かない。 アドレスを書き出しても、次回実行時には同じ場所を指す保証がない。

関連

参考