C++からCSVファイルを読み取る（クラス化）

UECアドベントカレンダーの11日目の記事です。
思い付きで書いた短い記事ですいません。

昨日、[C++からCSVファイルを1行ずつ読み取るプログラム](https://qiita.com/takoyaki3/items/34677decc70f2f14ef62)を書きました。
しかし、よくよく考えてみると、ファイルを開く処理や閉じる処理、読み取り用バッファの準備はいかなるCSVファイルでも共通して必要な処理なので、クラス化した方がいいと思い、クラスにまとめてみました。
下の例のように、クラス化する前と後で、CSVファイルを出力するためのmain関数がだいぶ簡略化できることが分かります。

#### クラス化前

```loadcsv.cpp
int main() {
    FILE* fp = fopen("data.csv", "r");
    if (fp == NULL) return 1;
    while (!feof(fp)) {
        char line[MaxLine];
        fgets(line, MaxLine, fp);
        CellsTy cells = cells_split(line, ',');
        if (cells.size() == 0) continue;

        // セルの出力処理
        for (string cell : cells) {
            cout << cell << "   ";
        }
        cout << endl;
    }
}
```

#### クラス化後

```loadcsv.cpp
int main() {
	mycsv csv("data.csv");
	while (!csv.eof())
	{
		vector<string> cells = csv.loadnextline();
        // セルの出力処理
        for (string cell : cells) {
            cout << cell << "   ";
        }
		cout << endl;
	}
}
```

クラス化前と比べると、かなり読みやすくなったかと思います。

## CSVファイルをC++で読み取るためのクラス

それでは、本題のコードです。
ファイルの入力にC言語を使っているところが若干気になりますが・・・。許してください。

```loadcsv.cpp
#include <string>
#include <vector>
#include <iostream>
#include <cstdio>

using namespace std;

class mycsv {
private:
	FILE* fp;
	int bufsize;
	char* line;
	char comma;
	void init() {
		fp = NULL;
		line = new char[bufsize];
		comma = ',';
	}
public:
	// set comma charcter
	void setcomma(char c) {
		comma = c;
	}
	// open csv file
	bool open(string filename) {
		if (fp != NULL) fclose(fp);
		fp = fopen(filename.c_str(), "r");
		if (fp == NULL) return false;
		return true;
	}
	// close csv file
	void close() {
		if (fp != NULL) fclose(fp);
		fp = NULL;
	}
	vector<string> loadnextline() {
		memset(line, bufsize, sizeof(char) * bufsize);
		fgets(line, bufsize, fp);

		size_t len = strlen(line);
		line[len - 1] = comma;
		line[len] = '\0';

		vector<string> cells;
		string l;

		for (int i = 0; i < len; i++) {
			if (line[i] == '\r') continue;
			if (line[i] == comma) {
				cells.push_back(l);
				l.clear();
				continue;
			}
			l.push_back(line[i]);
		}
		return cells;
	}
	bool eof() {
		if (fp == NULL) return true;
		return feof(fp);
	}
	vector<vector<string>> loadall() {
		vector<vector<string>> ans;
		while (!eof())
		{
			ans.push_back(loadnextline());
		}
		return ans;
	}
	mycsv(string filename, int linemax = 4096) {
		bufsize = linemax;
		init();
		open(filename);
	}
	mycsv(int linemax) {
		bufsize = linemax;
		init();
	}
	~mycsv() {
		delete[] line;
		if (fp == NULL) return;
		fclose(fp);
		fp = NULL;
	}
};

// ここから下は使用例
int main() {
	mycsv csv("data.csv");
	while (!csv.eof())
	{
		vector<string> cells = csv.loadnextline();
		for (int i = 0; i < cells.size(); i++) {
			cout << cells[i] << "	";
		}
		cout << endl;
	}
}
```

## メンバ関数

|関数名|役割|
|---|---|
|open|ファイルを開く|
|close|ファイルを閉じる|
|loadnextline|1行読み込む|
|eof|ファイルの終了まで読み込んだか否か|
|setcomma|区切り文字の変更|
|loadall|csvファイルをすべて読み込み|
|mycsv|コンストラクタ|
|~mycsv|ディスコンストラクタ|

### コンストラクタ
コンストラクタは、次の3通りを作成しました。

- 引数なし
- ファイル名、１行あたりの最大文字数
- １行あたりの最大文字数

1行あたりの最大文字数を省略した場合、適当なサイズのバッファ（上記だと4096文字）を作成しています。
ファイル名を指定した場合、読み込みを行うようになっています。

### open
ファイルを開く関数です。引数としてstring型の文字列を指定すると、ファイルを開きます。
コンストラクタで既に開いている場合は不要です。

### close
ファイルを閉じる関数です。

### loadnextline
1行分のセルを読み取る関数です。
``vector<string>``型で、各列に分解したものを返します。

### eof
ファイルが読み込み可能かどうかを判定する関数です。falseであれば読み込めます。

### setcomma
区切り文字を変更します。タブ区切りなどにしたくなった場合にどうぞ。

### loadall
CSVファイルを一括で読み取ります。``loadnextline``とは併用しないでください。
``vector<vector<string>>``型で、CSVファイル全体を返します。

### ディスコンストラクタ
ファイルを閉じ、バッファを解放します。