C++におけるファイル読み込みのまとめ
目次
ファイルの開き方
C++でファイルを開くときはfstreamヘッダをインクルードする必要があり、この中のifstream(インプットストリーム)を使用します。例えば「test.txt」を開くときは以下のようになります。
std::ifstream ifs(“test.txt”); std::string str; ifs>>str; std::cout<<str<<std::endl;
ファイルを閉じるときはcloseメソッドを使えばよいが、特に使用しなくても勝手に閉じるので使わない場合が多いです。
読み込みに失敗したかどうか判定するにはfailメソッドを使えばできます。
if(ifs.fail()) { std::cout<<”読み込みに失敗しました”<<std::endl; }
また、バイナリで開くなどの指定も可能。
モード 意味
ios::app 追加出力
ios::ate 開く時にEOFまで移動する
ios::binary ファイルをバイナリモードで開く
ios::in 読み込み専用で開く
ios::out 出力用にファイルを開く
ios::trunc 既存のファイルを上書きする
これとは別にC言語でよければfopenでも簡単に開けます。
値の取得方法
>>演算子を使う
>>演算子を用いて値を取得することができる
std::ifstream ifs(“test.txt”); int a,b,c; char split; ifs>>a>>split>>b>>split>>c;
ただ、
2,4,6,9
のようなデータならよいですが、
3,,5,
のように区切りの間に空白部分があるとあやしくなってしまいます。
1行ずつ読み込む
std::getline関数を用いることで1行読み込みが行えるため、その文字列から数値に落とし込みます。
std::ifstream ifs(“test.txt”); std::string str; while(getline(ifs,str)) { std::cout<< str << std::endl; }
上の例はgetlineの戻り値はifsなのでファイルの終端まで読み込みを行うことができます。
stringにはc_strメソッドとdataメソッドの2つの方法のいずれかでchar*への変換が可能となっています。
(両者の違いについてC++標準文字列:c_str, data, operator[])
取得した文字列をどうやって数値に戻していくのか検討したいと思います。
(1) sscanfを用いる方法(C言語)
C言語の標準ライブラリに定義されている関数です。
int sscanf(const char *str, const char *format, ... );
…の部分は可変引数と呼ばれ、文字通り任意の個数だけ引数を指定できる。これはprintfなどでも使用されています。
これを利用して得られたchar*を分解します。
例えば読み込んだデータが
int , int , int ,float
といった並びになっていれば
int a,b,c; float d; sscanf(str.c_str(),“%d,%d,%d,%f”,&a,&b,&c,&d);
とすればめでたく数値を読み込めます。
(2)文字列から数値に変換する方法
(2-1)文字の解析
sscanfでは一度に文字を数値に変換できてしまえましたが、その他の方法では、その前に文字列を数値のものに分割する必要のあるケースも多いです。例えばCSVファイルなどのカンマ区切りのものはカンマとデータそのものを分離させる必要があり、さらに文字列を数値データに直す必要があります。
例)
“1,6,-2” (←文字列)
↓
“1”、”6”、”-2” (←文字列×3)
↓
1、6、-2 (←数値)
この問題を解決するにはどうすればよいのか。
A. getlineによる分割
getlineは指定文字リテラルで文字を分割することもできます。3つめの引数にカンマなどの文字を指定することで可能。ただし、この際に使用する入力文字列ストリームistringstreamを使用するにはsstreamをインクルードする必要があります。
#include<sstream> … std::ifstream ifs(“test.txt”); std::string str; while(getline(ifs,str)) { std::string tmp; std::istringstream stream(str); while(getline(stream,tmp,’,’)) { std::cout<< tmp << std::endl; } }
例)
5
4
ただ、このままだと文字列ということに変わりはないです。
B. splitによる分割(指定キーワードによる文字列分割)
残念ながらC++には想像しているようなSplitメソッドは存在しないようで、自作で作るかBoostライブラリを使用するしかないらしいです。
が、しかし、それについて解決策を記述されている方も多く、そちらを参考にした方がいいかもしれません。
(2-2)数値への変換
A. atoi,afofなどを用いる方法(C言語)
文字列を数値に変換する手法としてC言語(stdlib.h)にあるatoi関数やatof関数を用いるものがあります。(iはintのi)
ただし、これらの手法の問題点は数値に変換したい文字列が数値でなかった場合の例外発生がないということです。
int tmp1 = atoi(“123”); //○ int tmp2 = atoi(“ABC”); //×
なので、これを使う場合はちゃんと数字かどうかの判定が欠かせないです。
B. strtodを用いる手法(C言語)
この関数もstdlibに宣言されている関数ですが、atoiなどとの違いはエラー検出があるという点です。
double strtod(const char *s, char **endptr);
はじめの引数には変換したい文字列、2つ目の引数にはエラーの場合の出力用のようです。
doubleへの変換が失敗した場合失敗した個所の文字列のポインタが格納されるようになっています。
char *tmp1 = "1.23"; char *err1=NULL; char *tmp2 = "1.23ABC"; char *err2=NULL; char *tmp3 = "ABC1.23DEF"; char *err3=NULL; double x1 = strtod(tmp1,&err1); double x2 = strtod(tmp2,&err2); double x3 = strtod(tmp3,&err3); cout << "x1=" << x1 << "," << err1 << endl; cout << "x2=" << x2 << "," << err2 << endl; cout << "x3=" << x3 << "," << err3 << endl;
x2=1.23,ABC
x3 =0,ABC1.23DEF
(※3つめは失敗)
文字列を含んでいても一部では変換がうまくいっていますが、冒頭から文字列の場合はうまくいかない様子です。たとえ変換に失敗しても0が帰ってくるだけのようです。
いずれにせよ第2引数の値などでエラー判定が可能なため安心して数値変換が行えるようになっています。ちなみに第2引数をNULLとしてしまっても変換不可能な処理を行わないだけで動きます。
C. C++/CLIを用いる手法
VisualStudioであればプロジェクトのプロパティで「共通言語ランタイムサポート」を「共通言語ランタイムサポート(/clr)とするとコンソールアプリケーションでもSystem::Stringが使えるようになるのでこれを用いて1行の文字をカンマなどの指定の文字で分割(Splitメソッド)し、String→int,floatなどで値の取得ができます。
char *tmpstr = "123"; System::String ^str = gcnew System::String(tmpstr); try { int tmp = int::Parse(str); System::Console::WriteLine(tmp);//123 } catch(System::Exception^){}
try~catchは例外処理。doubleやfloatに変換したいときは『int::』の部分を『float::』や『double::』にすればできます。
もしこの方法を用いるのなら読み込みからこちらの文法を使用したほうがよいかもしれないです。