使用 fstream 读取 csv,并且只阅读 fstream

Reading csv with fstream, and only fstream

本文关键字:fstream 读取 csv 使用      更新时间:2023-10-16

我已经看到了关于这个问题的其他答案,但它们都处理一个std::stringstream,或者一个临时charstd::string数组,各种其他类型的外部库,但我想尝试只使用fstream标头,尝试读取一个只有数字的文件,charshort, 与float一起,以逗号分隔,形成多行文字;有些可能是数组或向量。例:

1,1.1,11.1,11
2,2.2,22.2,22
3,3.3,33.3,33
...

顺序是已知的,因为每行都遵循struct中的变量。行数可能会有所不同,但是,就目前而言,让我们假设它也已知。同样为了举例起见,让我们只考虑这个顺序,以及这些类型:

int, double, double, int

根据我看到的一段代码,我尝试了这种简单(而且很可能是幼稚)的方法:

int a, d;
double b, c;
char fileName {"file.txt"};
std::fstream fs {fileName};
if(!fs.is_open())
// open with fs.out, write some defaults; this works, no need to mention
else
{
char comma;
while(fs.getline(fileName, 100, 'n'))
{
fs >> a >> comma >> b >> comma >> c >> comma >> d;
std::cout << 2*a << ", " << 2*b << ", " << 2*c << ", " << 2*d << 'n';
}
}

如果文件有上面的三行,加上一个终止n,它输出这个:

4, 4.4, 44.4, 44
6, 6.6, 66.6, 66
6, 6.6, 66.6, 66
*** stack smashing detected ***: <unknown> terminated
Aborted (core dumped)

如果我在文件开头添加一个n,它会输出:

2, 2.2, 22.2, 22
4, 4.4, 44.4, 44
6, 6.6, 66.6, 66
6, 6.6, 66.6, 66

如果我删除最后一个n,它会按预期工作。我有几个问题:

  1. 在写入文件时,除了添加起始n而不是插入终止文件以按预期工作之外,我还能做什么?

  2. 如果变量的数量更长,比如每行 100 个,我该怎么做才能避免"带着fs >> a >> c >> ...绕地球一周"?

  3. 如果我只需要阅读一行特定的行,或者只需要阅读几行,一种方法可能是以某种方式计算n或行的出现次数。我该怎么做?

(编辑)

  1. 最后,正如标题所提到的,是否可以在不涉及其他标头的情况下做到这一点,而只能使用fstream(例如,目前如此)?

顺序是已知的,因为每一行都跟着来自 结构。行数可能会有所不同,但是,现在,让我们假设它是 也知道。同样为了示例,让我们只考虑这个 顺序,以及这些类型:

int, double, double, int

如果字段的数量和顺序已知,则可以根据需要使用',''n'分隔符,简单地使用>>getline进行读取。虽然使用面向行的输入来读取整行,然后stringstream解析字段要明智得多,但没有理由不能只利用fstream来做同样的事情,因为你已经指出了你的目标。它不是一个优雅的解决方案,但仍然是一个有效的解决方案。

使用>>运算符

您的数据有 4 个字段,前 3 个由comma分隔,最后一个由newline分隔。您可以简单地连续循环并使用>>运算符读取,并在每次读取后测试fail()eof(),例如

#include <iostream>
#include <fstream>
#define NFIELD 4
#define MAXW 128
int main (int argc, char **argv) {
int a, d;
double b, c;
char comma;
std::fstream f (argv[1]);
if (!f.is_open()) {
std::cerr << "error: file open failed " << argv[1] << ".n";
return 1;
}
for (;;) {          /* loop continually */
f >> a >> comma >> b >> comma >> c >> comma >> d;
if (f.fail() || f.eof())   
break;
std::cout << 2*a << "," << 2*b << "," << 2*c << "," << 2*d << 'n';
f.ignore (MAXW, 'n');
}
f.close();
}

保持一个简单的字段计数器n,您可以使用基于字段编号的简单switch语句将正确的值读入相应的变量中,并在读取所有字段时输出(或以其他方式存储)构成结构的所有 4 个值。(显然,您也可以在阅读每个成员时填写它们)。不需要任何特殊情况,例如

#include <iostream>
#include <fstream>
#define NFIELD 4
int main (int argc, char **argv) {
int a, d, n = 0;
double b, c;
char comma;
std::fstream f (argv[1]);
if (!f.is_open()) {
std::cerr << "error: file open failed " << argv[1] << ".n";
return 1;
}
for (;;) {          /* loop continually */
switch (n) {    /* coordinate read based on field number */
case 0: f >> a >> comma; if (f.eof()) goto done; break;
case 1: f >> b >> comma; if (f.eof()) goto done; break;
case 2: f >> c >> comma; if (f.eof()) goto done; break;
case 3: f >> d; if (f.eof()) goto done; break;
}
if (++n == NFIELD) {    /* if all fields read */
std::cout << 2*a << "," << 2*b << "," << 2*c << "," << 2*d << 'n';
n = 0;      /* reset field number */
}
}
done:;
f.close();
}

示例输入文件

使用您提供的示例输入。

$ cat dat/mixed.csv
1,1.1,11.1,11
2,2.2,22.2,22
3,3.3,33.3,33

示例使用/输出

您只需将输出上的每个字段加倍即可获得所需的输出:

$ ./bin/csv_mixed_read dat/mixed.csv
2,2.2,22.2,22
4,4.4,44.4,44
6,6.6,66.6,66

(以上两者的输出相同)

使用getline',''n'分隔

您可以使用逻辑上的细微变化来使用getline。在这里,您读取带有f.getline(buf, MAXC, ',')的前 3 个字段,当找到第 3 个字段时,您使用f.getline(buf, MAXC)读取最后一个字段。例如

#include <iostream>
#include <fstream>
#define NFIELD  4
#define MAXC  128
int main (int argc, char **argv) {
int a = 0, d = 0, n = 0;
double b = 0.0, c = 0.0;
char buf[MAXC];
std::fstream f (argv[1]);
if (!f.is_open()) {
std::cerr << "error: file open failed " << argv[1] << ".n";
return 1;
}
while (f.getline(buf, MAXC, ',')) { /* read each field */
switch (n) {    /* coordinate read based on field number */
case 0: a = std::stoi (buf); break;
case 1: b = std::stod (buf); break;
case 2: c = std::stod (buf); 
if (!f.getline(buf, MAXC))  /* read d with 'n' delimiter */
goto done;
d = std::stoi (buf);
break;
}
if (++n == NFIELD - 1) {    /* if all fields read */
std::cout << 2*a << "," << 2*b << "," << 2*c << "," << 2*d << 'n';
n = 0;      /* reset field number */
}
}
done:;
f.close();
}

(注意:与使用>>运算符不同,当如上所述使用getline时,每个comma后面不能有空格

示例使用/输出

输出是相同的。

$ ./bin/csv_mixed_read2 dat/mixed.csv
2,2.2,22.2,22
4,4.4,44.4,44
6,6.6,66.6,66

无论您是使用类似上面示例的内容,还是stringstream,您都必须知道字段的数量和顺序。无论您使用循环和if..else if..else还是switch逻辑都是相同的。您需要某种方式将读取与正确的字段进行协调。保持一个简单的字段计数器与其他任何事情一样简单。仔细查看,如果您有其他问题,请告诉我。