How to create stream which handles both input and output in C++?(如何在C++中创建既处理输入又处理输出的流?)
问题描述
我正在尝试创建一个既是输入流又是输出流的类(如std::cout
和std::cin
)。我试图重载操作符<<
和>>
,但后来我明白了,编写这样的代码并不明智(因为这将是重写C++流的一种方法),而且当像std::basic_iostream
、std::basic_ostream
、std::basic_istream
这样的类在C++标准库中可用时,维护是非常困难的,因为我必须为每种类型重载操作符。因此,我尝试这样定义我的类:
#include <istream>
class MyStream : public std::basic_iostream<char> {
public:
MyStream() : std::basic_iostream<char>(stream_buffer) {}
};
我的问题是std::basic_iostream<char>
的构造函数中的第一个参数。从cppreference开始,std::basic_iostream::basic_iostream
获取指向派生自std::basic_streambuf
的流缓冲区的指针:
explicit basic_iostream( std::basic_streambuf<CharT,Traits>* sb );
我已经阅读并尝试了Apache C++ Standard Library User's Guide's chapter 38中的示例。它说我必须传递一个指向流缓冲区的指针,有三种方法可以做到这一点:
- 在类初始化前创建流缓冲区
- 从另一个流获取流缓冲区(使用
rdbuf()
或类似成员) - 将
basic_streambuf
对象定义为受保护成员或私有成员
std::basic_streambuf
类创建一个对象,它不会做任何事情,对吗?所以我定义了另一个派生自std::basic_streambuf<char>
的类。但这一次我不知道要定义什么函数,因为我不知道在插入、提取和刷新数据时调用哪个函数。
如何创建具有自定义功能的流?
请注意,这是试图构建有关创建C++流和流缓冲区的标准指南。
推荐答案
创建行为类似于流的类很容易。假设我们想要创建名为MyStream
的类,类的定义将非常简单:
#include <istream> // class "basic_iostream" is defined here
class MyStream : public std::basic_iostream<char> {
private:
std::basic_streambuf buffer; // your streambuf object
public:
MyStream() : std::basic_iostream<char>(&buffer) {} // note that ampersand
};
类的构造函数应使用指向自定义std::basic_streambuf<char>
对象的指针调用std::basic_iostream<char>
的构造函数。std::basic_streambuf
只是一个模板类,它定义了流缓冲区的结构。因此,您必须拥有自己的流缓冲区。您可以通过两种方式获取:
- 来自另一个流:每个流都有一个成员
rdbuf
,该成员不接受任何参数,并返回指向它所使用的流缓冲区的指针。示例:
...
std::basic_streambuf* buffer = std::cout.rdbuf(); // take from std::cout
...
- 创建您自己的:您始终可以通过从
std::basic_streambuf<char>
派生来创建缓冲区类,并根据需要对其进行自定义。
MyStream
类,我们需要流缓冲区。让我们从上面选择选项2并创建我们自己的流缓冲区,并将其命名为MyBuffer
。我们需要以下内容:
- 构造函数以初始化对象。
- 程序临时存储输出的连续内存块。
- 连续内存块,用于临时存储来自用户(或其他内容)的输入。
- 方法
overflow
,当分配给存储输出的内存已满时调用。 - 方法
underflow
,当程序读取所有输入并请求更多输入时调用该方法。 - 方法
sync
,输出刷新时调用。
因为我们知道创建流缓冲类需要什么,所以让我们声明它:
class MyBuffer : public std::basic_streambuf<char> {
private:
char inbuf[10];
char outbuf[10];
int sync();
int_type overflow(int_type ch);
int_type underflow();
public:
MyBuffer();
};
这里的inbuf
和outbuf
是将分别存储输入和输出的两个数组。int_type
是一种特殊类型,类似于char,为支持char
、wchar_t
等多种字符类型而创建。
在我们开始实现Buffer类之前,我们需要知道缓冲区将如何工作。
要了解缓冲区如何工作,我们需要知道数组是如何工作的。数组没有什么特别之处,只是指向连续内存的指针。当我们声明一个带有两个元素的char
数组时,操作系统会为我们的程序分配2 * sizeof(char)
内存。当我们使用array[n]
访问数组中的元素时,它被转换为*(array + n)
,其中n
是索引号。当您将n
添加到一个数组时,它会跳到下一个n * sizeof(<the_type_the_array_points_to>)
(图1)。如果你不知道什么是指针算法,我建议你在继续之前先学习一下。cplusplus.com有good article面向初学者的指针。
array array + 1
/
------------------------------------------
| | | 'a' | 'b' | | |
------------------------------------------
... 105 106 107 108 ...
| |
-------
|
memory allocated by the operating system
figure 1: memory address of an array
现在我们已经了解了很多关于指针的知识,让我们来看看流缓冲区是如何工作的。我们的缓冲区包含两个数组inbuf
和outbuf
。但是标准库如何知道输入必须存储到inbuf
,输出必须存储到outbuf
?因此,有两个区域分别称为GET区域和PUT区域,分别是输入区域和输出区域。
PUT区域由以下三个指针指定(图2):
pbase()
或投篮:投球区域开始epptr()
或结束放置指针:放置区域结束pptr()
或放置指针:下一个字符放置的位置
这些实际上是返回相应指针的函数。这些指针由setp(pbase, epptr)
设置。在此函数调用后,pptr()
被设置为pbase()
。为了更改它,我们将使用pbump(n)
将pptr()
重新定位为n个字符,n可以是正数也可以是负数。请注意,该流将写入epptr()
的前一个内存块,但不会写入epptr()
。
pbase() pptr() epptr()
| | |
------------------------------------------------------------------------
| 'H' | 'e' | 'l' | 'l' | 'o' | | | | | | |
------------------------------------------------------------------------
| |
--------------------------------------------------------
|
allocated memory for the buffer
figure 2: output buffer (put area) with sample data
Get Area使用以下三个指针指定(图3):
eback()
或后端,开始获取区域egptr()
或结束Get指针,Get区域结束gptr()
或获取指针,要读取的位置
setg(eback, gptr, egptr)
函数设置。请注意,该流将读取egptr()
的前一个内存块,而不是egptr()
。
eback() gptr() egptr()
| | |
------------------------------------------------------------------------
| 'H' | 'e' | 'l' | 'l' | 'o' | ' ' | 'C' | '+' | '+' | | |
------------------------------------------------------------------------
| |
--------------------------------------------------------
|
allocated memory for the buffer
figure 3: input buffer (get area) with sample data
现在我们已经讨论了在创建自定义流缓冲区之前需要了解的几乎所有内容,是时候实现它了!我们将尝试以类似std::cout
!
让我们从构造函数开始:
MyBuffer() {
setg(inbuf+4, inbuf+4, inbuf+4);
setp(outbuf, outbuf+9);
}
在这里,我们将所有三个get指针都设置到一个位置,这意味着没有可读字符,当需要输入时强制underflow()
。然后我们以这种方式设置PUT指针,这样流就可以写入除最后一个元素之外的整个outbuf
数组。我们将保留它以备将来使用。
现在,让我们实现sync()
方法,该方法在刷新输出时调用:
int sync() {
int return_code = 0;
for (int i = 0; i < (pptr() - pbase()); i++) {
if (std::putchar(outbuf[i]) == EOF) {
return_code = EOF;
break;
}
}
pbump(pbase() - pptr());
return return_code;
}
这很容易完成它的工作。首先,它确定要打印的字符数量,然后逐个打印并重新定位pptr()
(放置指针)。如果任何字符为EOF,则返回EOF或-1,否则返回0。
但如果PUT区域已满怎么办?因此,我们需要overflow()
方法。让我们来实现它:
int_type overflow(int_type ch) {
*pptr() = ch;
pbump(1);
return (sync() == EOF ? EOF : ch);
}
不是很特别,这只是将额外的字符放入outbuf
保留的最后一个元素中,并重新定位pptr()
(放置指针),然后调用sync()
。如果sync()
返回EOF,则返回EOF,否则返回额外的字符。
现在一切都完成了,除了输入处理。让我们实现underflow()
,当读取输入缓冲区中的所有字符时调用:
int_type underflow() {
int keep = std::max(long(4), (gptr() - eback()));
std::memmove(inbuf + 4 - keep, gptr() - keep, keep);
int ch, position = 4;
while ((ch = std::getchar()) != EOF && position <= 10) {
inbuf[position++] = char(ch);
read++;
}
if (read == 0) return EOF;
setg(inbuf - keep + 4, inbuf + 4 , inbuf + position);
return *gptr();
}
有点难懂。让我们看看这是怎么回事。首先,它计算它应该在缓冲区中保留多少字符(最多4个),并将其存储在keep
变量中。然后,它将最后keep
个数字字符复制到缓冲区的开头。这样做是因为可以使用std::basic_iostream
的unget()
方法将字符放回缓冲区。程序甚至可以读取下一个字符,而不需要使用std::basic_iostream
的peek()
方法提取它。在放回最后几个字符后,它读取新字符,直到它到达输入缓冲区的末尾或获得EOF作为输入。如果没有读取字符,则返回EOF,否则继续。然后,它重新定位所有获取指针,并返回读取的第一个字符。
MyStream
,以便它使用我们的流缓冲区。因此,我们更改了私有buffer
变量:
...
private:
MyBuffer buffer;
public:
...
您现在可以测试自己的流,它应该从终端获取输入并显示输出。
请注意,此流和缓冲区只能处理基于char
的输入和输出。您的类必须从相应的类派生以处理其他类型的输入和输出(例如,对于宽字符,std::basic_streambuf<wchar_t>
)并实现成员函数或方法,以便它们可以处理该类型的字符。
这篇关于如何在C++中创建既处理输入又处理输出的流?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!
本文标题为:如何在C++中创建既处理输入又处理输出的流?
- 如何提取 __VA_ARGS__? 2022-01-01
- 从父 CMakeLists.txt 覆盖 CMake 中的默认选项(...)值 2021-01-01
- 哪个更快:if (bool) 或 if(int)? 2022-01-01
- GDB 不显示函数名 2022-01-01
- 将函数的返回值分配给引用 C++? 2022-01-01
- DoEvents 等效于 C++? 2021-01-01
- 使用 __stdcall & 调用 DLLVS2013 中的 GetProcAddress() 2021-01-01
- OpenGL 对象的 RAII 包装器 2021-01-01
- XML Schema 到 C++ 类 2022-01-01
- 将 hdc 内容复制到位图 2022-09-04