数値をファイルへ出力し、ファイルから数値を読む(ファイル出入力)

#はじめに
ファイル出入力の練習に作成しました。
元々は素数で`const int[]`配列を作成し、`.hpp`ファイルとして出力するプログラムだったのですが、どう考えてもメモリの使い過ぎになるので、ファイルに数値だけを列挙して後から一個ずつ使える感じに書き直しました。

#実装
##使い方の説明

```cpp
std::string file_name;
Enumeration e(0, 10, "file");
file_name = e.MakeDat(kPrime);
```

`Enumeration`クラスではファイルへの数値の列挙を行います。
コンストラクタで列挙する数値の範囲(ここでは`0 <= 10`)と出力するファイル名を指定し、
`MakeDat`で`.dat`ファイルを出力します。引数は出力する数値の種類を表します。
戻り値は完全な出力したファイル名(`file.dat`)です。

結果として以下のようなファイルが出力されます

```:file.dat
2
3
5
7
```

このように`0 <= 10`の範囲内の素数をファイルへと出力します。

```cpp
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行

```cpp:NumberEnumeration.hpp
// 数値を列挙する
/*
 * 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行

```cpp:NumberEnumeration.cpp
#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`が完成したら削除します。
- 素数判定のプログラムは[こちら](https://qiita.com/asksaito/items/76b71602dd956b79dbf7)を参考にしました。

##FileArray
###hppファイル
45行

```cpp:FileArray.hpp
// 数値が列挙されたファイルを開いて使用する
/*
 * 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行

```cpp:FileArray.cpp
#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行

```cpp:Main.cpp
#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

```: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++ スタイルガイド](https://ttsuki.github.io/styleguide/cppguide.ja.html#Unnamed_Namespaces_and_Static_Variables)を参考にしました。

無名名前空間は`.h`に置いてはいけないとGoogle C++ スタイルガイドには書いてあります。色々考えたら全く持ってその通りですね。
ヘッダをインクルードしたら使えるようになる定数は名前が被るようなことが無ければ無名名前空間を使うのが楽そうとか考えてましたが、名前が被ったら想像が出来ませんが多分やばいと思います。

今回の場合は変に名前空間のスコープを深くするより分かりやすいと思うのでこのままおいておこうと思います。

ファイルの削除は`C++17`からの機能のようで、これを使えるようにするために環境の再構築だのなんだの大変でした。[こちらの質問](https://qiita.com/takey/questions/87d7a7d0f006c3d3ef36)と[こちらの記事](https://qiita.com/spiegel-im-spiegel/items/ba4e8d2418bdfe0c8049)に感謝です。

ちなみに`C++17`が必要なのは`#include <filesystem>`と`std::filesystem::remove(kTemp);`の部分だけなのでここを消せば`temp.dat`は残りますが、`C++17`より前の環境でも動きます。