【问题标题】:Are there binary memory streams in C++C++中有二进制内存流吗
【发布时间】:2010-12-06 06:23:23
【问题描述】:

我通常使用stringstream 写入内存字符串。有没有办法以二进制模式写入字符缓冲区?考虑以下代码:

stringstream s;
s << 1 << 2 << 3;
const char* ch = s.str().c_str();

ch 的内存将如下所示:0x313233 - 字符 1、2 和 3 的 ASCII 码。我正在寻找一种方法来自己写入二进制值。也就是说,我希望内存中有 0x010203。问题是我希望能够写一个函数

void f(ostream& os)
{
    os << 1 << 2 << 3;
}

并在外部决定使用哪种流。像这样的:

mycharstream c;
c << 1 << 2 << 3; // c.data == 0x313233;
mybinstream b;
b << 1 << 2 << 3; // b.data == 0x010203;

有什么想法吗?

【问题讨论】:

  • 这是十六进制,而不是二进制。为什么你不能写 0x01、0x02 等等……毕竟这些是实际的 ASCII 字符。
  • 他希望内存的内容(实际字节)为 0x010203(十进制的 66051),而不是字符串“0x010203”。
  • 我已经修改了问题。希望现在更清楚了。
  • 很好的问题。太糟糕了,不可能给出一个好的答案,因为这是标准库中的一个设计错误。

标签: c++ iostream


【解决方案1】:

要在流中读取和写入二进制数据,包括字符串流,请使用 read() 和 write() 成员函数。所以

unsigned char a(1), b(2), c(3), d(4);
std::stringstream s;
s.write(reinterpret_cast<const char*>(&a), sizeof(unsigned char));
s.write(reinterpret_cast<const char*>(&b), sizeof(unsigned char));
s.write(reinterpret_cast<const char*>(&c), sizeof(unsigned char));
s.write(reinterpret_cast<const char*>(&d), sizeof(unsigned char));

s.read(reinterpret_cast<char*>(&v), sizeof(unsigned int)); 
std::cout << std::hex << v << "\n";

这会在我的系统上提供0x4030201

编辑: 为了使用插入和提取运算符(>)使这项工作透明,您最好创建一个派生的 streambuf 来做正确的事情,并将其传递给您想要使用的任何流。

