首页 理论教育 自定义缓冲区教程:C++STL详解

自定义缓冲区教程:C++STL详解

时间:2023-10-25 理论教育 版权反馈
【摘要】:缓冲区的主要接口由3个指针构成。对于程序开发者而言,实现自定义输出流缓冲区尤为重要。此时,默认构造函数会自动维护“输出缓冲区”的指针设置为0或NULL。鉴于以上阐述,在实现自定义流缓冲区类时,一定要派生出保护类型虚成员函数over- flow()。例7-8例7-8的执行结果为:4.自定义输入缓冲区输入机制和输出机制是基本相同的。

自定义缓冲区教程:C++STL详解

1.流缓冲区的简述

流缓冲区是一种I/O缓冲区,其接口由类basic_streambuf定义。针对字符型别char和wchar,C++ STL分别提供了预先定义好的流缓冲区(streambuf)和宽字符流缓冲区(wstre-ambuf)。尤其在特殊通道上通信时,各类可以作为基类。要实现这一点,必须对流缓冲区的操作有所了解。缓冲区的主要接口由3个指针构成。eback()、gptr()和egptr()函数返回的指针构成了read(input)缓冲区的界面。pbase()、pptr()和epptr()函数返回的指针构成了write(output)缓冲区的接口。这些指针分别由I/O操作操控,后者会导致相关I/O通道上的相关响应。精确操作将分为读取和写入。

对于程序开发者而言,实现自定义输出流缓冲区尤为重要。

输出流缓冲区一般由3个指针维护。这3个指针分别由pbase()、pptr()和epptr()函数获得。它们表示的意义为:

1)pbase()是指输出流缓冲区的起始位置。

2)pptr()是当前写入位置。

3)epptr()是输出流缓冲区的结尾,指向“得被缓冲之最后一个字符”的下一个位置。

pbase()~pptr()的序列字符已被写至相应的输出通道,但尚未清空。

成员函数sputc()可以写入一个字符。如果当时有个空的改写位置,字符就会被复制到该位置上。之后,指向当前改写位置的那个指针会加1。如果缓冲区是空的,就调用虚over-flow()函数将output缓冲区的内容发送至对应的输出通道中。这个函数能有效地将字符送至某种“外部表述”。基类basic_streambuf所实例化的overflow()函数只返回end-of-file(),表示没有更多字符被写入。

成员函数sputn()可用来一次写入多个字符。该函数把实际任务委派给虚xsputn()函数。后者可针对“多个字符”做出更有效的操作。类basic_streambuf中的xsputn()对每个字符调用sputn()。改写xsputn()不是必要的。通常,“同时写入多个字符”会比“一次写入一个字符”效率高得多,多用sputn()优化对字符序列的处理。

同时,广大程序员也应该认识到,对一个流缓冲区写入数据时不一定要采取缓冲行为,而可以令字符即刻发送。此时,默认构造函数会自动维护“输出缓冲区”的指针设置为0或NULL。

2.虚overflow()函数详解

通过上述描述,给出以下实例。例中并未采取缓冲行为,而是对每个字符都调用over-flow()。

简要介绍一下虚overflow()函数。Visual C++软件开发环境的类basic_streambuf类的保护成员虚overflow()函数的说明中提到:overflow()是保护型虚成员函数,每当有新的字符被插入至空的缓冲区内时,overflow()即被调用。该函数的默认返回值是traits_type::eof。如果函数调用失败,将返回traits_type::eof或抛出一个异常;否则,该函数的返回值为traits_type::not_eof()。

其原型为:

参数_Meta并不和traits_type::eof相等,在该函数被调用时,将努力插入字符型变量_Meta,因为参数是int_type型,执行过程中被插入到输出缓冲区中的数值是进行了数值类型转换的,即traits_type::to_char_type()。以上功能的实现是通过多种方式实现的:

1)如果“写”的位置有效,元素被存储至写的位置,并增加输出缓冲区的下一个指针。

2)通过分配新的或额定的存储空间至输出缓冲区,以确保每个“写”的位置均有效。

3)通过“写出”至一些外部目标地址,部分或所有元素均保证在输出流缓冲区的起始位置和下一个指针之间。

虚overflow()和sync()、underflow()函数,定义了输出流缓冲区类的特性。每个类会采用不同的方式执行overflow()函数,但调用流类的接口是相同的。overflow()函数是最频繁调用的函数之一,通常被流缓冲区类的函数调用,例如sputc()和sputn()。其他流类也可随时调用overflow()函数。

overflow()函数耗用输出区域中指针pbase和指针pptr之间的字符,并重新初始化这些区域。overflow()函数必须耗用字符nCh,或它可能选择将字符放置入新的输出区域,以便于在下一次调用时被耗费。

在不同的派生类中,耗用的定义是多种多样的。例如,文件缓冲区类filebuf“写字符”到文件时,类streambuf保持这些字符在缓冲区中,并响应overflow()的调用扩张缓冲区。通过释放旧的缓冲区,并使用新的缓冲区,更大的缓冲区取代旧的缓冲区,实现扩张内存的目的。同时,指针的调整也是必需的。

