STL之自定义缓冲区

简介

流缓冲区是一种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 
#include 
#include 
#include 

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 
#include 
#include 
#include 
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 
#include 
#include 
#include 
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;
}



你可能感兴趣的:(STL)