【讨论】:

  • 它肯定回答了问题的第一部分,但是有没有办法让插入看起来总是一样的(即 s
  • 你自己的streambuf不能这样做;格式化是在(非虚拟)istream 和 ostream 方法中完成的,其结果就是 streambuf 看到的。
  • 这个问题实际上显示了内存中的结果0x010203,而这可能会产生0x00000001 0x00000002 0x00000003(假设sizeof(int)==4)。
  • @MSalters 你说得对,显然比我小 6 岁的我是个白痴。
【解决方案2】:

你可以用模板做这种事情。例如:

//struct to hold the value:
template<typename T> struct bits_t { T t; }; //no constructor necessary
//functions to infer type, construct bits_t with a member initialization list
//use a reference to avoid copying. The non-const version lets us extract too
template<typename T> bits_t<T&> bits(T &t) { return bits_t<T&>{t}; }
template<typename T> bits_t<const T&> bits(const T& t) { return bits_t<const T&>{t}; }
//insertion operator to call ::write() on whatever type of stream
template<typename S, typename T>
S& operator<<(S &s, bits_t<T> b) {
    return s.write((char*)&b.t, sizeof(T));
}
//extraction operator to call ::read(), require a non-const reference here
template<typename S, typename T>
S& operator>>(S& s, bits_t<T&> b) {
    return s.read((char*)&b.t, sizeof(T));
}

它可以使用一些清理,但它是功能性的。例如:

//writing
std::ofstream f = /*open a file*/;
int a = 5, b = -1, c = 123456;
f << bits(a) << bits(b) << bits(c);

//reading
std::ifstream f2 = /*open a file*/;
int a, b, c;
f >> bits(a) >> bits(b) >> bits(c);

【讨论】:

  • 我更喜欢这个答案,因为它不会让人困惑,而且它还可以包装其他东西,比如vector&lt;float&gt;
  • 嘿,@SamuelPowell 我非常喜欢这种方法,我更进一步,并在这种方法之上编写了更多的序列化程序。我喜欢这个,因为与其他 C++ 序列化程序相比,它的复杂性很低。如果有兴趣,请查看github.com/goblinhack/simple-c-plus-plus-serializer - 会对您的 cmets 感兴趣。我发现由于(我认为)运算符重载问题,我不得不删除模板中的流类型。无论如何,它适用于许多类型。
【解决方案3】:

好吧,只使用字符,而不是整数。

s << char(1) << char(2) << char(3);

【讨论】:

    【解决方案4】:

    重载一些不寻常的运算符效果很好。在下面我选择重载 因为它具有与 相同的从左到右的关联性,并且在某种程度上具有接近的外观和感觉...

    #include <iostream>
    #include <stdint.h>
    #include <arpa/inet.h>
    
    using namespace std;
    
    ostream & operator<= (ostream& cout, string const& s) {
        return cout.write (s.c_str(), s.size());
    }
    ostream & operator<= (ostream& cout, const char *s) {
        return cout << s;
    }
    ostream & operator<= (ostream&, int16_t const& i) {
        return cout.write ((const char *)&i, 2);
    }
    ostream & operator<= (ostream&, int32_t const& i) {
        return cout.write ((const char *)&i, 4);
    }
    ostream & operator<= (ostream&, uint16_t const& i) {
        return cout.write ((const char *)&i, 2);
    }
    ostream & operator<= (ostream&, uint32_t const& i) {
        return cout.write ((const char *)&i, 4);
    }
    
    int main() {
        string s("some binary data follow : ");
    
        cout <= s <= " (machine ordered) : " <= (uint32_t)0x31323334 <= "\n"
             <= s <= " (network ordered) : " <= htonl(0x31323334) ;
        cout << endl;
    
        return 0;
    }
    

    有几个缺点:

    • 的新含义可能会让读者感到困惑或导致意想不到的结果:

      cout <= 31 <= 32;
      

      不会给出与

      相同的结果
      cout <= (31 <= 32);
      
    • 在阅读代码时没有明确提到字节序,因为 如上例所示。

    • 它不能简单地与 混合,因为它不属于 同组优先。我通常用括号来澄清这样的 如:

      ( cout <= htonl(a) <= htonl(b) ) << endl;
      

    【讨论】:

    • 这是一个很酷的概念证明,但请注意,C++ 的重载运算符被认为是邪恶的,因为它们允许 this&lt;&lt; 的不明显重载是合理的,因为它是 标准 重载。不应发明新的 hacky 重载,并且应谨慎使用重载本身。
    【解决方案5】:

    对于这个用例,我自己实现了一个“原始移位运算符”:

    template <typename T, class... StreamArgs>
    inline std::basic_ostream<StreamArgs...> &
    operator <= (std::basic_ostream<StreamArgs...> & out, T const & data) {
            out.write(reinterpret_cast<char const *>(&data), sizeof(T));
            return out;
    }
    

    把它放在方便的地方并像这样使用它:

    std::cout <= 1337 <= 1337ULL <= 1337. <= 1337.f;
    

    优点:

    • 可链接
    • 自动sizeof()
    • 也接受数组和结构/类实例

    缺点:

    • 对非 POD 对象不安全:泄漏指针和填充
    • 输出是特定于平台的:填充、字节序、整数类型

    【讨论】:

      【解决方案6】:
      #include <sstream>
      
      class bostringstream {
      public:
        bostringstream() : oss() {}
      
        template <typename T, typename std::enable_if<std::is_fundamental<T>::value,
                                                      bool>::type = true>
        bostringstream& operator<<(const T& v) {
          oss.write((char*)&v, sizeof(T));
          return *this;
        }
      
        template <typename T, typename std::enable_if<
                                  std::is_fundamental<typename T::value_type>::value,
                                  bool>::type = true>
        bostringstream& operator<<(const T& v) {
          oss.write((char*)v.data(), v.size() * sizeof(typename T::value_type));
          return *this;
        }
      
        template <typename _InputIterator>
        bostringstream& write(_InputIterator first, _InputIterator last) {
          char* data = (char*)&(*first);
          auto n = std::distance(first, last);
          oss.write(data, n * sizeof(*first));
          return *this;
        }
      
        template <typename T, typename std::enable_if<std::is_fundamental<T>::value,
                                                      bool>::type = true>
        bostringstream& write(const T* v, std::streamsize count) {
          oss.write((char*)v, sizeof(T) * count);
          return *this;
        }
      
        auto rdbuf() const { return oss.rdbuf(); }
      
        auto str() const { return oss.str(); }
      
        std::size_t size() { return oss.tellp(); }
      
      protected:
        std::ostringstream oss;
      };
      

      例子:

      #include <array>
      #include <string>
      #include <vector>
      #include <iostream>
      #include <fstream>
      #include "bsstream.hpp"
      
      int main(int argc, char **argv) {
      
        int i = 1;
        float j = 1.1;
        double k = 1.2;
        std::vector<int> ii{1,2};
        std::vector<double> jj{1.2,2.2};
        std::string kk = "abcd";
        std::array<int, 2> ll{3,4};
        int l[] = {1,2};
      
        bostringstream of;
        of << i << j <<k;
        of <<ii << jj << kk << ll;
        of.write(l, 2);
      
        std::ofstream oof("foo.bin", std::ios::binary);
        oof << of.str();
        oof.close();
      
      }
      

      不是一个优雅的解决方案,但有效且灵活

      【讨论】:

        【解决方案7】:

        我真的很喜欢韩洛的方法,并且已经验证它非常有效!如果将 oss 成员变量更改为使用 std::stringstream (vs. ostringstream),该类也可用于使用重载流提取运算符进行提取,如下所示:

            template <typename T, typename std::enable_if<std::is_fundamental<T>::value, bool>::type = true>
            bostringstream& operator>> (T& v)
            {
                char buffer[sizeof(T)];
                oss.read(buffer, sizeof(T));
                v = *(T*)buffer;
                return *this;
            }
        
        

        示例模板支持整数类型,如果为 std::is_compound 添​​加新模板,则还可以支持 std::map 等类型。对于 std::vector 之类的东西,即“is_fundemental”,我建议先将大小推送到流中,因此在提取端可以拉出它以了解之后要拉出多少个元素。这种方法可以很好地处理常见的 std::vector 和 std::map 类型。

        【讨论】:

          猜你喜欢
          • 2018-11-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-30
          • 1970-01-01
          • 2019-08-17
          • 1970-01-01
          • 2021-05-29
          相关资源
          最近更新 更多