C++によるテキストファイルの入出力
C++によるテキストファイルの読み込み・書き出しを、.NET Framework対応のC++/CLRの場合と、標準C++準拠の場合とについて説明する。
C++/CLRに用意されているストリームを用いると、以下に示すように簡単にテキストファイルの入出力を行うことができる。
コンソールの場合もテキストファイルの場合も、文字列の入力はRead/ReadLine関数で、出力はWrite/WriteLine関数で行うことができる。数値データなどはその値を表す文字列に変換して書き出すが、そのための関数としてToStringがある。数値データの読み込みは、その数値を表す文字列を読みこみ、読み込んだ文字列をParse関数で対応する数値データに変換する。例えば、以下のコード
double v = 1.0 / 3.0;
Console::WriteLine(v.ToString());
Console::WriteLine(1.23.ToString());
を実行すると、図1のような出力になる。
図1
Consoleはコンソールへの入出力を行うためのストリーム(参照クラス型)であり、その入出力用の関数WriteLineを用いている。
v.ToString()
により、v の内容がその値を表す文字列に変換され、
1.23.ToString()
により、数値 1.23 がその値を表す文字列 “1.23” に変換されて出力される。
参照クラス型(C++/CLR用クラス型 ref class)については、<岡本安晴「大学生のための心理学VC++プログラミング入門」勁草書房>などを参照されたい。
コンソールからの入力は、以下のようにReadLineを用いてできる。
String ^ s;
s = Console::ReadLine();
double v = double::Parse(s);
Console::WriteLine("入力値の2倍 = " + (v * 2.0).ToString());
ReadLineは文字列を読み込むので、C++/CLRでの文字列型String(Sが大文字であることに注意)のハンドル(C++でのポインタに対応。 ^ でハンドルであることを表す)を用意して代入している。読み込んだ文字列をその表す数値に変換するときは
数値の型::Parse(文字列)
の形で関数Parseを用いる。
出力では、
Console::WriteLine("入力値の2倍 = " + (v * 2.0).ToString());
として、2つの文字列、"入力値の2倍 = " と (v * 2.0).ToString() を演算子 + で結合したものをコンソールに書き出している。
実行例を図2に示す。
図2
コンソール画面が表示されたら、「5.0」とキーボードから打ち込んでEnterキーを押すと図2のような画面を得る。
以下では、コンソールアプリケーションの新規作成の形で、テキストファイル入出力を行うプログラムを用意する手順を説明する。
まず、Visual Studio Express 2012 for Windows Desktopを起動する。
図3
メニュ「ファイル|新しいプロジェクト」(図3)を選ぶと、図4の「新しいプロジェクト」ダイアログウィンドウが表示される。
図4
テンプレートとして「Visual C++|CLR」を選び、右のペインに表示されている項目から「CLRコンソールアプリケーション」を選ぶ。「名前」の欄に適当な名前を指定するが、これはプロジェクト名となる。デフォルトでプロジェクト名は「ソリューション名」となる。「場所」は右の「参照」ボタンをクリックして表示されるダイアログウィンドウにおいて変更・指定することができる。ここでの設定により、「場所」の名前のフォルダの下にソリューション名のフォルダが作成され、ソリューション名のフォルダの下に「名前」で指定したプロジェクト名のフォルダが作成される。プロジェクト名のフォルダ内にプログラムのソースコードファイルなどが用意される。
図4の「OK」ボタンのクリックで図5のような画面になる。
図5
プロジェクト名のソースコードファイル(図5では、SampleConsoleApp.cpp)が作成されて、プログラムの雛型が用意されている。
図5のプログラムの場合、コンソールへの出力は
Console::WriteLine(L”Hello World”);
により文字列”Hello World”の表示として行われるが、その後、直ちにreturn 0が実行されて、プログラムは実行終了となり、コンソール画面が消えてしまう。これを防ぐため、図6のように、ReadLineをreturn文の前に入れておくと、Enterキーが押されるまで、ReadLIneは入力待ちの状態となる(図7)。
図6
図7
図7の状態でEnterキーを押すと、ReadLineの実行終了となり、プログラムも終了する。
ファイルへの出力プログラム例として、図6のプログラムを図8のように書き換えたものを作成した。
図8
エディタ内のコードは以下のようになっている。
// SampleConsoleApp.cpp : メイン プロジェクト ファイルです。
#include "stdafx.h"
using namespace System;
int main(array<System::String ^> ^args)
{
using System::IO::StreamWriter;
StreamWriter ^ fout = gcnew StreamWriter("Out.txt");
int sum = 0;
for (int i = 1; i <= 10; i++){
sum += i;
fout->WriteLine("i = " + i.ToString() + " sum = " + sum.ToString());
}
fout->Close();
Console::WriteLine("計算終了");
Console::ReadLine();
return 0;
}
StreamWriterがファイル出力用のストリーム(参照クラス型)である。出力先のファイル名を、gcnewによってオブジェクトを生成するときにコンストラクタの引数として指定している。出力の仕方は、コンソールへの出力の場合と同じで、WriteLineによって行っている。書き込み終了後は、
fout->Close()
とClose関数を実行しておく。Close()を実行せずにプログラムを終了すると、ReadLineで書き出した内容がファイルにすべて転送されずにプログラムが終了することがあるので注意。
このプログラムを実行すると、図9のような画面になる。
図9
図9の画面の状態で、Enterキーを押すとプログラムは終了する。終了後、出力ファイルOut.txtを開くために、メニュ「ファイル|ファイルを開く」を選ぶ(図10)。
図10
図11の「ファイルを開く」ダイアログウィンドウが表示される。
図11
プロジェクトのフォルダSampleConsoleApp内に、プログラムファイルSampleConsoleApp.cppとともに出力ファイルOut.txtが格納されていることがわかる。Out.txtを選んで「開く」ボタンをクリックすると、図12のようにファイルの内容が表示される。
図12
次に、ファイルからの読み込みプログラムを作成する。
図4の要領でプログラムSampleTextFileRd(もちろん、他の名前でもよい)を作成する(図13)。
図13
図13のプログラムを図14のように書き換える。
図14
図14のエディタ内のコードは以下のようになっている。
Console::Write("入力ファイル = ");
String ^ flnm = Console::ReadLine();
using System::IO::StreamReader;
StreamReader ^ fin = gcnew StreamReader(flnm);
double sum = 0.0;
while(true){
String ^ s = fin->ReadLine();
if(s == nullptr) break;
Console::WriteLine(s);
sum += double::Parse(s);
}
fin->Close();
Console::WriteLine("sum = " + sum.ToString());
Console::ReadLine();
return 0;
文字列型変数(String型のハンドル)flnmに設定された名前のファイルから読み込むためのストリームのハンドルfinを
StreamReader ^ fin = gcnew StreamReader(flnm)
と用意している。finの関数ReadLineを
String ^ s = fin->ReadLine()
と呼び出して、ファイルの1行分の内容をString^型変数sに設定している。このとき、読み込み位置がファイルの最後であるときは、s にはnullptrが設定されるので、次のif文
if(s == nullptr) break
によって、読み込みのループを抜け出している。
上のプログラムを実行する前に、読み込むファイルを用意しておかなければならない。ここでは、以下の手順で作成する。
メニュ「ファイル|新しいファイル」を選ぶ(図15)。
図15
表示される「新しいファイル」ダイアログウィンドウにおいて「全般|テキストファイル」を選ぶ(図16)。
図16
「開く」ボタンをクリックすると図17のような画面になる。
図17
メニュ「ファイル|名前を付けて〜.txtを保存」を選ぶ(図18では、〜の部分はTextFile1となっている)。
図18
「名前を付けてファイルを保存」ダイアログウィンドウが表示される(図19)。
図19
保存先フォルダとしてプログラムのプロジェクトフォルダ(図19では、「ファイルの種類」が「テキストファイル(*.txt)」となっているため、プログラムファイルSampleTextFileRd.cppなどは表示されていない)を選んで、ファイル名を設定して「上書き保存」ボタンをクリックする。図20のように、設定したファイル名でテキストファイルがエディタに表示される。
図20
図21のようにデータを打ち込んで保存する。エディタの画面に書き込んだだけではファイルにはその内容が保存されないので、書き込んだ後は必ず保存する。
図21
保存後、F5キーを押してビルド・実行する。図22の画面が表示される。
図22
入力データファイルの名前を打ち込む(図23)。
図23
Enterキーを押すと、設定したファイル名が読み込まれて、ファイルからの読み込みが行われ、読み込んだデータの和が出力される(図24)。
図24
上のプログラムでは、入力ファイルには1行に1個のデータが書かれている。1行に複数のデータが書かれている場合は、1行分のデータを読み込んでから、行内のデータを1つずつ取り出すことになる。
図25に1行当たり2個のデータが書かれているファイルからの読み込みを行うプログラムを示す。
図25
エディタ画面に表示されているプログラムを以下に示す。
// SampleRead2data.cpp : メイン プロジェクト ファイルです。
#include "stdafx.h"
using namespace System;
//
// pos 番目の文字列の取り出し
//
String ^ SeekStr( String ^ str, int pos ){
String ^ vstr = "";
array<wchar_t> ^ separator = { ' ', '\t', ',',' ','/' };
array<String ^> ^ ary_str;
ary_str = str->Split( separator );
int L = ary_str->Length;
int ckp = -1;
int tmpPos = 0;
do {
if (ary_str[tmpPos]->Length > 0) ckp++;
tmpPos++;
} while ((ckp != pos) && (tmpPos < L) );
if (ckp == pos) vstr = ary_str[tmpPos-1];
return vstr;
}
int main(array<System::String ^> ^args)
{
Console::Write("入力ファイル = ");
String ^ flnm = Console::ReadLine();
using System::IO::StreamReader;
StreamReader ^ fin = gcnew StreamReader(flnm);
double sum = 0.0, sum10 = 0.0;
while(true){
String ^ s = fin->ReadLine();
if(s == nullptr) break;
Console::WriteLine(s);
sum += double::Parse(SeekStr(s, 0));
sum10 += double::Parse(SeekStr(s, 1));
}
fin->Close();
Console::WriteLine("sum = " + sum.ToString()
+ " sum10 = " + sum10.ToString());
Console::ReadLine();
return 0;
}
関数String ^ SeekStr( String ^ str, int pos )によって、文字列str内のpos番目のデータが関数値として取り出される。posは0番目から数える。先頭のデータが0番目である。データは
array<wchar_t> ^ separator = { ' ', '\t', ',',' ','/' }
の{ }内に並べられた文字で区切られているとして処理される。連続する区切り文字の間は空の文字列になるが、この空の文字列は無視される。
図26
上のプログラムを図26のデータに対して実行すると、図27のような結果を得る。
図27
関数ToStringの引数に書式を示すものをとることができる。次に、書式の例を示す。
int main(array<System::String ^> ^args)
{
double ve = Math::Exp(1.0);
Console::WriteLine(ve.ToString());
Console::WriteLine(ve.ToString("F7"));
Console::WriteLine(ve.ToString()->PadLeft(30));
Console::WriteLine(ve.ToString()->PadRight(25) + "です");
Console::WriteLine(ve.ToString("E5"));
Console::ReadLine();
return 0;
}
上のプログラムを実行すると図28の結果を得る。
図28
ToString(文字列)における文字列が
“Fn”
であれば、小数点以下n桁で出力される。
“En”
であれば、n桁の指数表記で出力される。
PadLeft(m)
は、出力において左に空白文字を補って出力文字列の長さがmであるようにする。
PadRight(m)
は、出力において右に空白文字を補って出力文字列の長さがmであるようにする。
なお、ファイル入出力で日本語を扱うときは
StreamReader ^ fin = gcnew StreamReader(flnm, System::Text::Encoding::GetEncoding("shift-jis"));
とか
StreamWriter ^ fout = gcnew StreamWriter("Out.txt", false, System::Text::Encoding::GetEncoding("shift-jis"));
というように、
System::Text::Encoding::GetEncoding("shift-jis")
を第2パラメータ(StreamReaderの場合)、あるいは第3パラメータ(StreamWriterの場合、第2パラメータfalseは既存のファイルがあるときは上書きを指定するものである)におく。
標準C++準拠の場合
テキストファイル入出力はいろいろな方法が可能であるが、簡単な方法として以下の方法がある。なお、テキストファイル入出力については<テキストファイル入出力:C++/Linuxプログラミング>で詳しく説明している。
まず、プログラムの新規作成を以下の手順で行う。
Visual Studio Express 2012 for Windows Desktopを起動して、メニュ「ファイル|新しいプロジェクト」を選ぶ(図29)。
図29
表示された「新しいプロジェクト」ダイアログウィンドウにおいて、「テンプレート|Visual C++|Win32」を選び、「Win32コンソールアプリケーション」を選ぶ(図30)。
図30
「場所」は、「参照」ボタンをクリックすると表示されるダイアログウィンドウで設定することができる。「名前」は、プロジェクトの名前を適当に設定する。これらの設定後、「OK」ボタンをクリックすると図31の「Win32 アプリケーションウィザード」ダイアログウィンドウが表示される。
図31
「完了」ボタンのクリックで、図32のようにWin32プログラムの雛型が用意されたエディタ画面が表示される。Visual C++が用意する雛型を用いない方法については<Threadプログラミング/C++11/VC2013RC>で説明している。
図32
エディタ画面に表示されている図32の雛型を図33のように書き換える。
図33
図33のコードは以下のようになっている。
// StdCppRdWrTextFile.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//
#include "stdafx.h"
#include <iostream>
#include <fstream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
cout << "Input Data File = ";
char finnm[100];
cin >> finnm;
ifstream fin;
fin.open( finnm );
cout << "Output File = ";
char foutnm[100];
cin >> foutnm;
ofstream fout;
fout.open( foutnm );
double sum = 0.0, sum10 = 0.0;
while( ! fin.eof() ){
double v, v1;
fin >> v >> v1;
cout << v << " " << v1 << endl;
sum += v;
sum10 += v1;
fout << v << " " << v1 << endl;
}
fin.close();
fout << sum << " " << sum10 << endl;
char c;
cin >> c;
return 0;
}
データを図34のように用意する。
図34
図34のデータに対して図33のプログラムを実行すると、図35のようになる。
図35
図35の状態で何か文字キー(例えば、’A’キー)を押してからEnterキーを押すと c への入力文
cin >> c;
が終了して、プログラムも終了する。
プログラムの終了後、図35で指定した出力ファイルResult.txtをエディタで開くと、図36のようになっている。
図36