テキストファイル入出力
C++/Linuxプログラミング
テキストファイル入出力に関係するサンプルプログラムを以下に用意した。
4.テキストファイルに用意されたデータの読み込みと処理結果のテキストファイルへの出力
の順に説明する。
テキストファイルの入出力プログラム例をリスト1に示す。入力はifstream型のストリーム、出力はofstream型のストリームにより、標準入出力ストリームcin、coutを用いる場合と同じように行うことができる。
リスト2のデータを入力ファイルとしたときの実行例を図1に示す。
============= リスト1 TxtInOut.cpp ====================
#include <iostream> // cin, cout用ヘッダーファイル
#include <fstream> // テキストファイル入出力ストリーム用ヘッダーファイル
#include <string> // string型使用のため
using namespace std;
int main(){
char nm_fin[100], nm_fout[100];
cout << "Input File Name = ";
cin >> nm_fin;
ifstream fin; // テキストファイル入力用ストリーム
fin.open(nm_fin);
if(fin.fail()){
cout << "fin.open() failed." << endl;
return 9;
}
cout << "Output File Name = ";
cin >> nm_fout;
ofstream fout; // テキストファイル出力用ストリーム
fout.open(nm_fout);
if(fout.fail()){
cout << "fout.open() failed." << endl;
return 9;
}
string str;
int id = 0;
while(true){
fin >> str;
if (fin.fail()) break; // ファイルを最後まで読み込んだ
fout << (id++) << ": " << str << endl;
}
fin.close();
fout.close();
return 0;
}
==================================================
================ リスト2 Data.txt =======================
Data-1
Data-2
Data-3
==================================================
図1
入力テキストファイルData.txtの内容が1つずつ読みこまれて、出力用テキストファイルOut.txtに書き出されていることがわかる。
プログラムは圧縮ファイルTxtInOut.zipとして用意した。ファイル名TxtInOut.zipをクリックしてダウンロードすることができる。
リスト1のプログラムの場合、読み込みは演算子>>によって行われているので、データは空白などで区切られた文字列ごとに1つずつ読みこまれる。しかし、テキストファイルの場合、1行単位で行単位にまとめて読み込む方がプログラムが書きやすいことがある。行単位で読み込む関数としてgetlineがある。次に、この関数の使用例を示す。
リスト3に、テキストファイルから1行単位に読み込む関数getlineの使用例を示す。
================= リスト3 SepStr.cpp =============================
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
//
// stringをseparator文字で分割
//
class SeparateString {
public:
char * sep_c; // separator文字を格納するための配列
int n_sep_c; // separator文字の数
vector<string> list_str; // 分割された文字列を格納するためのvector<string>型コンテナ
SeparateString(): n_sep_c(0), sep_c(NULL) {};
virtual ~SeparateString(){
if(sep_c != NULL) delete [] sep_c;
if(!list_str.empty()) list_str.clear();
}
virtual vector<string>::size_type get_size(){
return list_str.size();
}
virtual void SetSeparator( char * c, int n_c ){
if(sep_c != NULL) delete [] sep_c;
n_sep_c = n_c;
int pos = 0;
sep_c = new char[n_sep_c];
for (int i = 0; i < n_sep_c; i++){
sep_c[i] = c[i];
}
}
//
// separator文字と一致するかどうかの判定
//
bool check_char( char c ){
bool v = false;
for (int i = 0; i < n_sep_c; i++)
if (c == sep_c[i]) v = true;
return v;
}
//
// stringをseparator文字で分割してvector<string>型コンテナlist_strに格納する
//
virtual void SetString( string & str ){
if(!list_str.empty()) list_str.clear();
if(str.length() > 0){
int s_pos = -1, e_pos = -1;
while(true){
do{
e_pos++;
}while((!check_char(str[e_pos])) && (e_pos < (str.length() - 1)));
if (!check_char(str[e_pos])){
e_pos++;
if (!check_char(str[e_pos])) e_pos++;
}
if((e_pos - s_pos) > 1){
list_str.push_back( str.substr( s_pos+1, e_pos - s_pos - 1 ) );
}else{
list_str.insert(list_str.end(), 1, "");
}
if(e_pos >= (str.length() - 1)) break;
s_pos = e_pos;
}
}
}
//
// 分割された文字列から空文字列でないipos番目の文字列を返す
// 該当する文字列がないときは、空の文字列""を返す。
//
virtual void SeekStr( int ipos, string & s ){
string v = "";
int ck_cnt = -1;
if(!list_str.empty()){
for(vector<string>::iterator ip = list_str.begin(); ip != list_str.end(); ip++){
if((*ip).length() > 0) ck_cnt++;
if(ck_cnt == ipos){
v = *ip;
break;
}
}
}
s = v;
}
//
// 分割されたipos番目の文字列(空文字列を含む)を返す。
//
virtual void SeekStrAll( int ipos, string & s ){
string v = "";
int ck_cnt = -1;
if(!list_str.empty()){
for(vector<string>::iterator ip = list_str.begin(); ip != list_str.end(); ip++){
ck_cnt++;
if(ck_cnt == ipos){
v = *ip;
break;
}
}
}
s = v;
}
};
int main(){
char fl_nm[100];
cout << "File name = ";
cin >> fl_nm;
ifstream fin;
fin.open(fl_nm);
//
// 1回目の読み込みと文字列の処理
//
cout << endl << "\t\t Step-1" << endl;
char sepchr[] = {' ', '\t', ','};
SeparateString sepstr;
sepstr.SetSeparator( sepchr, 3 );
string str;
while(true){
getline(fin, str);
if(fin.fail()) break;
cout << endl << str << endl;
sepstr.SetString( str );
cout << "Size = " << sepstr.get_size() << endl;
for(int i = 0; i < sepstr.get_size(); i++){
string tstr;
sepstr.SeekStrAll( i, tstr );
cout << " <" << tstr << ">";
}
cout << endl;
}
fin.close();
//
// 2回目の読み込みと文字列の処理
//
cout << endl << "\t\t Step-2" << endl;
fin.open(fl_nm);
char sepchr1[] = {' ', '\t'};
sepstr.SetSeparator( sepchr, 2 );
while(true){
getline(fin, str);
if(fin.fail()) break;
cout << endl << str << endl;
sepstr.SetString( str );
cout << "Size = " << sepstr.get_size() << endl;
for(int i = 0; i < sepstr.get_size(); i++){
string tstr;
sepstr.SeekStrAll( i, tstr );
cout << " <" << tstr << ">";
}
cout << endl;
}
fin.close();
return 0;
}
==============================================
関数getlineは
getline(fin, str);
というように、
getline( 入力ストリーム, 文字列用変数 )
の形式で用いる。入力ストリームから1行分の文字列が読み込まれ、文字列用変数に設定される。
リスト4のテキストデータファイルの場合、各データはコンマ’,’、空白’ ‘、タブ文字’\t’で区切られて複数個並べられている。1行単位で読み込んだ場合、これらの各データを1つずつ取り出す必要がある。そのためのクラス型としてSeparateString型が宣言されている。
============== リスト4 DataStr.txt =================
a,b,c abc def
123,456,789
===========================================
データの分離文字として、コンマ’,’、空白’ ‘、タブ文字’\t’を用いる場合は、以下のようにする。
char sepchr[] = {' ', '\t', ','};
SeparateString sepstr;
sepstr.SetSeparator( sepchr, 3 );
分離文字を格納する配列を用意して(上の場合、sepchr)、SetSeparatorメソッドで設定する。2番目の引数は、分離文字を表す配列の大きさ(分離文字の個数)を示すものである。
分離文字を設定した後、メソッドSetStringを
sepstr.SetString( str );
というように呼び出すと、strに格納された文字列が分離文字によって区切られ、区切られた各文字列がSeparateString型のオブジェクト(上の例の場合、sepstr)内のvector<string>型のコンテナに格納される。格納された各文字列は、メソッドSeekStr、あるいはSeekStrAllによって取り出すことができる。
virtual void SeekStr( int ipos, string & s )
は、空の文字列でないものの中からipos番目のものを選んでsに設定するものである。ipos番目のものがない(iposの値が、空でない文字列の個数より大きい)ときは、sには空の文字列””が設定される。
virtual void SeekStrAll( int ipos, string & s )
は、空の文字列も含めてipos番目の文字列をsに設定するものである。iposの値が文字列の個数より大きいときは、sには空の文字列””が設定される。
リスト4のデータファイルDataStr.txtを入力ファイルとしたときの実行例を図2に示す。
図2
2回目の入出力を示す「Step-2」以下の出力では、分離文字からコンマ’,’を除いて
char sepchr1[] = {' ', '\t'};
sepstr.SetSeparator( sepchr, 2 );
と設定されているので、読み込みデータ「123,456,789」は1つのまとまった文字列データとして扱われている。
プログラムは圧縮ファイルSepStr.zipとしてまとめたので、ファイル名SepStr.zipをクリックしてダウンロードすることができる。
数値データを表す文字列は、その表す数値に変換する必要がある。そのための関数StrToDouble(実数型doubleに変換)とStrToInt(整数型intに変換)を用意した。その使用例をリスト5に示す。
================= リスト5 StrToNum.cpp ========================
#include <iostream>
#include "MyLib.h"
using namespace std;
using namespace mylib;
int main(){
cout << endl;
try{
string s = ""; // 実数値として不適当な文字列
cout << "string = <" << s << ">" << endl;
double v = StrToDouble(s);
cout << "value = " << v << endl;
}
catch(myexcept & e){
cout << e.what() << endl;
}
try{
string s = "123.45";
cout << "string = <" << s << ">" << endl;
double v = StrToDouble(s);
cout << "value = " << v << endl;
}
catch(myexcept & e){
cout << e.what() << endl;
}
try{
string s = "xyz123.45"; // 実数値として不適当な文字列
cout << "string = <" << s << ">" << endl;
double v = StrToDouble(s);
cout << "value = " << v << endl;
}
catch(myexcept & e){
cout << e.what() << endl;
}
try{
string s = "";
cout << "string = <" << s << ">" << endl;
int iv = StrToInt(s);
cout << "value = " << iv << endl;
}
catch(myexcept & e){
cout << e.what() << endl;
}
try{
string s = "12345";
cout << "string = <" << s << ">" << endl;
int iv = StrToInt(s);
cout << "value = " << iv << endl;
}
catch(myexcept & e){
cout << e.what() << endl;
}
try{
string s = "a123"; // 整数値として不適当な文字列
cout << "string = <" << s << ">" << endl;
int iv = StrToInt(s);
cout << "value = " << iv << endl;
}
catch(myexcept & e){
cout << e.what() << endl;
}
cout << endl;
return 0;
}
=======================================================
関数StrToDoubleとStrToIntはヘッダーファイルMyLib.hとソースコードファイルMyLib.cppに用意されている。それぞれをリスト6とリスト7に示す。
===================== リスト6 MyLib.h =========================
#ifndef MYLIB_CK
#define MYLIB_CK
#include <string>
#include <sstream>
#include <exception>
using namespace std;
namespace mylib {
//
// 例外生成用クラス型
//
class myexcept: public std::exception {
public:
string vsc;
myexcept( const char * sc ): vsc(sc) { }
virtual ~myexcept() throw() {}
string & what() { return vsc; }
};
//
// 文字列の表す数値に変換
//
double StrToDouble( string s );
int StrToInt( string s );
}
#endif
=========================================================
===================== リスト7 MyLib.cpp =======================
#include "MyLib.h"
using namespace std;
namespace mylib {
//
// 文字列の表す数値に変換
//
double StrToDouble( string s ){
double v;
if (s.length() == 0) throw myexcept("NULL string@StrToDouble !");
istringstream istr(s);
istr >> v;
if (istr.fail()){
string ts = "Conversion to double error !...input string = " + s;
char ccs[100];
int il = ts.size();
if (il > 98) il = 98;
for (int i = 0; i < il; i++) ccs[i] = ts[i];
ccs[il] = 0;
throw myexcept(ccs);
}
return v;
}
int StrToInt( string s ){
int v;
if (s.length() == 0) throw myexcept("NULL string@StrToInt !");
istringstream istr(s);
istr >> v;
if (istr.fail()){
string ts = "Conversion to int error !...input string = " + s;
char ccs[100];
int il = ts.size();
if (il > 98) il = 98;
for (int i = 0; i < il; i++) ccs[i] = ts[i];
ccs[il] = 0;
throw myexcept(ccs);
}
return v;
}
}
=======================================================
変換関数StrToDoubleおよびStrToIntでは、空の文字列や不適切な文字列により変換が失敗した場合にはmyexcept型(リスト6のMyLib.hで宣言されている)の例外が生成される。このことは図3の実行例に示されている。
図3
空の文字列や、数値を表すものとしては不適切な「xyz123.45」などが変換文字列として使われたときに例外が生成されていることがわかる。
プログラムを圧縮ファイルStrToNum.zipにまとめた。ファイル名StrToNum.zipをクリックしてダウンロードすることができる。
4.テキストファイルに用意されたデータの読み込みと処理結果のテキストファイルへの出力
上で説明されたことの応用として、テキストファイルとして用意されたデータを読みこんで簡単な処理を行うプログラム例を用意した。データファイルはリスト8に示すものである。
====================== リスト8 Data2Vars.txt ===============================
/
1 10
2 20
3 30
4 40
5 50
6 60
7 70
8 80
9 90
10 100
/
==================================================================
プログラムをリスト9に示す。
==================== リスト9 AnalData.cpp =================================
#include <iostream> // cin,cout用ヘッダーファイル
#include <fstream> // テキストファイルストリーム用ヘッダーファイル
#include <string> // string用ヘッダーファイル
#include "MyLib.h" // SeparateString型,StrToDouble関数用ヘッダーファイル
using namespace std;
using namespace mylib;
int main(){
char fin_nm[100], fout_nm[100]; // 入出力用ファイル名設定用文字列配列
cout << "Data file name = ";
cin >> fin_nm;
ifstream fin;
fin.open(fin_nm);
if(fin.fail()){
cout << "fin.open() failed." << endl;
return 9;
}
cout << "Output file name = ";
cin >> fout_nm;
ofstream fout;
fout.open(fout_nm);
fout<< "Data File = " << fin_nm << endl;
string str; // テキストファイルからの読み込み用変数
while(true){
getline(fin, str);
if(str.size() > 0)
if(str[0] == '/') break; // データの読み込み開始
}
double X[1000], Y[1000]; // データ格納用配列
int n = 0;
double SumX = 0.0, SumY = 0.0;
char sepchr[] = {' ', '\t', ','}; // データ文字列の分離文字
SeparateString sepstr; // 文字列を分割するためのSeparateString型オブジェクト
sepstr.SetSeparator( sepchr, 3 );
while(true){
string vstr;
getline(fin, str);
if(str[0] == '/') break; // データの読み込み終了
sepstr.SetString( str );
sepstr.SeekStr( 0, vstr ); // 1番目のデータ
X[n] = StrToDouble( vstr );
SumX += X[n];
sepstr.SeekStr( 1, vstr ); // 2番目のデータ
Y[n] = StrToDouble( vstr );
SumY += Y[n];
n++;
}
fin.close();
fout << endl << "Data..." << endl;
for (int i = 0; i < n; i++)
fout << "X[" << i << "] = " << X[i]
<< " Y[" << i << "] = " << Y[i] << endl;
fout << endl << "SumX = " << SumX << " MeanX = " << SumX/n << endl;
fout << "SumY = " << SumY << " MeanY = " << SumY/n << endl;
fout.close();
return 0;
}
============================================================
ヘッダーファイルMyLib.hとそのソースコードファイルMyLib.cppをリスト10とリスト11に示す。
==================== リスト10 MyLib.h ============================
#ifndef MYLIB_CK
#define MYLIB_CK
#include <string>
#include <sstream>
#include <vector>
#include <exception>
using namespace std;
namespace mylib {
//
// stringをseparator文字で分割
//
class SeparateString {
public:
char * sep_c; // separator文字を格納するための配列
int n_sep_c; // separator文字の数
vector<string> list_str; // 分割された文字列を格納するためのvector<string>型コンテナ
SeparateString(): n_sep_c(0), sep_c(NULL) {};
virtual ~SeparateString(){
if(sep_c != NULL) delete [] sep_c;
if(!list_str.empty()) list_str.clear();
}
virtual vector<string>::size_type get_size(){
return list_str.size();
}
virtual void SetSeparator( char * c, int n_c ){
if(sep_c != NULL) delete [] sep_c;
n_sep_c = n_c;
int pos = 0;
sep_c = new char[n_sep_c];
for (int i = 0; i < n_sep_c; i++){
sep_c[i] = c[i];
}
}
//
// separator文字と一致するかどうかの判定
//
bool check_char( char c ){
bool v = false;
for (int i = 0; i < n_sep_c; i++)
if (c == sep_c[i]) v = true;
return v;
}
//
// stringをseparator文字で分割してvector<string>型コンテナlist_strに格納する
//
virtual void SetString( string & str ){
if(!list_str.empty()) list_str.clear();
if(str.length() > 0){
int s_pos = -1, e_pos = -1;
while(true){
do{
e_pos++;
}while((!check_char(str[e_pos])) && (e_pos < (str.length() - 1)));
if (!check_char(str[e_pos])){
e_pos++;
if (!check_char(str[e_pos])) e_pos++;
}
if((e_pos - s_pos) > 1){
list_str.push_back( str.substr( s_pos+1, e_pos - s_pos - 1 ) );
}else{
list_str.insert(list_str.end(), 1, "");
}
if(e_pos >= (str.length() - 1)) break;
s_pos = e_pos;
}
}
}
//
// 分割された文字列から空文字列でないipos番目の文字列を返す
// 該当する文字列がないときは、空の文字列""を返す。
//
virtual void SeekStr( int ipos, string & s ){
string v = "";
int ck_cnt = -1;
if(!list_str.empty()){
for(vector<string>::iterator ip = list_str.begin(); ip != list_str.end(); ip++){
if((*ip).length() > 0) ck_cnt++;
if(ck_cnt == ipos){
v = *ip;
break;
}
}
}
s = v;
}
//
// 分割されたipos番目の文字列(空文字列を含む)を返す。
//
virtual void SeekStrAll( int ipos, string & s ){
string v = "";
int ck_cnt = -1;
if(!list_str.empty()){
for(vector<string>::iterator ip = list_str.begin(); ip != list_str.end(); ip++){
ck_cnt++;
if(ck_cnt == ipos){
v = *ip;
break;
}
}
}
s = v;
}
};
class myexcept: public std::exception {
public:
string vsc;
myexcept( const char * sc ): vsc(sc) { }
virtual ~myexcept() throw() {}
virtual string & what() { return vsc; }
};
//
// 文字列の表す数値に変換
//
double StrToDouble( string s );
int StrToInt( string s );
}
#endif
=========================================================
==================== リスト11 MyLib.cpp ========================
#include "MyLib.h"
using namespace std;
namespace mylib {
//
// 文字列の表す数値に変換
//
double StrToDouble( string s ){
double v;
if (s.length() == 0) throw myexcept("NULL string@StrToDouble !");
istringstream istr(s);
istr >> v;
if (istr.fail()){
string ts = "Conversion to double error !...input string = " + s;
char ccs[100];
int il = ts.size();
if (il > 98) il = 98;
for (int i = 0; i < il; i++) ccs[i] = ts[i];
ccs[il] = 0;
throw myexcept(ccs);
}
return v;
}
int StrToInt( string s ){
int v;
if (s.length() == 0) throw myexcept("NULL string@StrToInt !");
istringstream istr(s);
istr >> v;
if (istr.fail()){
string ts = "Conversion to int error !...input string = " + s;
char ccs[100];
int il = ts.size();
if (il > 98) il = 98;
for (int i = 0; i < il; i++) ccs[i] = ts[i];
ccs[il] = 0;
throw myexcept(ccs);
}
return v;
}
}
=========================================================
リスト8のデータData2Vars.txtを入力ファイルとしたときのプログラムの実行例を図4に示す。
図4
======================= リスト12 Out.txt ===========================
Data File = Data2Vars.txt
Data...
X[0] = 1 Y[0] = 10
X[1] = 2 Y[1] = 20
X[2] = 3 Y[2] = 30
X[3] = 4 Y[3] = 40
X[4] = 5 Y[4] = 50
X[5] = 6 Y[5] = 60
X[6] = 7 Y[6] = 70
X[7] = 8 Y[7] = 80
X[8] = 9 Y[8] = 90
X[9] = 10 Y[9] = 100
SumX = 55 MeanX = 5.5
SumY = 550 MeanY = 55
=============================================================
プログラムなどは圧縮ファイルAnalData.zipとしてまとめた。ファイル名AnalData.zipをクリックしてダウンロードすることができる。