【问题标题】:Reading csv with fstream, and only fstream用 fstream 读取 csv,而且只有 fstream
【发布时间】:2018-04-08 18:12:06
【问题描述】:

我在这个问题上看到了其他答案,但他们都处理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 &gt;&gt; a &gt;&gt; c &gt;&gt; ... 绕地球一圈?

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

(编辑)

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

【问题讨论】:

  • 您可能想再次检查您的代码。这真的没有意义。
  • @FeiXiang 谢谢你让我知道。我看到我在fs &gt;&gt; ... 行中颠倒了两个&lt;&lt;。还有什么我想念的吗? (不要编辑太多次)
  • 有一些帖子改变了语言环境。搜索它
  • 解析csv文件c++。使用该搜索字符串,您应该会偶然发现具有 csv_classification 结构的答案。
  • 您试图提取一行并将其放入名为fileName 的字符数组中,基本上丢弃一行,因为您从未读取过fileName

标签: c++ fstream


【解决方案1】:

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

int, double, double, int

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

使用&gt;&gt; 运算符

您的数据有 4 个字段,前 3 个由 comma 分隔,最后一个由 newline 分隔。您可以简单地连续循环并使用&gt;&gt; 运算符读取并在每次读取后测试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();
}

(注意:不像使用&gt;&gt;操作符,当使用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,逻辑都是相同的。您需要某种方式来协调您的阅读与正确的领域。保持一个简单的字段计数器与其他任何事情一样简单。如果您还有其他问题,请仔细查看并告诉我。

【讨论】:

  • 我的目的是为struct 生成一个保存文件,保存输入数据和其他各种设置。有不同的主题,每个主题都需要相同的struct。我可以强化写作规则,例如 csv,我可以确定要保存多少行(主题数),即使它会及时填满,因为每次使用后都会刷新。这就是为什么我说它将具有已知数量的行和(有序)值。我发现第一个例子非常吸引人。我在 cppreference 上没有看到,但也有 eol() 吗?或者也许是一种计算\n 的方法(我可能不得不使用char)?
  • 没有eol(),但f.getline (NCHARS, '\n') 允许进行相同的测试。您始终可以在第一个示例中添加一个简单的int n = 0;,并在每个循环结束时将其递增。那将为您计算行数。搏一搏。如果您有任何问题,请发表另一条评论,我很乐意为您提供帮助。
  • 很可能我会使用所有的行,并用默认值填充未使用的行。为了找到某一行,我想在每行的开头有一个带有{"one", "two", ...} 的字符串,但我认为必须检查字符串会很麻烦。或者,查看 cppreference fstream.ignore(),我发现我可以使用它来确定 \n 的出现,并使用循环创建一个单独的函数。也许不漂亮,但工作(现在)。 (刚看到你的评论)谢谢你的帮助。我会将此标记为答案,但我可能会返回 cmets。 :-)
  • 当然,这行得通,但无论何时您可以选择使用简单的计数器 - 或使用其他函数 - 采用计数器路由并避免单独函数调用的开销。 (它是最小的,但可以在大型应用程序中加起来......)此外,而不是ignore 中的某个数字NCHARS,正确的常量是包含&lt;limits&gt; 并使用std::numeric_limits&lt;int&gt;::max()(即INT_MAX ) 表示要忽略的字符数。
  • 那么我认为一个不错的方法是将它们结合起来。我可以计算字符直到\n,这将是我的MAXC,直接在变量中使用它来getline()&gt;&gt;。如果我只需要在某些行上执行操作,我可以基于enum 甚至是字符串数组跳到不同的\n。也许我什至可以在没有中间char 数组或stringstream 的帮助下做到这一点,就像你的例子一样。所以我可以使用循环避免switch。它开始看起来更好了,我以为我会因为敢于提出这样的问题而被活剥皮,这对“书籍”来说是这样的。
猜你喜欢
  • 2013-05-29
  • 1970-01-01
  • 1970-01-01
  • 2018-04-03
  • 1970-01-01
  • 2011-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多