我目前正在学习 C++(来自 Java),并且正在尝试了解如何在 C++ 中正确使用 IO 流。
假设我有一个Image
包含图像像素的类,并且我重载了提取运算符以从流中读取图像:
istream& operator>>(istream& stream, Image& image)
{
// Read the image data from the stream into the image
return stream;
}
所以现在我可以读取这样的图像:
Image image;
ifstream file("somepic.img");
file >> image;
但现在我想使用相同的提取运算符从自定义流中读取图像数据。假设我有一个文件,其中包含压缩形式的图像。因此,我可能不想使用 ifstream 来实现自己的输入流。至少我会在 Java 中这样做。在 Java 中,我会编写一个自定义类来扩展InputStream
该类并实现该int read()
方法。所以这很容易。用法如下所示:
InputStream stream = new CompressedInputStream(new FileInputStream("somepic.imgz"));
image.read(stream);
所以使用相同的模式也许我想在 C++ 中做到这一点:
Image image;
ifstream file("somepic.imgz");
compressed_stream stream(file);
stream >> image;
但也许这是错误的方式,不知道。扩展istream
类看起来很复杂,经过一番搜索,我发现了一些关于扩展的提示streambuf
。但是对于这样一个简单的任务,这个例子看起来非常复杂。
那么在 C++ 中实现自定义输入/输出流(或 streambufs?)的最佳方法是什么?
解决方案
有些人建议根本不使用 iostreams,而是使用迭代器、boost 或自定义 IO 接口。这些可能是有效的替代方案,但我的问题是关于 iostreams。接受的答案导致下面的示例代码。为了更容易阅读,没有标题/代码分离,并且导入了整个 std 命名空间(我知道这在实际代码中是一件坏事)。
这个例子是关于读取和写入垂直异或编码的图像。格式很简单。每个字节代表两个像素(每像素 4 位)。每一行都与前一行异或。这种编码为压缩图像做准备(通常会产生很多更容易压缩的 0 字节)。
#include <cstring>
#include <fstream>
using namespace std;
/*** vxor_streambuf class ******************************************/
class vxor_streambuf: public streambuf
{
public:
vxor_streambuf(streambuf *buffer, const int width) :
buffer(buffer),
size(width / 2)
{
previous_line = new char[size];
memset(previous_line, 0, size);
current_line = new char[size];
setg(0, 0, 0);
setp(current_line, current_line + size);
}
virtual ~vxor_streambuf()
{
sync();
delete[] previous_line;
delete[] current_line;
}
virtual streambuf::int_type underflow()
{
// Read line from original buffer
streamsize read = buffer->sgetn(current_line, size);
if (!read) return traits_type::eof();
// Do vertical XOR decoding
for (int i = 0; i < size; i += 1)
{
current_line[i] ^= previous_line[i];
previous_line[i] = current_line[i];
}
setg(current_line, current_line, current_line + read);
return traits_type::to_int_type(*gptr());
}
virtual streambuf::int_type overflow(streambuf::int_type value)
{
int write = pptr() - pbase();
if (write)
{
// Do vertical XOR encoding
for (int i = 0; i < size; i += 1)
{
char tmp = current_line[i];
current_line[i] ^= previous_line[i];
previous_line[i] = tmp;
}
// Write line to original buffer
streamsize written = buffer->sputn(current_line, write);
if (written != write) return traits_type::eof();
}
setp(current_line, current_line + size);
if (!traits_type::eq_int_type(value, traits_type::eof())) sputc(value);
return traits_type::not_eof(value);
};
virtual int sync()
{
streambuf::int_type result = this->overflow(traits_type::eof());
buffer->pubsync();
return traits_type::eq_int_type(result, traits_type::eof()) ? -1 : 0;
}
private:
streambuf *buffer;
int size;
char *previous_line;
char *current_line;
};
/*** vxor_istream class ********************************************/
class vxor_istream: public istream
{
public:
vxor_istream(istream &stream, const int width) :
istream(new vxor_streambuf(stream.rdbuf(), width)) {}
virtual ~vxor_istream()
{
delete rdbuf();
}
};
/*** vxor_ostream class ********************************************/
class vxor_ostream: public ostream
{
public:
vxor_ostream(ostream &stream, const int width) :
ostream(new vxor_streambuf(stream.rdbuf(), width)) {}
virtual ~vxor_ostream()
{
delete rdbuf();
}
};
/*** Test main method **********************************************/
int main()
{
// Read data
ifstream infile("test.img");
vxor_istream in(infile, 288);
char data[144 * 128];
in.read(data, 144 * 128);
infile.close();
// Write data
ofstream outfile("test2.img");
vxor_ostream out(outfile, 288);
out.write(data, 144 * 128);
out.flush();
outfile.close();
return 0;
}