鉴于以上阐述,在实现自定义流缓冲区类时,一定要派生出保护类型虚成员函数over- flow()。

3.自定义输出缓冲区最简单的实例

下面实现一个最简单的自定义缓冲区实例。此例题是摘自《C++标准程序库》一书。

例7-7

例7-7的输出结果为:

提示(www.xing528.com)

getloc()函数是获取基本流缓冲区类对象的场合(或场景)。

对于较复杂的自定义输出以及其他任意目标的写入操作,例如文件、socket连接名或流缓冲区,若向目标端改写数据,只需实例相应的overflow()即可。若有必要,也需要实例化xsputn()函数,有助于提升效率。为方便构造流缓冲区,实例化特殊类,用于构造函数参数传递给相应的流缓冲区。例7-13中使用fprintf()函数写入文件,使用ostream类的继承类,用以维护流缓冲区。此外,如果需要具备缓冲能力的流缓冲区,“写”缓冲区需要使用setp()函数初始化,并使用sync()函数实现同步。

setp()函数的原型为:

其作用是保存参数_Pbeg和_Pend限定的输出缓冲区内容。

sync()函数的原型为:

其作用是实现控制流和外部关联流(设备)之间的同步。

例7-8

例7-8的执行结果为:

4.自定义输入缓冲区

输入机制和输出机制是基本相同的。对输入而言,可能不进行最后的读取操作。sungetc()函数或sputbackc()函数可用于存储流缓冲区最后一次读取前的状态(可能需要读取下一字符但并不移动读取位置)。实例化“从stream缓冲区中读取”操作和实例化“向stream缓冲区写入数据”的操作相比,必须改写(重载)更多的函数。

同样,流缓冲区以3个指针维护一个read缓冲区,这些指针通过成员函数eback()、gptr()和egptr()获取。

1)eback()指的是input缓冲区的起始位置,或者回退区的尾端。如果不采取特殊措施,字符最多只能被回退到这个位置。

2)gptr()是当前的“读取位置”。

3)egptr()是input缓冲区的尾端。

读取位置和结束位置之间的字符已经从外部表述装置被传至程序内存,但仍等待着程序的处理。sgetc()函数或sbumpc()函数可以读取单一字符。两者的差别在于后者会令“读取指针”前进,而前者不会。如果缓冲区读取完毕,就不再有可用字符了。缓冲区必须重新获得补给。这项工作可由虚函数underflow()完成,underflow()函数负责读取数据。如果没有可用字符,sbumpc()函数会调用虚函数uflow(),而uflow()的默认行为即调用underflow(),移动(前进)“读取指针”。基类basic_streambuf中对underflow()的默认做法是令它返回EOF,这意味着不可能以此默认版本读取字符。

sgetn()函数用于一次读取多个字符。这个函数把任务委派给虚函数xsgetn(),后者的默认做法是简单地对每个字符调用sbumpc()。像重载函数xsputn()一样,通过改进xsgetn()优化多个字符的读取过程。

输入和输出还有不同,对于输入而言,仅仅改写一个函数是不够的,必须建立缓冲区,至少实例化函数underflow()和uflow()。因为underflow()不会将“读取指针”移动到当前字符之后,只能通过调用sgetc()移至下一字符,并需以缓冲区操作函数或uflow()完成。任何一个具备字符读取功能的流缓冲区必须实例出underflow()。若underflow()和uflow()均被实例化了,就没必要建立缓冲区了。

成员函数setg()可以建立一个read()缓冲区。该函数有三个参数,依次如下:

1)一个指针指向缓冲区头部(eback())。

2)一个指针指向当前读取位置(gptr())。

3)一个指针指向缓冲区尾部(egptr())。

和setp()不同的是,setg()包括三个参数,这是必需的。它们能定义出用来储存“将被回退给stream”的字符空间。因此,一旦指向read缓冲区的指针已经被设定好,会有部分字符被读取但仍存放在缓冲区内。运用sputbackc()和sungetc()可将字符回退到read缓冲区中,sputbackc()是以回退字符作为参数,并确保该字符确实是被读取的字符。如果可能,这两个函数会将读取指针退回一步。只有当读取指针不指向read缓冲区头部时,才能实现。如果到达缓冲区头部,试图退一字符,虚函数pbackfail()会被调用。通过改写函数可以实例出“即使在这种情况下也能恢复原读取位置”的机制。基类basic_streambuf并未定义相应操作,实际上不可能回退任意字符。如果不使用缓冲区的streams,pbackfail()函数应该被实例化出来,这些streams通常假设至少有一个字符可以被回退。若新缓冲区只用于读取,会产生新的问题,即缓冲区没有将旧数据保存下来,连一个字符也无法回退。实例化undereflow()时经常把当前缓冲区的最后若干个字符移到头部,之后再添加新读取的字符,且允许在调用pbackfail()之前回退一些字符。

例7-9

例7-9的输出结果为:

总结

读者认真阅读上述几个例题,体会如何使用自定义输出缓冲区和自定义输入缓冲区取代cout和cin,并认真体会各函数的用法。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