#はじめに
ファイル出入力の練習に作成しました。
元々は素数でconst int[]
配列を作成し、.hpp
ファイルとして出力するプログラムだったのですが、どう考えてもメモリの使い過ぎになるので、ファイルに数値だけを列挙して後から一個ずつ使える感じに書き直しました。
#実装
##使い方の説明
std::string file_name;
Enumeration e(0, 10, "file");
file_name = e.MakeDat(kPrime);
Enumeration
クラスではファイルへの数値の列挙を行います。
コンストラクタで列挙する数値の範囲(ここでは0 <= 10
)と出力するファイル名を指定し、
MakeDat
で.dat
ファイルを出力します。引数は出力する数値の種類を表します。
戻り値は完全な出力したファイル名(file.dat
)です。
結果として以下のようなファイルが出力されます
2
3
5
7
このように0 <= 10
の範囲内の素数をファイルへと出力します。
FileArray f(file_name);
while(f.IsEOF() == false)
std::cout << f.GetNumber() << std::endl;
FileArray
クラスではファイルから数値を読みだす作業を行います。
コンストラクタで読みだすファイル名を指定します。
IsEOF
は名前の通りEOFかの判定、GetNumber
はファイルから数値を一つ取得します。
出力は以下のようになります
2
3
5
7
##NumberEnumeration
###hppファイル
50行
// 数値を列挙する
/*
* Enumeratoinクラス
* 数値をファイルへ列挙します
*
* 使用可能なもの
*----- コンストラクタ -----*
* Enumeration(min, max, file); // 範囲とファイル名を定義します (min <= max) file_name
* Enumeration(); // 0 <= 0 "a"
*----- 定数 -----*
* kPrime // 関数の引数 - 素数を列挙することを示すフラグです
* kInteger // 関数の引数 - 整数を列挙することを示すフラグです
*----- 関数 -----*
* std::string MakeDat(flag); // 数値を列挙してfile.datに出力します。
* // 戻り値は出力したファイル名です。
*/
#ifndef NUMBER_ENUMERATION_HPP
#define NUMBER_ENUMERATION_HPP
#include <iostream>
#include <string>
namespace file_io{
class Enumeration{
// 定数
private:
static constexpr int kPrime = 0;
static constexpr int kInteger = 1;
// 変数
private:
int min_value; // 列挙する数値の最小値
int max_value; // 列挙する数値の最大値
int count; // 要素数
std::string file_name; // ファイル名
bool IsPrime(const int); // 素数判定
public:
Enumeration():
min_value(0), max_value(0), count(0),file_name("a"){}
Enumeration(const int n, const int m, const std::string f):
min_value(n), max_value(m), count(0), file_name(f){}
// 数字を列挙したファイルを作成する 引数:フラグ 戻り値:ファイル名
std::string MakeDat(const int);
};
}
namespace{
constexpr int kPrime = 0;
constexpr int kInteger = 1;
}
#endif /* NUMBER_ENUMERATION_HPP */
###cppファイル
69行
#include "NumberEnumeration.hpp"
#include <iostream>
#include <string>
#include <math.h>
#include <fstream>
#include <filesystem>
namespace file_io{
// 素数判定
bool Enumeration::IsPrime(const int number)
{
if(number < 2){ return false; }
if(number == 2){ return true; }
if(number % 2 == 0){ return false; }
double sqrt_number = std::sqrt(number);
for(int i = 3; i <= sqrt_number; i += 2)
{
if(number % i == 0){ return false; }
}
return true;
}
// .datファイル作成
std::string Enumeration::MakeDat(const int flag)
{
const std::string kTemp = "temp.dat";
std::ofstream temp(kTemp, std::ios::out|std::ios::trunc);
if(temp.fail()){
std::cerr << "Something wrong occurred in " << kTemp << std::endl;
exit(8);
}
for(int i = min_value; i <= max_value; ++i)
{
if((flag == kPrime) && IsPrime(i))
{
temp << std::to_string(i) << std::endl;
++count;
}
if(flag == kInteger)
{
temp << std::to_string(i) << std::endl;
++count;
}
}
temp.close();
std::string line;
std::ofstream out_file(file_name + ".dat", std::ios::out|std::ios::trunc);
std::ifstream in_file(kTemp, std::ios::in);
if(out_file.fail() || in_file.fail()){
std::cerr << "Something wrong occurred" << std::endl;
exit(8);
}
for(int i = 0; i < count; ++i)
{
std::getline(in_file, line);
if(i != count - 1){
out_file << line << std::endl;
}
else{
line = line.substr(0, line.length());
out_file << line;
}
}
in_file.close();
std::filesystem::remove(kTemp);
return (file_name + ".dat");
}
}
####簡単な説明
- 無名名前空間内の定数は
Main
からフラグを指定するときに使います。 -
ofstream
はファイルのクローズはデストラクタでやってくれますが、途中ifstream
でファイルをオープンするので一度ファイルを閉じています。 - 初めに
temp.dat
に出力をしてから改めてout_file
を作成するのは最後の数値の後に改行が入らないようにする為です。これがないとFileArray
で出力した時に
2
3
5
7
0
と、末尾に0
がくっついてしまいます(多分'\0'
の0
だと思います)。
-
temp.dat
はout_file
が完成したら削除します。 - 素数判定のプログラムはこちらを参考にしました。
##FileArray
###hppファイル
45行
// 数値が列挙されたファイルを開いて使用する
/*
* FileArrayクラス
* 数値が列挙された.datファイルを扱います
* 使用可能なもの
*----- コンストラクタ -----*
* FileArray(file); // fileファイルをオープンします
* FileArray(); // "a.dat"ファイルをオープンします
*----- デストラクタ -----*
* ~FileArray(); // ファイルをクローズします。
*----- 関数 -----*
* int GetNumber(); // ファイルから数値を一つ読み取ります
* bool IsEOF(); // ファイルの終端かを判定します
*/
#ifndef FILE_ARRAY_HPP
#define FILE_ARRAY_HPP
#include <iostream>
#include <string>
#include <fstream>
namespace file_io{
class FileArray{
private:
std::ifstream file; // オープンするファイル
public:
FileArray():
file("a"){
if(file.fail()){
std::cerr << "Unable to open a" << std::endl;
exit(8);
}
}
FileArray(std::string f):
file(f){
if(file.fail()){
std::cerr << "Unable to open " << f << std::endl;
exit(8);
}
}
~FileArray(){ file.close(); }
int GetNumber(); // ファイルから数値を一つ読みとる
bool IsEOF(); // EOFか
};
}
#endif /* FILE_ARRAY_HPP */
###cppファイル
16行
#include "FileArray.hpp"
#include <iostream>
#include <string>
namespace file_io{
int FileArray::GetNumber()
{
int temp;
file >> temp;
return temp;
}
bool FileArray::IsEOF()
{
return file.eof();
}
}
##Main
###cppファイル
15行
#include "NumberEnumeration.hpp"
#include "FileArray.hpp"
#include <iostream>
#include <string>
using namespace file_io;
int main()
{
Enumeration e(0, 100, "file");
FileArray f(e.MakeDat(kPrime));
while(f.IsEOF() == false){
std::cout << f.GetNumber() << std::endl;
}
return 0;
}
#テスト
##file.dat
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
##実行結果(GCC 10.3.0)
E:>g++ -std=c++17 -o Main.exe Main.cpp NumberEnumeration.cpp FileArray.cpp
E:>Main.exe
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
#あとがき
"ファイル出力の最後の改行をどうしたら消せるのか問題"は初めの出力で要素数を取得して、後からコピーするときに要素数に従って改行文字を消す場所を探す方法を取ることで解決出来ました。単純な数列ならまだしも素数を並べるとなるとどこで終わるかが事前にわかりそうにないのでこれくらいしか思いつかなかったです。
変数だとかの名前の付け方はGoogle C++ スタイルガイドを参考にしました。
無名名前空間は.h
に置いてはいけないとGoogle C++ スタイルガイドには書いてあります。色々考えたら全く持ってその通りですね。
ヘッダをインクルードしたら使えるようになる定数は名前が被るようなことが無ければ無名名前空間を使うのが楽そうとか考えてましたが、名前が被ったら想像が出来ませんが多分やばいと思います。
今回の場合は変に名前空間のスコープを深くするより分かりやすいと思うのでこのままおいておこうと思います。
ファイルの削除はC++17
からの機能のようで、これを使えるようにするために環境の再構築だのなんだの大変でした。こちらの質問とこちらの記事に感謝です。
ちなみにC++17
が必要なのは#include <filesystem>
とstd::filesystem::remove(kTemp);
の部分だけなのでここを消せばtemp.dat
は残りますが、C++17
より前の環境でも動きます。