1、第八章输入输出流inputoutputstream的基本概念及原理第八章 输入输出流(inputoutput stream)的基本概念及原理8.1 预定义数据类型的输入输出8.1.1 基本情况及其优点C+输入输出流的优点:(一)重载运算符“”能以函数重载的形式极大地扩大用途,在输入输出流中充分体现多态性。C语言的输入输出系统本来就灵活性大、功能比较完善。但它有一个较大缺点:无法处理众多的用户自定义数据类型(主要是类及其对象)。例如,有一个结构类型exampl如下:struct exampl int j; char str80; str_ex;如欲输出此结构对象str_ex的两个成员的内容,因而
2、笼统地使用如下输出语句printf,printf(“%exampl”,str_ex);则将会出现编译错误。而C+的输出/输入系统则能很好地解决这个问题。(二)类型安全(type-safe)例1C语言输出语句中的类型错误(第一章已看过)include void main( ) int i=3; double d=4.4; printf(“%dt%fn”, i, d);运行结果:3 4.400000 对!但如写错为: printf(“%dt%dn”, i, d);则编译时不出错,但运行结果错,为:3 262144 但在C+中只须写 couti“ ”dendl;而不必写出变量类型!就能得出3 4.4
3、,对!始终不会出错!(三)通过缓存增加功能。(四)附带优点是书写方便以及显示中没有冗余字符,能自动略去浮点数尾数中的零(但如用户希望显示多余的零,也可以做到)。C+的输入输出系统是对流的操作,也即操作数据使其流向对象,或从对象流出。什么是流?流是从源头到目的的数据流动。当键入字符时,字符从键盘流入程序中;当将数据写入磁盘文件中时,数据自程序流动至磁盘上。C+输入输出流库是使用继承方法建立起来的一个输入输出类库,它具有两个平行的基类,即streambuf类和ios类。所有其它流类都是从它们直接或间接地派生出来的。streambuf类用于提供物理设备的接口,它提供缓冲或处理流的通用方法。它作为一个
4、虚基类,具有类层次如下:streambufconbuffilebufstrstreambuf图 8.1ios类及其派生类用于为用户提供使用流类的接口。它使用streambuf完成检查错误的格式化输入输出操作,并支持对streambuf的缓冲区进行输入输出时的格式化或非格式化转换。ios类作为流库中的一个虚基类,派生出许多派生类,其主要层次如下:i o sistreamfstreambaseostreamfstreamistream-withassignostream-withassigniostreamiostream-withassign图 8.28.1.2 预定义流(标准流)的基本原理预定
5、义输出输入流涉及较多的头文件有四个:ios.h,istream.h,ostream.h和iostream.h。下面分别介绍。8.1.2.1 输出流的基本概念流输出运算符“”是在头文件ostream.h的class ostream中定义的。从图8.2可以看出,class ostream是从class ios中派生出来的。因此下面先看一下用于定义class ios的头文件ios.h。先看ios.h:/*ios.h - definitions/declarations for the ios class.*/class ios public: enum io_state goodbit = 0x00
6、, eofbit = 0x01, failbit = 0x02, badbit = 0x04 ; enum open_mode in = 0x01, out = 0x02, ate = 0x04, app = 0x08, trunc = 0x10, nocreate = 0x20, noreplace = 0x40, binary = 0x80 ; enum seek_dir beg=0, cur=1, end=2 ; enum skipws = 0x0001, left = 0x0002, right = 0x0004, internal = 0x0008, dec = 0x0010, oc
7、t = 0x0020, hex = 0x0040, showbase = 0x0080, showpoint = 0x0100, uppercase = 0x0200, showpos = 0x0400, scientific = 0x0800, fixed = 0x1000, unitbuf = 0x2000, stdio = 0x4000 ; static const long basefield; / dec | oct | hex static const long adjustfield; / left | right | internal static const long flo
8、atfield; / scientific | fixed ios(streambuf*); / differs from ANSI virtual ios(); inline long flags() const; inline long flags(long _l); inline long setf(long _f,long _m); inline long setf(long _l); inline long unsetf(long _l); inline int width() const; inline int width(int _i); inline ostream* tie(
9、ostream* _os); inline ostream* tie() const; inline char fill() const; inline char fill(char _c); inline int precision(int _i);inline int precision() const;/ inline operator void*() const; operator void *() const if(state&(badbit|failbit) ) return 0; return (void *)this; inline int operator!() const;
10、protected: ios();ios(const ios&); / treat as private int state;long x_flags; int x_precision; char x_fill; int x_width;以后讨论到有关class ios的问题时,可参照以上内容。再看ostream.h:/*ostream.h - definitions/declarations for the ostream class*/class ostream : virtual public ios public: ostream(streambuf*); virtual ostrea
11、m(); ostream& flush(); ostream& endl(); inline ostream& operator(ostream& (_cdecl * _f)(ostream&);inline ostream& operator(ios& (_cdecl * _f)(ios&); ostream& operator(const char *);inline ostream& operator(const unsigned char *);inline ostream& operator(const signed char *);inline ostream& operator(
12、char); ostream& operator(unsigned char);inline ostream& operator(signed char); ostream& operator(short); ostream& operator(unsigned short); ostream& operator(int); ostream& operator(unsigned int); ostream& operator(long); ostream& operator(unsigned long);inline ostream& operator(float); ostream& ope
13、rator(double); ostream& operator(long double); ostream& operator(const void *); ostream& operator(streambuf*);inline ostream& put(char); ostream& put(unsigned char);inline ostream& put(signed char); ostream& write(const char *,int);inline ostream& write(const unsigned char *,int);inline ostream& wri
14、te(const signed char *,int); ostream& seekp(streampos); ostream& seekp(streamoff,ios:seek_dir); streampos tellp();protected: ostream(); ostream(const ostream&); / treat as private ostream& operator=(streambuf*); / treat as private ostream& operator=(const ostream& _os) return operator=(_os.rdbuf();
15、int do_opfx(int); / not used void do_osfx(); / not usedprivate: ostream(ios&); ostream& writepad(const char *, const char *); int x_floatused;附注:在C+的老版本中, _Cdecl是预定内部宏,供编译系统使用。当它有定义(例如等于1)时,标示只选择C+语言而不选择Pascal语言。而另一个宏_Pascal有定义时,则标示同时选择Pascal语言。class ostream_withassign : public ostream public: ostre
16、am_withassign(); ostream_withassign(streambuf* _is); ostream_withassign(); ostream& operator=(const ostream& _os) return ostream:operator=(_os.rdbuf(); ostream& operator=(streambuf* _sb) return ostream:operator=(_sb); ;extern ostream_withassign cout;extern ostream_withassign cerr;extern ostream_with
17、assign clog;从以上文件看出,class ostream中所定义的各个流输出运算符“”是相对于各个预定义数据类型进行重载的。也即,他们可用于各种预定义数据类型。试看以下我们很熟悉的例子:例1用于三种最常用预定义数据类型的输出运算符/ out_1.cpp/ copied from p.341 of Wangs book#include void main( ) int i=10, j=45; double x=12.34, y=56.78; char * str=Windows; couti=i,j=jendl; coutx=x,y=yendl; coutstr=strendl;/*
18、Results:i=10,j=45x=12.34,y=56.78str=Windows*/在以上程序中,输出流对象cout顺序地调用流输出运算符”,将一条语句中的多个不同类型的数据依次输出。输出流对象cout还可用于输出各类函数的运行结果,如下例:例2输出各类函数的运行结果/ out_2.cpp#include int fun1( ) int j = 111; return j;double fun2( ) double j = 13.57; return j;void main() coutThe first fun has fun1( ); the second fun has fun2(
19、 )endl;/* Results:The first fun has 111; the second fun has 13.57*/从图8.2和ostream.h中可看出,从class ostream中派生出class ostream-withassign,而ostream-withassign建立了三个对象:cout、clog和cerr。这就是标准库中定义的三个输出流对象。其中cout是标准的输出流对象,而cerr和clog则与标准错误流有关。其中,cout和clog是缓冲输出流(buffered output stream)对象,发送给它们的数据暂存在缓存内,只当满足8.1.2.3中所述
20、条件之一时才将数据输出至标准设备;而cerr是非缓冲输出流对象, 发送给它的任何数据在执行完一条语句后立即输出。现在简要地看一下输出流对象cout、clog和cerr级联地运行的过程。从以上图8.2和ostream.h中可看出:cout、clog和cerr都是class ostream-withassign的对象,而class ostream-withassign是从class ostream中派生出来的,因此class ostream是这些输出流对象的基类。以cout为例,这是对象cout调用函数operator ( ),也即cout.operator ( )。按照支配规则,也即对象cout
21、调用了基类的成员函数operator ( )。函数operator ( )原型的返回值是class ostream对象的引用,而实际上则返回class ostream的派生类class ostream-withassign的对象cout的引用。此返回的对象可继续调用下一个函数operator ( )。直至整条语句末尾。如只从表面看,有些现象看不清楚,见以下两例:例1变量值出错/ out_10.cpp/ trap of cout, clog and cerr statements#include void main( )int i=10;coutfirst i=i,second i=i+endl
22、;/* Results:first i=11,second i=10*/以上程序中,原来指望所显示的两个i都等于10,结果不然。为何?例2第七章7.1“程序运行错误及其处理”中例1程序sqrt_negative_1.cpp。该程序中主函数第二语句“coutSqrt of -4 is sqroot(-4)endl;”出错,但并不显示字符串“Sqrt of -4 is”。例3有些输出语句并不立即输出数据。/ out_4.cpp/ To use endl( ) function to display output contents#include #include / for getch()void
23、 main() char *c12=12345 ; coutc12ABCD c12; getch(); coutABCDEFGendl;/* Results:只在击键一次之后,才能显示以下内容:12345 ABCD 12345 ABCDEFG*/以上程序中,主函数的第二语句运行完后,并无输出,也即屏幕上并无显示。何故?这些都要求、也只能从汇编语言的更深层次来解释。整句cout、clog或cerr输出语句的运行都可分为三个阶段:入栈阶段、缓存阶段和输出阶段。详述如下。第一阶段 - 入栈阶段此阶段由主函数main( )完成。主函数按照逆顺序将整条输出语句中需要输出的各项数据或数据的存放地址压入栈区
24、。其中:(a) 如果数据是单个数据(无论预定义类型或是用户自定义类型),则直接将该项数据压入栈区。其中整型数据或单个字符占4个字节(堆栈按照4字节边界原则运行,4-byte boundary),double型数据占8个字节,类的对象则可能占更多空间,等等。(b) (b)如果数据是数组,则系统将该数组放置于数据区内,并将此数组首址压入栈区。(c)如果数据是一个函数运行后的返回值,则系统执行该函数,并根据该函数返回只的数值类型,按照上两项规定来决定将该数据或是数据地址压入栈区。第二阶段 - 缓存阶段此阶段由重载运算符函数完成。它取出栈区内所存数据或按数据地址找到数据本身,并将该项完整的数据存入位于
25、堆区内的缓存。如缓存是空的,则该数据被放置于缓存的首址(例如8.1.2.2“非缓冲输出流” );如缓存内已存有其它数据,则该数据被放置于缓存内已有数据之后(例如8.1.2.3“缓冲输出流” )。第三阶段 - 输出阶段此阶段由另一些重载运算符函数调用flush函数来完成,它们最终调用硬件接口的函数_imp_WriteFile,直接将数据输出至标准设备(显示屏幕)。由于函数_imp_WriteFile无法访问堆区,因此必须再将数据从堆区内再转存至栈区内,才能供函数_imp_WriteFile调用后输出。flush函数顺序地从缓存(堆区)内取出所存全部数据,从新压入至栈区另外的地址,以备输出。接着函
26、数_imp_WriteFile直接将位于栈区新地址处的全部数据输出至标准设备(显示屏幕)。下面将分别介绍缓冲输出流对象和非缓冲输出流对象,它们在执行输出语句时的差别在于:(1)每当非缓冲输出流对象cerr语句运行时,在入栈阶段之后,它调用重载运算符函数,接连执行缓存阶段和输出阶段,直接将全部数据输出至标准设备(显示屏幕)。(2)每当缓冲输出流对象cout或clog调用重载运算符函数时,一般情况下只执行入栈阶段和缓存阶段,只将数据存入缓存而不输出数据。只当满足8.1.2.3中所述条件之一时才执行输出阶段的操作,通过运算符函数,从堆区中的缓存内取出所存全部数据,调用用于控制硬件接口的函数_imp_
27、WriteFile,直接将全部数据输出至标准设备(显示屏幕)。 8.1.2.2 非缓冲输出流的运行机理cerr是非缓冲输出流对象,发送给它的所有数据在整条输出语句(不是半条语句)运行后立即输出至标准设备。例1非缓冲输出流对象cerr一次将整条语句的数据顺序地输出至标准设备。/ out_3.cpp/ cerr outputs the contents of one statement immediately#include int fun1( ) int j = 512; return j;double fun2( ) double j = 12.34; return j;void main()
28、 cerrThe first result is fun1( ); the second result is fun2( );/* Results:The first result is 512;the second result is 12.34*/以上程序out_3.cpp中只有一句cerr输出语句,整个语句的运行分为两大步:第一大步就是入栈阶段;第二大步包括缓存阶段和输出阶段。第一大步 - 入栈阶段主函数按照逆顺序将整条语句中需要输出的各项数据或数据的存放地址压入栈区。其顺序操作如下:(a) 系统执行函数fun2( ),其运行结果为12.34。此数据被压入栈区,占8个字节。(b) (b)第二项被压入栈区的数据是字符串the second result is 在数据区内的地址(0042a038)。(c)第三项被压入栈区的数据是字符;(其ASCII码值为3Bh),占4个字节。(d)系统接着执行函数fun1( ),其运行结果512被压入栈区,占4个字节。(e)第五项也即最后一项被压入栈区的数据是字符串the first result is 在数据区内的地址(0042a01c)。Table_out_1:程序out_3.cpp入栈阶段后的栈区内容0x0042a01c5123Bh0x0042a03812.34低地址高地址请注意:以上数据压入栈区的顺
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1