c++ でテキストファイルを読み込んでループを回す時のメモ
データ解析においてテキストファイルに書かれたデータを読み込む機会は結構多い。
俺も今までさんざん使ってきたんだけど、結構細かい書き方忘れちゃうし、曖昧な箇所も多いのでこの際ちゃんと覚える。
例えば次のような内容のテキストファイル(test.dat)があったとする。
1 2
3 4
これを読み込んでみよう。
書き方その1
void test(){
int a,b;
ifstream ifs;
ifs.open("test.dat");
int loop=0;
ifs>>a>>b;
while(!ifs.eof()){
cout<<"loop="<<loop<<endl;
cout<<"a,b="<<a<<","<<b<<endl;
ifs>>a>>b;
loop++;
}
}
出力結果
loop=0
a,b=1,2
loop=1
a,b=3,4
メモ
ヘッダはfstream.hが必要。using name spaceとかは書いてないけどコンパイルするならほんとは必要。ファイルの読み込みはfstreamを通して行う。
ファイルを開くとき、ここでは一度fstreamクラスのオブジェクト(ifs)をつくってから、openという関数を使っているが、ifstream("test.dat")と一気にコンストラクタで開くことも可能。まぁどっちでもいい。
loopはloopの回数数えてるだけので別になくてもいい。ただ、for分と違って無限loopが怖いのでwhile文の場合はloop回数についつい神経質になってしまう。
eof()は読みこむ際に最後の行であるかを判定してtrue(0)かfalse(1)を返す。
うまく読み込めればfalse、最後までいって読み込めなくなったらtrueを返す。
while文はtrueの時だけ回るので!がついているというわけ。
コードをよくよく見ると、1行目をloopの外で読み込んでからloopに突入し、表示→読み込みの順番でloopを繰り返しているが、これにはちゃんと意味がある。下の悪い例を見てほしい。
悪い書き方
void test(){
int a,b;
ifstream ifs;
ifs.open("test.dat");
int loop=0;
while(!ifs.eof()){
ifs>>a>>b;
cout<<"loop="<<loop<<endl;
cout<<"a,b="<<a<<","<<b<<endl;
loop++;
}
}
出力結果
loop=0
a,b=1,2
loop=1
a,b=3,4
loop=2
a,b=3,4
違いはloopの中で読み込み→表示を繰り返してること。見てわかるように最後に一回余分にloopが回ってる。
この不具合は!eof()が最後の行を読み込んだときはtrueでその次の存在しない行を読み込んだ時に初めてfalseを出力するために発生する。
つまり、最後の存在しない行を読み込んで!eof()がfalseになった直後に表示を余分にしてしまうのだ。このへんはなんとなくコードを書いているとやってしまいがちなので注意する。(というかやってしまった)
書き方その2
void test(){
int a,b;
ifstream ifs;
ifs.open("test.dat");
int loop=0;
while(ifs>>a>>b){
cout<<"loop="<<loop<<endl;
cout<<"a,b="<<a<<","<<b<<endl;
loop++;
}
}
違いはwhile文の中身で、ifs>>a>>bの読み込みがうまくいってる限りloopが回り、読み込めなくなった時点で止まる。さっきよりすっきりしてるし、例の一回余分に読み込んでしまう不具合も発生しない。難点を挙げるとすれば読み込みのifs>>...の部分が長くなったときにwhile文のかっこの中身がごちゃごちゃしてしまって見づらいくらい?いやほんとに難点って言っていいのかわからないけど。
ちなみに言うまでもないけど、for文はloopの回数がわかってる時には使えるが、
datファイルの中身が何行あるかわからないときは今回みたいにwhile文を使うしかない。
個人的にはfor文のほうが好きだけど致し方ない。
まとめ
・テキストファイルの読み込みはfstreamを使いwhile文でloopを回す
・fstream::eof()を使うときは最後の行の振る舞いに注意
おわり!