UECアドベントカレンダーの11日目の記事です。
思い付きで書いた短い記事ですいません。
昨日、C++からCSVファイルを1行ずつ読み取るプログラムを書きました。
しかし、よくよく考えてみると、ファイルを開く処理や閉じる処理、読み取り用バッファの準備はいかなるCSVファイルでも共通して必要な処理なので、クラス化した方がいいと思い、クラスにまとめてみました。
下の例のように、クラス化する前と後で、CSVファイルを出力するためのmain関数がだいぶ簡略化できることが分かります。
クラス化前
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;
}
}
クラス化後
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言語を使っているところが若干気になりますが・・・。許してください。
#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行あたりの最大文字数
- 1行あたりの最大文字数
1行あたりの最大文字数を省略した場合、適当なサイズのバッファ(上記だと4096文字)を作成しています。
ファイル名を指定した場合、読み込みを行うようになっています。
open
ファイルを開く関数です。引数としてstring型の文字列を指定すると、ファイルを開きます。
コンストラクタで既に開いている場合は不要です。
close
ファイルを閉じる関数です。
loadnextline
1行分のセルを読み取る関数です。
vector<string>
型で、各列に分解したものを返します。
eof
ファイルが読み込み可能かどうかを判定する関数です。falseであれば読み込めます。
setcomma
区切り文字を変更します。タブ区切りなどにしたくなった場合にどうぞ。
loadall
CSVファイルを一括で読み取ります。loadnextline
とは併用しないでください。
vector<vector<string>>
型で、CSVファイル全体を返します。
ディスコンストラクタ
ファイルを閉じ、バッファを解放します。