在 C + + 中是否存在二进制内存流

我通常使用 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;

有什么想法吗?

68503 次浏览

只用字符,不用整数。

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

要向流(包括字符串流)读写二进制数据,请使用 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了。

编辑: 为了透明地使用插入和提取操作符(< < 和 > >) ,最好创建一个派生的 stream buf 来执行正确的操作,并将其传递给您想要使用的任何流。

超载一些不寻常的操作符工作得相当不错。在这里我选择超载 < = ,因为它具有与 < < 相同的从左到右的联想性,并且有某种近似的外观和感觉..。

#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);
    
  • the endianess isn't clearly mentionned at reading the code, as illustrated in the above example.

  • it cannot mix simply with << because it doesn't belong to the same group of precedence. I usually use parenthesis to clarify such as :

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

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

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()
  • 也接受数组和 struct/class 实例

缺点:

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

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

//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);
#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();


}

不是一个优雅的解决方案,但工程和灵活性

我真的很喜欢韩洛的方法,并已经验证了它的伟大工作!如果将 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 _ Composite 添加一个新模板,那么也可以支持类似 std: : map 的类型。对于像 std: : Vector 这样的元素,我建议首先将大小推送到流,因此在提取方面,可以提取它,以了解之后需要提取多少元素。这种方法可以很好地使用常见的 std: : Vector 和 std: : map 类型。