0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

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

#実装
##使い方の説明

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の範囲内の素数をファイルへと出力します。

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行

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行

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.datout_fileが完成したら削除します。
  • 素数判定のプログラムはこちらを参考にしました。

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

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行

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行

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++ スタイルガイドを参考にしました。

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

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

ファイルの削除はC++17からの機能のようで、これを使えるようにするために環境の再構築だのなんだの大変でした。こちらの質問こちらの記事に感謝です。

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?