简介
流缓冲区是一种I/O缓冲区,接口由basic_streambuf定义。针对字符型别char和wchar,标准程序库分别提供预先定义好的流缓冲区(streambuf)和宽字符流缓冲区(wstreambuf)。尤其是在特殊通道上,各类可以当做基类。
- 函数eback(),gptr()和egptr()构成了read(input)缓冲区的界面。
- 函数pbase(),pptr(),epptr()构成了write(output)缓冲区的界面。
pbase()是指output stream缓冲区的起始位置。 pptr()是当前写入位置。 epptr()是output 缓冲区的结尾。 pbase()至pptr()之间的序列字符已被写至相应的输出通道,但未被清空。
输出缓冲区
对于程序员和程序开发者来说,类basic_streambuf仅仅是发送(sent)和提取(extracted)字符的地方。通常有两个公共函数,用于写入字符。
int streambuf::sputc( int nCh );
int streambuf::sputn( const char* pch, int nCount );
函数sputc发生错误时,会返回traits_type::eof()。
成员函数sputc()可用来向缓冲区中写入一个字符,如果当时有一个空的改写位置,字符就会被复制到该位置上。之后,指向当前改写位置的那个指针就会加1。如果缓冲区是空的,就调用虚函数overflow()将output缓冲区的内容发送至对应的输出管道中。
所以,通过重载overflow()函数可以自定义streambuffer。
#include <IOSTREAM>
#include <streambuf>
#include <LOCALE>
#include <CSTDIO>
using namespace std;
class outbuf : public std::streambuf
{
protected:
virtual int_type overflow(int_type c)
{
if(c != EOF)
{
c = std::toupper(c ,getloc());
if(putchar(c) == EOF)
{
return EOF;
}
}
return c;
}
};
void main()
{
outbuf ob;
std::ostream out(&ob);
int num = 56;
out << "56 十六进制数值: " << std::hex << std::showbase << num << endl;
}
上面是一个没有缓冲区的demo。因为缓冲区为空,每输出一个字符都会调用一次overflow()函数,在overflow中直接将字符输出。
#include <iostream>
#include <io.h>
#include <streambuf>
#include <cstdio>
using namespace std;
static const int bufferSize = 10;
class outbuf : public std::streambuf
{
protected:
char buffer[bufferSize];//定义一个数组表示缓冲区。
public:
outbuf(){
setp(buffer ,buffer + bufferSize - 1);//设置streambuf对象的缓冲区;
}
virtual ~outbuf(){
sync();//程序退出时,将数组中的残余数据输出至外部设备。
}
protected:
int flushBuffer()
{
int num = pptr() - pbase();
if(write(1 ,buffer , num) != num)//输出。
{
return EOF;
}
pbump(-num);//将当前写指针清空,指向缓冲区的开始位置。
return num;
}
virtual int_type overflow(int_type c)
{
/*
注意setp(buffer ,buffer + bufferSize - 1),
所以pptr()==epptr()的时候,(也就是调用underflow的时候),
pptr()指向缓冲区的最后一个位置(数组的最后一个位置)。所以pptr()指向有效内存。
*/
if(c != EOF){
*pptr() = c;
pbump(1);//使当前位置指向数组最后一个元素之后。
}
if(flushBuffer() == EOF)
{
return EOF;
}
return c;
}
virtual int sync()
{
if(flushBuffer() == EOF)
{
return -1;
}
return 0;
}
};
class fdostream : public std::ostream
{
protected:
outbuf buf;
public:
fdostream(int fd):ostream(0)
{
rdbuf(&buf);//设置streambuf对象。
}
};
void main()
{
fdostream out(1);
out << "51 hexadecimal: " << std::hex << std::showbase << 51 << endl;
}
输入缓冲区
函数sgetc()和sbumpc()可以从streambuf中读取一个字符,不同之处是sbumpc()会使当前指针后移,而sgetc()仅返回当前字符。
如果缓冲区为空,就没有可用字符了。缓冲区必须重新补给。如果没有可用字符,函数sbumpc()会调用虚函数uflow(),而uflow()的默认行为是调用underflow(),移动“读取指针”。
需要说明一下的时,eback()返回缓冲区中有效数据的起始位置。epptr()返回缓冲区有效数据末端位置。pptr()返回当前读取位置。这里需要注意的是,eback()并不一定等于pptr(),因为为了支持回退,eback()和pptr()之间存储已经读取过的字符。而epptr()并不一定是缓冲区数组的最后一个元素,因为可能没有从输入设备读取这么多的数据。
在给出demo之前,由于输入缓冲区支持回退,也就是读取指针可以左移,读取之前读取过的字符。所以,在重写函数underflow自定义自己的streambuf时,也需要支持这种特性。
#include <iostream>
#include <streambuf>
#include <cstring>
#include <io.h>
using namespace std;
static const int bufferSize = 10;
static const int maxBackNum = 4;
class inbuf : public std::streambuf
{
protected:
char buffer[bufferSize];
public:
inbuf()
{
setg(buffer + maxBackNum ,buffer + maxBackNum ,buffer + maxBackNum);
}
protected:
virtual int_type underflow()
{
if(gptr() < egptr())
{
return *gptr();
}
int numputback;
numputback = gptr() - eback();
if(numputback > maxBackNum)
numputback = maxBackNum;
memcpy(buffer + maxBackNum - numputback ,gptr() - numputback ,numputback);
int num ;
num = read(0 ,buffer + maxBackNum ,bufferSize - maxBackNum);
if(num < 0)
return EOF;
setg(buffer + (maxBackNum - numputback) ,buffer + maxBackNum ,buffer + maxBackNum + num);
return *gptr();
}
};
void main()
{
inbuf ib;
std::istream in(&ib);
char c;
for(int i = 1 ;i <= 20 ;i++)
{
in.get(c);
cout << c << flush;
if(i == 8)
{
in.unget();
in.unget();
}
}
cout << endl;
}