插入器和提取器读/写二进制数据与文本

时间:2011-11-22 17:10:28

标签: c++ text iostream binary-data

我一直在尝试阅读iostream并更好地理解它们。偶尔我发现它强调插入器(<<)和提取器(>>)意味着在文本序列化中使用。这是一些地方,但这篇文章是一个很好的例子:

http://spec.winprog.org/streams/

<iostream>宇宙之外,有些情况下&lt;&lt;和&gt;&gt;以流方式使用,但不遵守任何文本约定。例如,当Qt的QDataStream

使用它们时,它们会写入二进制编码数据

http://doc.qt.nokia.com/latest/qdatastream.html#details

在语言层面,&lt;&lt;和&gt;&gt;运算符属于你的项目重载(因此QDataStream做的事情显然是可以接受的)。我的问题是,对于那些使用<iostream>来使用&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;和&gt;&gt;运营商实施二进制编码和解码。 (例如)是否有任何期望,如果写入磁盘上的文件,该文件应该可以通过文本编辑器查看和编辑?

是否应始终使用其他方法名称并将其基于read()write()?或者文本编码应该仅仅被视为默认行为,与标准库iostream集成的类可以选择忽略?


更新关键术语问题似乎是“格式化”与“未格式化”的I / O区别(与术语“文本”与“二进制”相对)。我发现了这个问题:

writing binary data (std::string) to an std::ofstream?

来自@ TomalakGeret'kal的评论说“我不想使用&lt;&lt;二进制数据,因为我的大脑将其视为”格式化输出“,这不是你的意思再次,它完全有效,但我不会那样混淆我的大脑。“

问题的接受答案是,只要您使用ios::binary就可以了。这似乎支持了辩论中的“它没有任何问题”......但我仍然没有看到任何有关该问题的权威来源。

3 个答案:

答案 0 :(得分:9)

实际上,运算符<<>>是位移运算符;严格来说,将它们用于I / O已经是一种误用。然而,误操作与操作员超载本身一样久,今天的I / O是它们最常见的用法,因此它们被广泛认为是I / O插入/提取操作符。我很确定如果没有iostreams的先例,没有人会将这些运算符用于I / O(特别是对于具有可变参数模板的C ++ 11,解决了使用这些运算符解决iostreams的主要问题,更干净的方式)。另一方面,从语言的角度来看,重载operator<<operator>>可能意味着你想要的任何意思。

因此,问题归结为对这些运算符的可接受的使用。为此,我认为必须区分两种情况:第一,在iostream类上工作的新重载,第二,在其他类上工作的新重载,可能设计为像iostream一样工作。

让我们考虑iostream类上的第一个新运算符。让我首先观察iostream类是关于格式化的(和反向过程,可以称为“deformatting”;“lexing”恕我直言,因为提取器不确定类型,但只尝试根据给定的类型解释数据)。负责原始数据的实际I / O的类是streambufs。但请注意,正确的二进制文件一个文件,您只需转储内部原始数据。就像文本文件一样(实际上更是如此),二进制文件应该对它包含的数据进行明确的编码。特别是如果期望在不同系统上读取文件。因此,格式化输出的概念对二进制文件也很有意义;只是格式是不同的(例如,写一个预定数量的字节,其中最重要的字节首先是一个整数值)。

iostream本身就是用于处理文本文件的类,也就是说,其内容被解释为数据的文本表示的文件。为此优化了许多内置行为,如果在二进制文件上使用,可能会导致问题。一个明显的例子是默认情况下,在尝试任何输入之前会跳过空格。对于二进制文件,这显然是错误的行为。同样,locales的使用对于二进制文件没有意义(尽管有人可能认为可能存在“二进制语言环境”,但我不认为为iostream定义的语言环境为其提供了合适的接口)。因此,我认为为iostream类编写二进制文件operator<<operator>>是错误的。

另一种情况是为二进制输入/输出定义一个单独的类(可能重用streambuf层来执行实际的I / O)。由于我们现在谈论不同的类别,上面的论证不再适用。所以现在的问题是:I / O上的operator<<operator>>应该被视为“文本插入/提取运算符”还是更普遍的“格式化数据插入/提取运算符”?标准类仅将它们用于文本,但是,根本没有用于二进制I / O插入/提取的标准类,因此标准用法无法区分这两者。

我个人会说二进制插入/提取足够接近文本插入/提取,这种用法是合理的。请注意,您还可以制作有意义的二进制I / O操纵器,例如: bigendianlittleendianintwidth(n)确定输出整数的格式。

除此之外,还有那些运算符用于非I / O(甚至你不会想到使用streambuf层)的东西,比如读取或插入容器。在我看来,这已经构成对运营商的滥用,因为数据没有被翻译成不同的格式。它只是存储在一个容器中。

答案 1 :(得分:4)

标准中的iostream的抽象是文本的抽象 格式化的数据流;不支持任何非文本格式。 这是iostreams的抽象。没什么不对的 定义一个不同的流类,其抽象是二进制格式, 但是在iostream中这样做可能会破坏现有代码,而不是 工作。

答案 2 :(得分:3)

重载运算符&gt;&gt;和&lt;&lt;执行格式化的IO。其余的IO功能(put,get,read,write等)执行未格式化的IO。未格式化的IO意味着IO库仅接受缓冲区,即输入的无符号字符序列。此缓冲区可能包含文本消息或二进制内容。应用程序负责解释缓冲区。但是,格式化的IO会考虑区域设置。对于文本文件,根据应用程序运行的环境,在输入/输出操作中可能会发生一些特殊的字符转换,以使它们适应系统特定的文本文件格式。在许多环境中,例如大多数基于UNIX的系统,将文件作为文本文件或二进制文件打开没有区别。请注意,您可以重载运算符&gt;&gt;和&lt;&lt;为您自己的类型。这意味着您能够将没有区域设置信息的格式化IO应用于您自己的类型,尽管这有点棘手。