【问题标题】:Read file with separator a space and a semicolon读取文件,分隔符为空格和分号
【发布时间】:2015-07-08 12:17:58
【问题描述】:

我写这个是为了解析一个带有数字的文件,其中分隔符只是一个空格。我的目标是读取文件的每个数字并将其存储在矩阵A 的相应索引中。所以,读到的第一个号码应该转到A[0][0],第二个号码应该转到A[0][1],以此类推。

#include <iostream>
#include <string>
#include <fstream>

using namespace std;

int main() {
    const int N = 5, M = 5;
    double A[N*M];
    string fname("test_problem.txt");
    ifstream file(fname.c_str());
    for (int r = 0; r < N; ++r) {
        for (int c = 0; c < M; ++c) {
            file >> *(A + N*c + r);
        }
    }

    for (int r = 0; r < N; ++r) {
        for (int c = 0; c < M; ++c) {
            cout << *(A + N*c + r) << " ";
        }
        cout << "\n";
    }
    cout << endl;

    return 0;
}

现在,我正在尝试解析这样的文件:

1 ;2 ;3 ;4 ;5
10 ;20 ;30 ;40 ;50
0.1 ;0.2 ;0.3 ;0.4 ;0.5
11 ;21 ;31 ;41 ;5
1 ;2 ;3 ;4 ;534

但它会打印(因此读取)垃圾。我该怎么办?


编辑

这是我在 C 中的尝试,但也失败了:

FILE* fp = fopen("test_problem.txt", "r");
double v = -1.0;
while (fscanf(fp, "%f ;", &v) == 1) {
    std::cout << v << std::endl;
}

-1 将始终被打印出来。

【问题讨论】:

  • 所有*(A + N*c + r)都可以改成A[N*c + r]
  • 你需要提供一个更好的测试用例来准确地说明你想要做什么。由于目前正在编写问题,我可以编写一个程序,读取每行中的每三个字符,我猜这不是你想要的。
  • timrau,是的,但这应该不是问题。 @RedRoboHood 我编辑了我的目标。我认为测试用例很好,我想用文件的数字填充矩阵。
  • @gsamaras 测试用例的问题在于它使每个数字看起来都将是相同的宽度,或者更糟糕的是,每个数字都将是一个字符。这是真的吗?
  • 矩阵中的空格和分号似乎没有被您的代码处理。也许在第一个(for int c)语句中使用 c+=3 而不是 ++c

标签: c++ file fstream text-parsing


【解决方案1】:

您的 C 示例的问题:

warning: format ‘%f’ expects argument of type ‘float*’, but
         argument 3 has type ‘double*’ [-Wformat=]

无论何时何地,打开警告 (-Wall -Wextra) 并进行更多错误检查。

无论如何,要将fscanf 转换为double,您需要%lf 而不是%f

【讨论】:

  • 哦,有道理。是的,你是对的,我只是做了一个小例子,并没有启用标志。
【解决方案2】:

鉴于您的输入格式...

1 ;2 ;3 ;4 ;5

...你的代码...

for (int c = 0; c < M; ++c) {
    file >> *(A + N*c + r);
}

...将“吃掉”第一个数值,然后阻塞第一个 ; 分隔符。最简单的修正是......

char expected_semicolon;

for (int c = 0; c < M; ++c) {
    if (c) {
        file >> expected_semicolon;
        assert(expected_semicolon == ';'); // if care + #include <cassert>
    }
    file >> *(A + N*c + r);
}

无论如何,我建议添加更好的错误检查...

if (std::ifstream file(fname))
{
    ...use file stream...
}
else
{
    std::cerr << "oops\n";
    throw or exit(1);
}

...作为打开文件流的一般做法。

对于循环获取数据,使用支持宏来提供类似断言的样式适用于流:

#define CHECK(CONDITION, MESSAGE) \
    do { \
        if (!(CONDITION)) { \
            std::ostringstream oss; \
            oss << __FILE__ << ':' << __LINE __ \
                << " CHECK FAILED: " << #CONDITION \
                << "; " << MESSAGE; \
            throw std::runtime_error(oss.str()); \
    } while (false)

...

for (int c = 0; c < M; ++c) {
    if (c)
        CHECK(file >> expected_semicolon &&
              expected_semicolon == ';',
              "values should be separated by semicolons");
    CHECK(file >> *(A + N*c + r), "expected a numeric value");
}

对于这个特定的输入解析,对于生产系统,您可能希望使用getline,这样您就可以知道您在输入中的位置...

size_t lineNum = 0;
std::string my_string;
for (int r = 0; r < N; ++r) {
    CHECK(getline(file, my_string), "unexpect EOF in input");
    ++lineNum;
    std::istringstream iss(my_string);
        for (int c = 0; c < M; ++c) {
            if (c)
                CHECK(file >> expected_semicolon &&
                      expected_semicolon == ';',
                      "unexpected char '" << c 
                      << "' when semicolon separator needed on line "
                      << lineNum);
            CHECK(iss >> *(A + N*c + r),
                  "non numeric value encountered on line " << lineNum);
        }
    }
 }

【讨论】:

  • 我同意 Tony,但是,断言会被触发,因为您需要 assert(expected_semicolon == ';');,它应该是 if 语句主体的一部分。
  • @gsamaras:该死的,我经常称这些变量为c - 考虑更改它,因为您的代码已经在使用c,但只在一个地方......也需要在流中保持断言......干杯。
  • 我在@TonyD 发表评论之前加入并更改了它。希望我没有混淆。考虑到 9 分钟的延迟,我认为托尼已经走了 :-) ......而且我没有完全解决它!
  • ... 我会在每个&gt;&gt; 操作之后添加一个assert(file)。并且在打开文件后也立即。 (尽管有些人认为assert 是检查此类错误的错误方法)
  • @AaronMcDaid:你把我羞辱成五分钟的错误处理练习脑筋急转弯......!希望你能和自己一起生活;-o。干杯。
【解决方案3】:

你应该在转换之前删除分号

std::string temp;
file >> temp;
std::replace( temp.begin(), temp.end(), ';', ' ');
*(A + N*c + r) =    std::stod( temp );

【讨论】:

  • 谢谢。如果每行的最后一个数字也有分号,您是否知道该怎么办?例如,第一行应该是 1 ;2 ;3 ;4 ;5 ; 而不是 1 ;2 ;3 ;4 ;5
  • 不,我收到terminate called after throwing an instance of 'std::invalid_argument' what(): stod
  • std::string temp; do { file &gt;&gt; temp; temp.erase (std::remove(temp.begin(), temp.end(), ';'), temp.end()); } while ( temp.size() == 0 ); *(A + N*c + r) = std::stod( temp );
  • 您应该更改您的测试值以在行尾包含分号,然后我将能够在我的答案中使用修改后的代码
  • 你的分号末尾有一个空格,所以会被单独读入temp string:你可以在replace之前使用if (temp == ";") continue;忽略它。
【解决方案4】:

为什么不试试 getline(),它接受一个分隔符作为第三个参数。

string buffer;
for (int c = 0; c < M; ++c) {
    getline(file, buffer, ';');
    stringstream tmp(buffer);
    tmp>>*(A + N*c + r);
}

getline() 将读取直到下一个分隔符或换行符或文件结尾

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-25
    • 1970-01-01
    • 1970-01-01
    • 2016-02-06
    • 2019-12-07
    • 1970-01-01
    相关资源
    最近更新 更多