使用 fstream 加载二进制文件

3

我想以以下方式使用 fstream 加载二进制文件:

#include <iostream>
#include <fstream>
#include <iterator>
#include <vector>

using namespace std;

int main()
{
    basic_fstream<uint32_t> file( "somefile.dat", ios::in|ios::binary );

    vector<uint32_t> buffer;
    buffer.assign( istream_iterator<uint32_t, uint32_t>( file ), istream_iterator<uint32_t, uint32_t>() );

    cout << buffer.size() << endl;

    return 0;
}

但是它没有起作用。在Ubuntu上,它会崩溃并出现std::bad_cast异常。在MSVC++ 2008中,它只会打印0。

我知道我可以使用file.read加载文件,但我想使用迭代器和operator>>来加载文件的部分。这是可能的吗?为什么上述代码不起作用?


所述二进制数据的来源是什么? - NoMoreZealots
二进制数据可能是图像或类似的东西。 - Kirill V. Lyadvinsky
4个回答

3
  1. istream_iterator需要basic_istream作为参数。
  2. 不可能在basic_istream类中重载operator>>
  3. 定义全局operator>>将导致与类成员operator>>的编译时冲突。
  4. 您可以为类型uint32_t专门定义basic_istream。但是,为了进行专业化,您应该重写basic_istream类的所有函数。相反,您可以定义虚拟类x并为其专门化basic_istream,如以下代码所示:
using namespace std;

struct x {};
namespace std {
template<class traits>
class basic_istream<x, traits> : public basic_ifstream<uint32_t>
{
public:
    explicit basic_istream<x, traits>(const wchar_t* _Filename, 
        ios_base::openmode _Mode, 
        int _Prot = (int)ios_base::_Openprot) : basic_ifstream<uint32_t>( _Filename, _Mode, _Prot ) {}

    basic_istream<x, traits>& operator>>(uint32_t& data)
    {
        read(&data, 1);
        return *this;
    }
};
} // namespace std 

int main() 
{
    basic_istream<x> file( "somefile.dat", ios::in|ios::binary );
    vector<uint32_t> buffer;
    buffer.assign( istream_iterator<uint32_t, x>( file ), istream_iterator<uint32_t, x>() );
    cout << buffer.size() << endl;
    return 0;
}

1
请注意,在专门使用 uint32_t 时,实际上您正在专门使用一种基本类型,即 unsigned int 或(较少)unsigned longtypedef 不会生成由模板(或实际上是 C++ 中的任何其他类型重载)区分的类型。 - quark

0
你可以重新加载operator>>以正确读取整数。 当然,它所做的就是读取4个字节。但这是其他所有operators>>最终都在做的事情。 以下是示例(无错误检查,假设字节顺序与当前编译器使用的相同等)。
std::istream& operator>>(std::istream& in, uint32_t& data)
{
    in.read(&data, sizeof(data));
    return in;
}

为您自己的整数定制(可能需要逐字节读取并进行移位赋值,如果不知道字节顺序,请查看十六进制编辑器中的文件),添加错误检查,然后您应该能够使用现有的代码。

编辑:啊,是的,请确保这个函数屏蔽了提供的stl操作符,该操作符读取整数--可能需要从您正在使用的流派生自己的类,并在其中使用它而不是std :: istream& in,以便编译器知道首先检查谁。


istream_iterator 使用 basic_istream 作为参数,因此我不能只从 basic_ifstream 继承并重载 operator>> - Kirill V. Lyadvinsky

0
另一种与Alexey Malistov的答案相同的方法:
#include <fstream>
#include <iterator>
#include <vector>
#include <iostream>

struct rint // this class will allow us to read binary
{
  // ctors & assignment op allows implicit construction from uint
  rint () {}
  rint (unsigned int v) : val(v) {}
  rint (rint const& r) : val(r.val) {}
  rint& operator= (rint const& r) { this->val = r.val; return *this; }
  rint& operator= (unsigned int r) { this->val = r; return *this; }

  unsigned int val;

  // implicit conversion to uint from rint
  operator unsigned int& ()
  {
    return this->val;
  }
  operator unsigned int const& () const
  {
    return this->val;
  }
};

// reads a uints worth of chars into an rint
std::istream& operator>> (std::istream& is, rint& li)
{
  is.read(reinterpret_cast<char*>(&li.val), 4);
  return is;
}

// writes a uints worth of chars out of an rint
std::ostream& operator<< (std::ostream& os, rint const& li)
{
  os.write(reinterpret_cast<const char*>(&li.val), 4);
  return os;
}

int main (int argc, char *argv[])
{
  std::vector<int> V;

  // make sure the file is opened binary & the istream-iterator is
  // instantiated with rint; then use the usual copy semantics
  std::ifstream file(argv[1], std::ios::binary | std::ios::in);
  std::istream_iterator<rint> iter(file), end;
  std::copy(iter, end, std::back_inserter(V));

  for (int i = 0; i < V.size(); ++i)
    std::cout << std::hex << "0x" << V[i] << std::endl;

  // this will reverse the binary file at the uint level (on x86 with
  // g++ this is 32-bits at a time)
  std::ofstream of(argv[2], std::ios::binary | std::ios::out);
  std::ostream_iterator<rint> oter(of);
  std::copy(V.rbegin(), V.rend(), oter);

  return 0;
}

0

主要问题可能是您所说的“二进制文件”的含义。 ios::binary 只确保 istream 对象不会将特定于平台的换行符替换为 '\n'。仅此而已。这对您来说足够了吗?

istream_iterator 基本上只是调用 operator>> 的一种花哨方式。如果您的流中有真正的二进制数据,那么它将失败。您的文件中是否有真正的二进制数据?还是整数存储为字符串?

如果您需要读取真正的二进制整数,则需要使用 istream.read() 或直接使用流缓冲区对象。


我有真正的二进制数据,而不是文本。主要问题是“为什么它不起作用”。我指出了istream_iterator,我有uint32_t数据,而不是char(第二个模板参数)。 - Kirill V. Lyadvinsky
是的,重载 >> 以读取 sizeof(uint32_t) 字节。根据文件的编写者和方式,您可能还需要修复字节序。 - Eugene
@Eugene,你能发一些示例吗?我认为仅仅重载运算符是不够的。 - Kirill V. Lyadvinsky
如果您需要读取真实的二进制整数,您需要使用 istream.read() 或直接使用流缓冲区对象。 - sbi
发布示例。真的没有自动跟踪这些评论线程的方法吗? - Eugene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接