テキストファイルの行単位読み込みと例外の生成
標準のテキストファイル読み込み用ストリームifstream型によるデータ項目単位の読み込み例をウェブサイト<テキストファイル入出力:項目単位での入力>に用意した。しかし、データファイルの誤りを見つけるときには、項目単位での読み込みではなく、行単位で読み込みを行う方が、データファイルでの誤りを探しやすい。行単位での読み込みを関数getlineを用いて行った。この関数を用いるときはヘッダーファイルstringをインクルードしておく。所属する名前空間はstdである。この関数は、例えば
getline( ifstream & fin, string & s )
という形で用いることができる。
1行に複数のデータ項目が書かれているときは、それらを1つずつ取り出すことが必要である。そのための関数としてSeekStrを以下のように用意した。
//
// 空白文字、タブ文字のチェック
//
bool checkBlnkTab( char c ){
bool
v = false;
if
(c == ' '){
v
= true;
}else
if (c == '\t'){
v
= true;
}
return
v;
}
//
// 文字列 s から pos 番目の部分文字列を抜き出す。
// 部分文字列は、空白文字あるいはタブ文字で区切られているものとする
//
string SeekStr( string & s, int pos ){
string
vs = "";
int
sL = s.length();
if
(sL > 0){
int
spos = 0, epos = 0;
for
(int i = 0; i <= pos; i++){
//
// 部分文字列の先頭位置
//
do{
if
(spos < sL){
if
(checkBlnkTab(s[spos])){
spos++;
}else{
break;
}
}else{
break;
}
}while(true);
if
(spos >= sL) return vs; // pos
番目の部分文字列がない
//
// 部分文字列の終わりの次の位置
//
epos
= spos;
do{
if
(epos < sL){
if
(!checkBlnkTab(s[epos])){
epos++;
}else{
break;
}
}else{
break;
}
}while(true);
if
(i == pos){
//
// pos
番目の部分文字列の設定
//
vs
= s.substr(spos, epos-spos);
}else{
spos
= epos;
}
}
}
return
vs;
}
取り出した文字列は、その表す数値に変換する必要がある。そのための関数を、文字列をファイルとして扱うクラス型istringstreamを用いて用意した。istringstream型は、string型の値、例えばstring型の変数sに文字列が設定されているとき
double
v;
istringstream istr(s);
istr
>> v;
と用いると、ストリーム型の変数istrは標準入力ストリームcinと同じように扱うことができ、上の例ではsに設定されている文字列の表す値がvに読み込まれる。
s
== “123.45”
であれば、vの値は「123.45」に設定される。sが”123.45 6.7 89.01”というように空白文字で区切られた複数の文字列であるときは、
double
v1, v2, v3;
istringstream istr(s);
istr
>> v1 >> v2 >> v3;
で、v1、v2、v3に123.45、6.7、89.01が読み込まれる。
このistringstream型を用いるときは、ヘッダーファイルsstreamをインクルードしておく。名前空間はstdである。このistringstream型を用いて文字列をその表す数値に変換する関数を以下のように用意した。
//
// 文字列の表す数値に変換
//
double StrToDouble( string s ){
double
v = 0.0;
if
(s.length() == 0) throw new exception("NULL string in StrToDouble.");
istringstream
istr(s);
istr
>> v;
if
(istr.fail()) throw new exception("Conversion error in
StrToDouble.");
return
v;
}
変換時の例外は
throw
new exception(文字列);
の形で生成しているので、処理の方は
catch (exception * e)
という形で受けている。
throw exception(文字列);
のときは、
catch (exception & e)
となる。newによって生成した例外をthrowするときはポインタ型引数を指定したcatchで受けるが、newを用いないときは参照引数を指定したcatchが対応する(岡本安晴「大学生のための心理学VC++プログラミング入門」勁草書房、7.5節「例外」参照)。
以上の準備のもとで用意したプログラムはこのウェブサイトの最後にあげているが、このプログラムはデータを図1のように用意した形式のテキストファイルを読み込む。
図1
スラッシュ/で始まる4行目までファイルを読み飛ばした後、次のスラッシュで始まる行の前の行までに書かれているデータを行単位で読み込む。これは以下のコードによって行っている。
int
n = 0;
while(true){
std::getline(fin,
s);
if
(s.length() > 0)
if
(s[0] == '/') break; // 最初の/の行まで読み飛ばす
}
while(true){
std::getline(fin,
s);
if
(s.length() > 0)
if
(s[0] == '/') break; // 次の/の行まで数える
n++;
}
上のコードは、スラッシュで始まる行で挟まれた行を数えるものであるが、この後、以下のように入力ファイルを開きなおしてデータの読み込みを行っている。
fin.close();
fin.open(s0); // 再度、入力データファイルを開きなおす。
while(true){
std::getline(fin,
s);
if
(s.length() > 0)
if
(s[0] == '/') break;
}
double
* x, * y;
x
= new double[n];
y
= new double[n];
for
(int i = 0; i < n; i++){
std::getline(fin,
s);
try{
x[i]
= StrToDouble(SeekStr(s,0));
y[i]
= StrToDouble(SeekStr(s,1));
}
catch
(exception * e){
fin.close();
fout.close();
cout
<< e->what() << endl;
cout
<< "i = " << i << "/string data -> ["
<< s << "]" << endl;
string
cks;
cout
<< "何か文字キーを入力すると終了します。" << endl;
cin
>> cks;
return
9;
}
}
プログラムを実行すると、図2のように入力データファイル名の入力が求められる。
図2
Visual C++の場合、プログラム実行時のカレントディレクトリ(フォルダ)はmain関数の書かれているソースコードファイルなどと同じディレクトリであるので、データファイルが同じフォルダにある場合はファイル名だけを書けばよい。図3のように例えば「data.txt」と設定してEnterキーを押すと、出力用ファイル名の設定が求められる(図3)。出力用ファイルも、ソースコードファイルと同じディレクトリに出力するときはファイル名だけを設定すればよい。
図3
出力ファイル名を設定してEnterキーを押すと計算が始まる。計算が終了すると図4のように「何か文字キーを入力すると終了します。」のメッセージが表示される。
図4
図5のように適当な文字キー(図5の場合はaキー)を押してから、Enterキーを押すとプログラムの実行が終了する(F5キーを押すなどして「デバッグ開始」で実行の場合)。
図5
プログラムの実行終了後、出力ファイルを開くと図6のようになっている。
図6
入力ファイルとして図7のようにデータの書き方がプログラムに合わない場合は、文字列を数値に変換する関数StrToDoubleで例外が生成されると図8のように例外の生成が表示される。
図7
図8
for文の制御変数iの値が4のときの処理で例外が生成されたことが表示されているので、図7の4+1番目のデータ行に誤りがあることがわかる。例外が生成されたときの処理対象文字列が「 5 a 50」であることも示されている。
プログラムリストの全体を以下に示す。
=======================================
// FileStreamLN.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//
#include "stdafx.h"
#include <iostream>
#include <fstream> // ifstream, ofstream
#include <sstream> // sstringstream型使用のため
#include <string> // string型使用のため
//#include <exception>
using namespace std;
//
// 空白文字、タブ文字のチェック
//
bool checkBlnkTab( char c ){
bool v = false;
if (c == ' '){
v = true;
}else if (c == '\t'){
v = true;
}
return v;
}
//
// 文字列 s から pos 番目の部分文字列を抜き出す。
// 部分文字列は、空白文字あるいはタブ文字で区切られているものとする
//
string SeekStr( string & s, int pos ){
string vs = "";
int sL = s.length();
if (sL > 0){
int spos = 0, epos = 0;
for (int i = 0; i <= pos; i++){
//
// 部分文字列の先頭位置
//
do{
if (spos < sL){
if (checkBlnkTab(s[spos])){
spos++;
}else{
break;
}
}else{
break;
}
}while(true);
if (spos >= sL) return vs; // pos 番目の部分文字列がない
//
// 部分文字列の終わりの次の位置
//
epos = spos;
do{
if (epos < sL){
if (!checkBlnkTab(s[epos])){
epos++;
}else{
break;
}
}else{
break;
}
}while(true);
if (i == pos){
//
// pos 番目の部分文字列の設定
//
vs = s.substr(spos, epos-spos);
}else{
spos = epos;
}
}
}
return vs;
}
//
// 文字列の表す数値に変換
//
double StrToDouble( string s ){
double v = 0.0;
if (s.length() == 0) throw new exception("NULL string in StrToDouble.");
istringstream istr(s);
istr >> v;
if (istr.fail()) throw new exception("Conversion error in StrToDouble.");
return v;
}
int _tmain(int argc, _TCHAR* argv[])
{
ifstream fin;
ofstream fout;
string s, s0, s1;
cout << "Data file name = ";
cin >> s0;
fin.open(s0);
if (fin.fail()){
cout << "Opening the input file failed." << endl;
cout << "何か整数値を入力して終了。" << endl;
int ck;
cin >> ck;
return 9;
}
cout << "Output file name = ";
cin >> s1;
fout.open(s1);
fout << "Data file = " << s0 << endl << endl;
int n = 0;
while(true){
std::getline(fin, s);
if (s.length() > 0)
if (s[0] == '/') break; // 最初の/の行まで読み飛ばす
}
while(true){
std::getline(fin, s);
if (s.length() > 0)
if (s[0] == '/') break; // 次の/の行まで数える
n++;
}
fout << "No. of data = " << n << endl;
fin.close();
fin.open(s0); // 再度、入力データファイルを開きなおす。
while(true){
std::getline(fin, s);
if (s.length() > 0)
if (s[0] == '/') break;
}
double * x, * y;
x = new double[n];
y = new double[n];
for (int i = 0; i < n; i++){
std::getline(fin, s);
try{
x[i] = StrToDouble(SeekStr(s,0));
y[i] = StrToDouble(SeekStr(s,1));
}
catch (exception * e){
fin.close();
fout.close();
cout << e->what() << endl;
cout << "i = " << i << "/string data -> [" << s << "]" << endl;
string cks;
cout << "何か文字キーを入力すると終了します。" << endl;
cin >> cks;
return 9;
}
}
fout << "\nData.." << endl;
double sx = 0.0, sy = 0.0;
for (int i = 0; i < n; i++){
fout << "x[" << i << "] = " << x[i] << " y[" << i << "] = " << y[i] << endl;
sx += x[i];
sy += y[i];
}
fout << endl << "mean_x = " << sx/10.0 << " mean_y = " << sy/10.0 << endl;
fin.close();
fout.close();
cout << endl << n << " 組みのデータが処理されました。" << endl;
cout << "何か文字キーを入力すると終了します。" << endl;
cin >> s;
return 0;
}
=======================================