面向对象程序设计 ch10.ppt
第第10章章运算符重载及流类库运算符重载及流类库在建立了自己的类以后,在建立了自己的类以后,C+允许程序员重新定义允许程序员重新定义C+中中已有的运算符,通过运算符重载已有的运算符,通过运算符重载,就可像处理基本数据类就可像处理基本数据类型那样使用它们。型那样使用它们。为了面向对象编程的需要,为了面向对象编程的需要,C+提供了一个用于输入输出提供了一个用于输入输出(I/O)操作的类体系,这个类体系提供了对预定义类型进操作的类体系,这个类体系提供了对预定义类型进行行I/O操作的能力,程序员也可以利用这个类体系进行自操作的能力,程序员也可以利用这个类体系进行自定义类型的定义类型的I/O操作。操作。本章将简要介绍运算符重载的基础知识、流类库的概念及本章将简要介绍运算符重载的基础知识、流类库的概念及使用流类库进行文件存取的基本方法。使用流类库进行文件存取的基本方法。主要内容主要内容10.1运算符重载运算符重载10.2流类库流类库10.3文件流文件流10.4文件读写综合实例文件读写综合实例10.1运算符重载运算符重载本节首先引入运算符重载的必要性,然后讨论类运算符和本节首先引入运算符重载的必要性,然后讨论类运算符和友元运算符的异同。友元运算符的异同。10.1.1重载对象的赋值运算符重载对象的赋值运算符编译器在默认情况下为每个类生成一个默认的赋值操作,编译器在默认情况下为每个类生成一个默认的赋值操作,用于同类的两个对象之间相互赋值。默认的含义是逐个为用于同类的两个对象之间相互赋值。默认的含义是逐个为成员赋值,即将一个对象的成员的值赋给另一个对象相应成员赋值,即将一个对象的成员的值赋给另一个对象相应的成员,这种赋值方式对于有些类可能是不正确的。假设的成员,这种赋值方式对于有些类可能是不正确的。假设类类str的数据成员是的数据成员是“char*st”,下面语句下面语句strs1(hello),s2(world);s2=s1;经赋值后,经赋值后,s2.st和和s1.st是同一块存储地址(如图是同一块存储地址(如图7.1所示)。所示)。当当s2和和s1的生存期结束时,存储的生存期结束时,存储“hello”的变量被删除的变量被删除2次,这是个严重的错误。另外,对于次,这是个严重的错误。另外,对于“s1=s1”的情况,的情况,也应不执行赋值操作。因此,程序必须为也应不执行赋值操作。因此,程序必须为str类定义自己的类定义自己的赋值操作赋值操作“=”。这个操作应该能够保证。这个操作应该能够保证s1.st=s2.st,但两但两者各自具有自己的存储地址(如图者各自具有自己的存储地址(如图7.2所示)。如果发现所示)。如果发现本身自己赋值,则不执行赋值操作。先不管如何声明这个本身自己赋值,则不执行赋值操作。先不管如何声明这个“=”函数,使用函数,使用“赋值函数赋值函数”一词代表这个操作,则可一词代表这个操作,则可像下面这样实现它:像下面这样实现它:str&str:赋值函数赋值函数(str&a)if(this=&a)/防止防止a=a这样的赋值这样的赋值return*this;/a=a,退出退出deletest;st=newcharstrlen(a.st)+1;/申请内存申请内存strcpy(st,a.st);/将对象将对象a的数据复制一份到的数据复制一份到/申请的内存区申请的内存区return*this;/返回返回this指针指向的对象指针指向的对象这个成员函数必须使用引用参数,这个成员函数必须使用引用参数,“赋值函数赋值函数”使用符号使用符号“operator=”表示,表示,C+的关键字的关键字“operator”和运算和运算符一起使用,表示一个运算符函数,例如符一起使用,表示一个运算符函数,例如“operator+”表示重载表示重载“+”运算符。读者应将运算符。读者应将operator=从整体上视为从整体上视为一个(运算符)函数名。运算符函数将在后续章节介绍。一个(运算符)函数名。运算符函数将在后续章节介绍。这样就可将它声明为这样就可将它声明为“str&stroperator=(str&);”,即即函数函数operator=(str&)返回返回str类对象的引用。定义时记作:类对象的引用。定义时记作:str&str:operator=(str&a)。当当str类定义了赋值运算符函数之后,类定义了赋值运算符函数之后,“operator=”是类是类的成员函数名,对象的成员函数名,对象s2调用这个成员函数,函数的参数是调用这个成员函数,函数的参数是s1。写成函数调用形式如下:写成函数调用形式如下:s2.operator=(s1);这虽然是正规的函数调用写法,但要实现的是这虽然是正规的函数调用写法,但要实现的是“=”作用,作用,所以系统允许直接写成如下形式的语句:所以系统允许直接写成如下形式的语句:s2=s1;这将被这将被C+编译解释为编译解释为s2.operator=(s1);即即s2调用成员函数调用成员函数str:operator=(str&)完成赋值操作。完成赋值操作。因为这个函数返回一个引用,所以它可以用于下面这种赋因为这个函数返回一个引用,所以它可以用于下面这种赋值操作中:值操作中:s3=s2=s1;C+编译器将其解释为编译器将其解释为s3.operator=(s2.operator=(s1);下面给出下面给出str类的完整实现和测试主程序,以便读者对比分类的完整实现和测试主程序,以便读者对比分析。析。由此可见,它们虽然是函数,但完全可以不写成函数调用,由此可见,它们虽然是函数,但完全可以不写成函数调用,而采用原来的书写习惯,系统会自动按其真正的含义执行。而采用原来的书写习惯,系统会自动按其真正的含义执行。运算符重载其实就是函数重载,抓住这个实质,就很容易运算符重载其实就是函数重载,抓住这个实质,就很容易理解了。理解了。【例【例10.1】完整实现完整实现str类的例子。类的例子。#include#includeusingnamespacestd;classstrprivate:char*st;public:str(char*s);str(str&s);str&operator=(str&a);str&operator=(char*s);voidprint()coutst这这4个运算符只能用个运算符只能用类运算符来重载。类运算符来重载。10.1.3和和+运算符重载实例运算符重载实例为了加深理解,本节再结合函数调用,分别给出使用友元为了加深理解,本节再结合函数调用,分别给出使用友元运算符和类运算符的例子。运算符和类运算符的例子。其实,插入符其实,插入符“”的重载也与其他运算的重载也与其他运算符重载一样,只是它们必须作为类的友元重载,因为操作符重载一样,只是它们必须作为类的友元重载,因为操作符的左边是流而不是被操作的对象。除非违反习惯将流放符的左边是流而不是被操作的对象。除非违反习惯将流放在操作符的右边,但这样既会影响程序的可读性,也违反在操作符的右边,但这样既会影响程序的可读性,也违反了操作符重载的原则。了操作符重载的原则。插入符函数的一般形式为:插入符函数的一般形式为:ostream&operator(istream&函数的流函数的流,类名类名&对象名对象名)/函数代码函数代码return函数的流函数的流;另外,提取符函数需要返回新的值,所以应该使用引用,另外,提取符函数需要返回新的值,所以应该使用引用,即即“类名类名&对象名对象名”,不能使用,不能使用“类名类名对象名对象名”。插入符。插入符函数不返回值,所以两种方法都可以。函数不返回值,所以两种方法都可以。【例【例10.2】使用友元函数重载运算符使用友元函数重载运算符“”。#includeclasstestprivate:inti;floatf;charch;public:test(inta=0,floatb=0,charc=0)i=a;f=b;ch=c;friendostream&operator(istream&,test&);ostream&operator(ostream&stream,testobj)streamobj.i,;streamobj.f,;streamobj.ch(istream&t_stream,test&obj)t_streamobj.i;t_streamobj.f;t_streamobj.ch;returnt_stream;voidmain()testA(45,8.5,W);operator(cout,A);testB,C;cout(cin,B);operator(cin,C);operator(cout,B);operator(cout,C);运行示例如下:运行示例如下:45,8.5,WInputasifch:55.8A23.4a/假设输入两组假设输入两组5,5.8,A2,3.4,a将主函数写成上面的函数调用形式,是为了演示运算符就将主函数写成上面的函数调用形式,是为了演示运算符就是函数重载。一般在使用时,则直接使用运算符。下面是是函数重载。一般在使用时,则直接使用运算符。下面是正规的使用方式:正规的使用方式:voidmain()testA(45,8.5,W);coutA;testB,C;coutBC;coutBC;显然,运算符显然,运算符“”重载函数有两个参数,第重载函数有两个参数,第1个是个是ostream类的一个引用,第类的一个引用,第2个是自定义类型的一个对象。个是自定义类型的一个对象。这个重载方式是友元重载。另外,这个函数的返回类型是这个重载方式是友元重载。另外,这个函数的返回类型是一个一个ostream类型的引用,在函数中实际返回的是该函数类型的引用,在函数中实际返回的是该函数的第的第1个参数,这样做是为了使得个参数,这样做是为了使得“”能够连续使用。能够连续使用。例如,对于语句例如,对于语句coutab;/a,b均为自定义类型的对象均为自定义类型的对象第第1次,系统把次,系统把couta作为作为operator(cout,a);来处理,返回来处理,返回cout,紧接着又把刚返回的紧接着又把刚返回的cout连同后面的连同后面的“b”一起作为一起作为operator(cout,b);处理,再返回处理,再返回cout,从而实现了运算符从而实现了运算符“”的连续使用。的连续使用。【例【例10.3】使用类运算符重载使用类运算符重载+运算符。运算符。#includeusingnamespacestd;classnumberintnum;public:number(inti)num=i;intoperator+();/前缀:前缀:+nintoperator+(int);/后缀:后缀:n+voidprint()coutnum=numend;intnumber:operator+()num+;returnnum;intnumber:operator+(int)/不用给出形参名不用给出形参名inti=num;num+;returni;voidmain()numbern(10);inti=+n;/i=11,n=11couti=iendl;/输出输出i=11n.print();/输出输出n=11i=n+;/i=11,n=12couti=iendl;/输出输出i=11n.print();/输出输出n=12同理,如果主函数的第同理,如果主函数的第2条和第条和第5条语句使用函数调用方式,条语句使用函数调用方式,则分别为:则分别为:inti=n.operator+();i=n.operator+(0);由此可见,只要定义正确,不必再使用函数调用方式,而由此可见,只要定义正确,不必再使用函数调用方式,而直接使用运算符。直接使用运算符。【例【例10.4】使用友元运算符重载使用友元运算符重载+运算符。运算符。为友元运算符需要要修改操作数,所以必须使用引用参数。为友元运算符需要要修改操作数,所以必须使用引用参数。程序如下:程序如下:#includeusingnamespacestd;classnumberintnum;public:number(inti)num=i;friendintoperator+(number&);/前缀:前缀:+nfriendintoperator+(number&,int);/后缀:后缀:n+voidprint()coutnum=numendl;intoperator+(number&a)a.num+;returna.num;intoperator+(number&a,int)/不用给出不用给出int类型的形参类型的形参名名inti=a.num+;returni;仍然使用上面的主程序,则运行结果一样。仍然使用上面的主程序,则运行结果一样。有些有些C+编译器不区分前辍或后辍运算符,这时只能通过编译器不区分前辍或后辍运算符,这时只能通过对运算符函数进行重载时来反映其为前辍或后辍运算符。对运算符函数进行重载时来反映其为前辍或后辍运算符。注意不能够自己定义新的运算符,而只能是把注意不能够自己定义新的运算符,而只能是把C+原有的原有的运算符用到自己设计的类上面去。同时,经过重载,运算运算符用到自己设计的类上面去。同时,经过重载,运算符并不改变原有的优先级,也不改变它所需的操作数数目。符并不改变原有的优先级,也不改变它所需的操作数数目。当不涉及到定义的类对象的时候,它仍然执行系统预定义当不涉及到定义的类对象的时候,它仍然执行系统预定义的运算,只有用到自己定义的对象上,才执行新定义的操的运算,只有用到自己定义的对象上,才执行新定义的操作。作。应该根据需要进行运算符重载。不排除在某些特殊情况下应该根据需要进行运算符重载。不排除在某些特殊情况下会有一些特殊的需要,但大多数情况下不会将运算符会有一些特殊的需要,但大多数情况下不会将运算符“+”重载为两个复数相减的运算重载为两个复数相减的运算(尽管有能力这么做尽管有能力这么做)。一般总是要求运算符重载合乎习惯。一般总是要求运算符重载合乎习惯。尽管尽管C+有那么多运算符可以重载,但实际中真正需要去有那么多运算符可以重载,但实际中真正需要去重载的运算符却没有几个。所介绍的重载的运算符却没有几个。所介绍的“+”重载,结论重载,结论对对“-”完全适用。完全适用。举一反三,四则运算就不会有什么问题了。举一反三,四则运算就不会有什么问题了。10.1.4类运算符和友元运算符的区别类运算符和友元运算符的区别如果运算符所需的操作数,尤其是第一个操作数希望进行如果运算符所需的操作数,尤其是第一个操作数希望进行隐式类型转换,则该运算符应该通过友元来重载。另一方隐式类型转换,则该运算符应该通过友元来重载。另一方面,如果一个运算符的操作需要修改类对象的状态,则应面,如果一个运算符的操作需要修改类对象的状态,则应当使用类运算符,这样更符合数据封装的要求。但参数是当使用类运算符,这样更符合数据封装的要求。但参数是使用引用还是对象,则要根据运算符在使用中可能出现的使用引用还是对象,则要根据运算符在使用中可能出现的情况来决定。例如,对复数类使用友元函数重载情况来决定。例如,对复数类使用友元函数重载“+”运运算符,可以写出如下格式:算符,可以写出如下格式:friendcomplexoperator+(形参形参1,形参,形参2)/函数体定义函数体定义关键字关键字friend把把complexoperator+(形参形参1,形参,形参2)说明成说明成类的友元,使得它能够访问类类的友元,使得它能够访问类complex的私有数据。形参的私有数据。形参可以都是对象或者对象的引用,也可以一个为对象一个为可以都是对象或者对象的引用,也可以一个为对象一个为引用,这要视具体问题而定。引用,这要视具体问题而定。下面仅仅给出使用对象和引用的两种函数原型声明。下面仅仅给出使用对象和引用的两种函数原型声明。friendcomplexoperator+(complex,complex);/对象对象/对象引用对象引用friendcomplexoperator+(complex&,complex&);其实,它们函数体的定义是一样的。其实,它们函数体的定义是一样的。/使用对象的定义使用对象的定义complexoperator+(complexc1,complexc2)returncomplex(c2.real+c1.real,c2.imag+c1.imag);/使用引用定义使用引用定义complexoperator+(complex&c1,complex&c2)returncomplex(c2.real+c1.real,c2.imag+c1.imag);定义的方式也很灵活,例如也可以使用如下引用定义定义的方式也很灵活,例如也可以使用如下引用定义的方法:的方法:complexoperator+(complex&c1,complex&c2)doubler=c2.real+c1.real;doublei=c2.imag+c1.imag;returncomplex(r,i);【例例10.5】使用对象作为友元函数参数来定义运算符使用对象作为友元函数参数来定义运算符“+”的例子。的例子。因为因为C+定义了复数模板,所以这里不使用命名空间,包定义了复数模板,所以这里不使用命名空间,包含的是含的是iostream.h文件。文件。#includeclasscomplexprivate:doublereal,imag;public:complex(doubler=0,doublei=0)real=r;imag=i;friendcomplexoperator+(complex,complex);voidshow()coutreal+imagi;complexoperator+(complexa,complexb)doubler=a.real+b.real;doublei=a.imag+b.imag;returncomplex(r,i);voidmain()complexx(5,3),y;y=x+7;y=7+y;y.show();程序运行正常,因为语句程序运行正常,因为语句“y=x+7;”和语句和语句“y=7+y;”可可以分别解释为:以分别解释为:y=operator+(x,7);y=operator+(7,y);而而“7”均可通过构造函数转换均可通过构造函数转换成成complex类型的对象,使类型的对象,使其参数匹配,保证正常工作。其参数匹配,保证正常工作。如果将第如果将第1个参数换为引用,则语句个参数换为引用,则语句“y=7+y;”等价为等价为“y=7.operator+(y);”,则系统无法解释这个式子的含则系统无法解释这个式子的含义。同理,如果将第义。同理,如果将第2个参数换为引用,则语句个参数换为引用,则语句“y=x+7;”等价为等价为“y=x.operator+(7);”,系统也无法系统也无法解释这个式子的含义。解释这个式子的含义。由此可见,如果对象作为重载运算符函数的参数,则可以由此可见,如果对象作为重载运算符函数的参数,则可以使用构造函数将常量转换成该类型的对象。如果使用引用使用构造函数将常量转换成该类型的对象。如果使用引用作为参数,因为这些常量不能作为对象名使用,所以编译作为参数,因为这些常量不能作为对象名使用,所以编译系统就要报错。假如将上面友元运算符均使用引用作为参系统就要报错。假如将上面友元运算符均使用引用作为参数,则数,则“y=x+7;”和和“y=7+y;”都不能通过编译。在使用都不能通过编译。在使用运算符重载时,必须分清场合及其使用方法。运算符重载时,必须分清场合及其使用方法。同理,如果将同理,如果将【例【例10.5】中的友元运算符换为类运算符,中的友元运算符换为类运算符,假设类运算符为如下形式:假设类运算符为如下形式:complexoperator+(complexa)doubler=a.real+real;doublei=a.imag+imag;returncomplex(r,i);因为因为“y=x+7;”等价为等价为“y=x.operator+(7);”,而而“7”可通过构造函数转换成可通过构造函数转换成complex类型的对象,所以使其参类型的对象,所以使其参数匹配,从而保证正常工作。而数匹配,从而保证正常工作。而“y=7+y;”等价为等价为“y=7.operator+(y);”,则系统无法解释这个式子的含则系统无法解释这个式子的含义。如果参数使用引用形式,则义。如果参数使用引用形式,则“y=x+7;”也无法通过编也无法通过编译。译。成员运算符比友元运算符少一个参数,这是因为成员函数成员运算符比友元运算符少一个参数,这是因为成员函数具有具有this指针。指针。complex&a是形参表,是形参表,a是类是类complex的引的引用对象,所以使用对象名。用对象,所以使用对象名。a.real代表对象代表对象a的数据成员的数据成员real,而而real则为当前对象的数据成员,它用以完成两个则为当前对象的数据成员,它用以完成两个复数的加法运算。复数的加法运算。C+同时具有类运算符和友元运算符,参数也可以使用对同时具有类运算符和友元运算符,参数也可以使用对象或引用,这就给编程带来了极大的方便,但也要注意各象或引用,这就给编程带来了极大的方便,但也要注意各自适用的特定场合。自适用的特定场合。10.1.5下标运算符下标运算符“”的重载的重载运算符运算符只能用类运算符来重载。下面例子中的一位数只能用类运算符来重载。下面例子中的一位数组组iArray类完全遵照类完全遵照C+关于数组的规定:数组关于数组的规定:数组an,其其下标从下标从0开始,开始,n-1结束。结束。【例【例10.6】设计类设计类iArray,对其重载下标运算符对其重载下标运算符,并在,并在进行下标访问时检查下标是否越界。进行下标访问时检查下标是否越界。#include#includeusingnamespacestd;classiArrayint_size;int*data;public:iArray(int);int&operator(int);intsize()constreturn_size;iArray()deletedata;iArray:iArray(intn)/构造函数中构造函数中n1if(n1)coutErrordimensiondescription;exit(1);_size=n;data=newint_size;int&iArray:operator(inti)/合理范围合理范围0_size-1if(i_size-1)/检查越界检查越界coutnSubscriptoutofrange;deletedata;exit(1);returndatai;voidmain()iArraya(10);cout数组元素个数数组元素个数=a.size()endl;for(inti=0;ia.size();i+)ai=10*(i+1);for(i=0;ia.size();i+)coutsetw(5)ai;注意数组注意数组1n个元素对应的下标为个元素对应的下标为0n-1。可用可用couta-1;和和couta-1;等语句检查越界,可用等语句检查越界,可用“iArrayb(0);”语句检查构造函数。语句检查构造函数。程序运行结果如下:程序运行结果如下:数组元素个数数组元素个数=101020304050607080901009.1.610.2流类库流类库C+的流类库由几个进行的流类库由几个进行I/O操作的基础类和几个支持特操作的基础类和几个支持特定种类的源和目标的定种类的源和目标的I/O操作的类组成。操作的类组成。10.2.1流类库的基本类等级流类库的基本类等级在在C+中,输入输出是通过流来完成的。中,输入输出是通过流来完成的。C+的输出操作的输出操作将一个对象的状态转换成一个字符序列,输出到某个地方。将一个对象的状态转换成一个字符序列,输出到某个地方。输入操作也是从某个地方接收到一个字符序列,然后将其输入操作也是从某个地方接收到一个字符序列,然后将其转换成一个对象的状态所要求的格式。这看起来很像数据转换成一个对象的状态所要求的格式。这看起来很像数据在流动,于是把接收输出数据的地方叫做目标,把输入数在流动,于是把接收输出数据的地方叫做目标,把输入数据来自的地方叫做源。而输入和输出操作可以看成字符序据来自的地方叫做源。而输入和输出操作可以看成字符序列在源、目标以及对象之间的流动。列在源、目标以及对象之间的流动。C+将与输入和输出将与输入和输出有关的操作定义为一个类体系,放在一个系统库里,以备有关的操作定义为一个类体系,放在一个系统库里,以备用户调用。这个执行输入和输出操作的类体系就叫做流类,用户调用。这个执行输入和输出操作的类体系就叫做流类,提供这个流类实现的系统库就叫做流类库。图提供这个流类实现的系统库就叫做流类库。图10.1是简化是简化的流类库的基本类等级图,而不是直接的继承关系图的流类库的基本类等级图,而不是直接的继承关系图(不不是是UML图图)。它们其实都是模板类,箭头仅仅是表示类的。它们其实都是模板类,箭头仅仅是表示类的等级关系。等级关系。这个类等级在头文件这个类等级在头文件iostream.h中说明。在图中说明。在图10.1中,中,ios类中的一个指针成员指向类中的一个指针成员指向streambuf类的对象,类的对象,streambuf类管理一个流的缓冲区,由于数据隐藏和封装的需要,普类管理一个流的缓冲区,由于数据隐藏和封装的需要,普通用户一般不涉及通用户一般不涉及streambuf类,而只使用类,而只使用ios类、类、istream类和类和ostream类中所提供的公有接口,进行流的提取和插类中所提供的公有接口,进行流的提取和插入操作。入操作。ios类是类是istream类和类和ostream类的虚基类,用来提类的虚基类,用来提供对流进行格式化供对流进行格式化I/O操作和错误处理的成员函数。从操作和错误处理的成员函数。从ios类公有派生的类公有派生的istream和和ostream两个类分别提供对流进行两个类分别提供对流进行提取操作和插入操作的成员函数,而提取操作和插入操作的成员函数,而iostream类通过将类通过将istream类和类和ostream类组合起来以支持对一个流进行双向类组合起来以支持对一个流进行双向(也就是输入和输出也就是输入和输出)操作,它并没有提供新的成员函数。操作,它并没有提供新的成员函数。C+的流类库预定义了的流类库预定义了4个流,它们是个流,它们是cin、cout、cerr和和clog。事实上,可以将事实上,可以将cin视为类视为类istream的一个对象,而的一个对象,而将将cout视为类视为类ostream的对象。的对象。流是一个抽象概念,当实际进行流是一个抽象概念,当实际进行I/O操作时,必须将流和操作时,必须将流和一种具体的物理设备,比如说键盘联接起来。一种具体的物理设备,比如说键盘联接起来。C+的流类的流类库预定义的库预定义的4个流所联接起的具体设备为个流所联接起的具体设备为:cin与标准输入设备相联接与标准输入设备相联接cout与标准输出设备相联接与标准输出设备相联接cerr与标准错误输出设备相联接与标准错误输出设备相联接(非缓冲方式非缓冲方式)clog与标准错误输出设备相联接与标准错误输出设备相联接(缓冲方式缓冲方式)操作系统在默认情况下,指定标准输出设备是显示终端,操作系统在默认情况下,指定标准输出设备是显示终端,标准输入设备是键盘。在任何情况下,指定的标准错误输标准输入设备是键盘。在任何情况下,指定的标准错误输出设备总是显示终端。事实上,一直是通过键盘来使用出设备总是显示终端。事实上,一直是通过键盘来使用cin,通过显示器来使用通过显示器来使用cout的。但在某些场合,也可以的。但在某些场合,也可以将标准输入或标准输出设备指定为其他设备,比如说将文将标准输入或标准输出设备指定为其他设备,比如说将文件操作转化为流的操作,简单方便。件操作转化为流的操作,简单方便。在第在第1章已经介绍过章已经介绍过C+同时提供的两种新的格式控制方同时提供的两种新的格式控制方式,但使用式,但使用right或者或者left时,需要用时,需要用setiosflags函数进行设函数进行设置,毕竟不如置,毕竟不如“coutaabc;能将连续的能将连续的3个字符分别正确地赋给相应对象。对字符串个字符分别正确地赋给相应对象。对字符串来讲,它从读到第一个字符开始,到空格符结束。对于字来讲,它从读到第一个字符开始,到空格符结束。对于字符数组,使用数组名来整体读入。假设声明符数组,使用数组名来整体读入。假设声明“chara30;”则使用则使用“cina;”读入,读入,“couta;”输出。但对输出。但对于字符指针,尽管为它动态分配了地址,也只能采取逐个于字符指针,尽管为它动态分配了地址,也只能采取逐个赋值的方法,它并不以空格结束,但舍弃空格赋值的方法,它并不以空格结束,但舍弃空格(读到字符读到字符才计数才计数)。因为。因为字符串没有结束位,所以将字符串作为整体输出时,有效字符串没有结束位,所以将字符串作为整体输出时,有效字符串的后面将出现乱码字符串的后面将出现乱码。不过,可以用手工增加表示字不过,可以用手工增加表示字符串的结束符符串的结束符“0”来消除乱码。例如:来消除乱码。例如:char*p=newchar5;for(inti=0;i*(p+i);/假设输入假设输入weandfp4=0;/结束符结束符coutbc;b3=0;假如输入假如输入“wea”,则则b内为内为we,c为为a。输入输入“wea”,则则b内为内为w,c为为e。void不能声明对象,不能声明对象,(void*)能输出无前缀的能输出无前缀的16进制数。进制数。例如整数例如整数a的值为的值为35,则,则cout(void*)a(void*)b“(void*)ababcstrr;coutabcstrrendl;下面是部分输入和输出的对照,由此可以分析并看出它们下面是部分输入和输出的对照,由此可以分析并看出它们的特点。的特点。键盘输入键盘输入输出结果输出结果23.567wty56.78yu230.567wty56.7812343647586.7897354dfghe02343.64759e+006dfghe04563.4e+25ty675w4563405ty6751如果默认输入输出格式不能满足自己的要求,就必须重载如果默认输入输出格式不能满足自己的要求,就必须重载它们。它们。10.2.3使用使用ios_base类类1.ios_base类简介类简介ios_base类派生类派生ios类,类,ios类又是类又是istream类和类和ostream类的类的虚基类。下面是虚基类。下面是ios_base类的部分定义。类的部分定义。classios_basepublic:classfailure;typedefT1fmtflags;staticconstfmtflagsboolalpha,dec,fixed,hex,internal,left,oct,right,scientific,showbase,showpoint,showpos,skipws,unitbuf,uppercase,adjustfield,basefield,floatfield;typedefT2iostate;staticconstiostatebadbit,eofbit,failbit,goodbit;typedefT3openmode;staticconstopenmodeapp,ate,binary,in,out,trunc;/其他部分其他部分fmtflagsflags()const;fmtflagsflags(fmtflagsfmtfl);fmtflagssetf(fmtflagsfmtfl);fmtflagssetf(fmtflagsfmtfl,fmtflagsmask);voidunsetf(fmtflagsmask);/其他部分其他部分protected:ios_base();:fmtflags实际上就是整数类型,参数实际上就是整数类型,参数fmtfl为格式控制标志。为格式控制标志。常量名的含义如表常量名的含义如表10.1所示。所示。这些常量供成员函数这些常量供成员函数ios_base:flags()和和ios_base:setf(fmtflags)来设置流的格式。格式标志存放在每个流的一个长整型成来设置流的格式。格式标志存放在每个流的一个长整型成员中,每个标志占员中,每个标志占1位,位,ios_base类中定义了如表类中定义了如表10.2几个几个处理标志的成员函数。为了简单易懂,这里直接采取熟悉处理标志的成员函数。为了简单易懂,这里直接采取熟悉的数据类型表示它们。的数据类型表示它们。使用流对象调用表使用流对象调用表10.2的成员函数。例如输出流的成员函数。例如输出流cout调用调用设置精度的函数设置精度的函数precision:cout.precision(4);设置输出包含设置输出包含4位小数。这与位小数。这与1.4.5节的节的setprecision(intn)不不同,那里的同,那里的n连小数点在内。连小数点在内。2.直接使用格式控制直接使用格式控制表表10.1中的名字可以直接用在系统提供的输入输出流中,中的名字可以直接用在系统提供的输入输出流中,而且有些是成对的。加而且有些是成对的。加no前缀表示取消原操作。下面给出前缀表示取消原操作。下面给出对应符号对应符号(比表比表10.1的多的多),并用实例演示部分用法。,并用实例演示部分用法。skipwsshowbaseshowpointuppercaseshowposuntibufboolalphanoskipwsnoshowbasenoshowpointnouppercasenoshowposnountibufnoboolalpha【例例10.1】演示使用标志位的例子。】演示使用标志位的例子。#includeusingnamespacestd;constdoublePI=3.141592;voidmain()inta=15;boolit=1,not=0;coutshowpoint123.0/输出小数位输出小数位noshowpoint123.0;/不输出小数位不输出小数位coutshowbase;/演示输出数基演示输出数基coutauppercasehexa“nouppercase/演示大小写演示大小写hexa“noshowbaseadecaendl;coutuppercasescientificPI“nouppercasePIfixedPIendl;coutcout.precision()“”PI“”;/演演示示/cout的成员函数的成员函数cout.precision(4);coutcout.precision()PIendl;cout.width(10);coutshowposrightanoshowposPI;/演示数值符号演示数值符号coutitnotboolalphait“not/演示演示boolnoboolalphaitnotendl;cout.width(10);coutleftPI123cout.width();cout123cout.width()endl;对照分析如下的输出结果是如何获得的,以便加深理解。对照分析如下的输出结果是如何获得的,以便加深理解。123.000123150XF0 xff153.141592E+0003.141592e+0003.14159263.14159243.1416+153.141610truefalse103.1416123101230用用width(int)设置宽度的效果只对一次输入或者输出有效,设置宽度的效果只对一次输入或者输出有效,在完成一次输入或输出之后,宽度设置自动恢复为在完成一次输入或输出之后,宽度设置自动恢复为0(表示表示按实际数据宽度输入输出按实际数据宽度输入输出)。在设置