istream_iterator 行为误区

istream_iterator behavior misunderstanding

目标是从二进制文件中读取16位有符号整数。首先,我将文件作为 ifstream 打开,然后我想使用 [=23 将每个数字复制到一个向量中=]istream_iteratorcopy 算法。 我不明白这段代码有什么问题:

int main(int argc, char *argv[]) {
    std::string filename("test.bin");

    std::ifstream is(filename);
    if (!is) {
        std::cerr << "Error while opening input file\n";
        return EXIT_FAILURE;
    }
    
    std::noskipws(is);
    std::vector<int16_t> v;
    std::copy(
        std::istream_iterator<int16_t>(is),
        std::istream_iterator<int16_t>(),
        std::back_inserter(v)
    );

    //v is still empty
}

这段代码没有产生错误,但在调用 std::copy 之后向量仍然为空。因为我在标准输入模式(“文本”模式)下打开文件,所以我期望 istream_iterator 甚至可以工作如果文件是二进制的。当然,关于这个 class.

的行为,我缺少一些东西

首先,要使用 ifstream 读取二进制文件,您需要以 binary 模式打开文件,而不是文本模式(默认)。否则,读取操作可能会错误解释换行符字节并在平台编码(即 CRLF->LF,反之亦然)之间转换它们,从而破坏您的二进制数据。

其次,istream_iterator使用operator>>,它默认读取并解析格式化文本,这不是读取二进制文件时想要的。您需要改用 istream::read()。但是,没有迭代器包装器(但如果需要,您可以编写自己的包装器)。

试试这个:

int main(int argc, char *argv[]) {
    std::string filename = "test.bin";

    std::ifstream is(filename, std::ifstream::binary);
    if (!is) {
        std::cerr << "Error while opening input file\n";
        return EXIT_FAILURE;
    }

    std::vector<int16_t> vec;
    int16_t value;

    while (is.read(reinterpret_cast<char*>(&value), sizeof(value))) {
        // swap value's endian, if needed...
        vec.push_back(value);
    }

    // use vec as needed...

    return 0;
}

话虽这么说,如果你真的想对二进制文件使用 istream_iterator,那么你将不得不编写自定义 class/struct 来包装 int16_t,然后定义一个operator>> 为该类型调用 read(),例如:

struct myInt16_t {
    int16_t value; 
    operator int16_t() const { return value; }
};

std::istream& operator>>(std::istream &is, myInt16_t &v) {
    if (is.read(reinterpret_cast<char*>(&v.value), sizeof(v.value))) {
        // swap v.value's endian, if needed...
    }
    return is;
}

int main(int argc, char *argv[]) {
    std::string filename = "test.bin";

    std::ifstream is(filename, std::ifstream::binary);
    if (!is) {
        std::cerr << "Error while opening input file\n";
        return EXIT_FAILURE;
    }

    std::noskipws(is);
    std::vector<int16_t> vec;
    std::copy(
        std::istream_iterator<myInt16_t>(is),
        std::istream_iterator<myInt16_t>(),
        std::back_inserter(vec)
    );

    // use vec as needed...

    return 0;
}

Since I'm opening the file in the standard input mode ("textual" mode), I was expecting istream_iterator to work even if the file is binary.

您在概念上理解错了。由于文件“是二进制文件”1,你应该期望istream_iterator工作即使 文件以“文本模式”打开2。文件的格式决定了您可以用它做什么;没有工具可以从文件中读取“格式为人类可读文本的数字”,除非文件实际上打算以这种方式读取。您的文件大概打算被读取为“字节对,每个字节代表一个 16 位数值”,因此您需要与该格式兼容的工具。文件 mode 只是拼图的一小部分。

要有意义地遍历文件,您需要以二进制模式打开它(以避免在 Windows 平台上损坏)并且 使用一个有能力的工具以您想要的方式解释二进制数据。您还需要确保正确考虑数据。尝试使用像 noskipws 这样的东西是没有意义的,因为 数据没有空白的概念,因为它不代表文本 .

  1. 严格来说这并没有什么意思;但通常情况下,说一个文件“是二进制的”表明文件内容 而不是 旨在供人类阅读,并且数值表示为它们在计算机内存中的样子,即直接以256为基数,而使用字节来表示文本,而文本又使用阿拉伯数字、.符号等来表示一个数字。

  2. “以文本模式”打开文件的含义取决于您使用的语言和平台。在许多语言(包括 C 和 C++)中,它对 Windows(基本上只是就地翻译 CR-LF 序列)和 none(至少,我最后检查过)对 [=54 的影响很小=]-类似平台。在某些(如 Python 3.x)中,它会自动引入将字节转换为表示实际文本的对象的机制,实际上使用编码而不是假装字节“是字符”(它们不是)。