14.9 字符串流

    C++库中提供了非模板strstream类族和模板sstream类族用以处理字符串的输入输出,strstream类族用于读取字符数组的格式化信息并将格式化信息写入字符数组,也就是说,strstream类流对象以字符数组为输入输出设备,sstream类族用于读取string对象中的格式化信息并将格式化信息写入string对象,也就是说,sstream类流对象将string对象作为输入输出设备。

    注意

    新的C++标准已经指出,除非出于和旧式代码兼容的目的,不推荐使用strstream类流对象,但本节还是为读者进行简要的介绍。

    14.9.1 strstream类族

    strstream类族是针对char型定义的,不是由模板类实例化而来,包括istrstream类(用于从字符数组中读取格式化信息)、ostrstream类(用于向字符数组中写入格式化信息)以及strstream类(即可读取,也可以写入)。

    提示使用strstream类族必须包含头文件<strstream>。

    1.ostrstream类流对象

    ostrstream类流对象支持一个字符数组作为数据传输目的地的输出流,可以将某个现成的字符数组和ostrstream类流对象关联起来,也可以使用流对象自动分配的存储空间,这取决于使用的构造函数,如下所示。


    ostrstream(char*,int,int=ios_base:out);

    该构造函数第1个参数是字符数组指针;第2个参数是缓冲区的大小;第3个参数是打开模式。如果是默认的模式(ios_base),则从缓冲区头部开始添加新的字符,如果打开模式是ios_base:ate或ios_base:app,则从缓冲区的尾部添加新的字符。

    如示例代码14.31所示。

    代码14.31 ostrstream与现成字符数组的绑定OstrstreamSample


    <————————————文件名:example1431.cpp———————————————> 01 #include<iostream> 02 #include<strstream> 03 using namespace std; 04 const int Len=20; 05 int main() 06 { 07 int num=9; 08 char sz[]="Hello"; 09 char buf[Len]; 10 ostrstream os(buf,Len);//创建ostrstream对象,并将其和字符数组buf绑定 11 os<<"num="<<num<<endl;//输出格式化信息到流中,实际上是到字符数组中 12 os<<"sz="<<sz<<endl; 13 os<<ends;//输出字符串结束符 14 cout<<buf; 15 return 0; 16 }

    输出结果如下所示。


    num=9 sz=Hello

    【代码解析】代码第10行在创建ostrstream流对象os时将其和字符数组buf绑定,这样使用os进行<<操作时,目的对象就是buf数组,成功地将格式化信息写入了字符数组,由于strstream类族是从iostream类族派生而来的,因此,前面介绍的其他操作函数、控制字和流状态等同样适用于字符串流。例如当插入操作超过申请的存储空间的大小时,流对象的bad函数返回false。

    注意

    如果想添加更多的字符,通常的做法是新建一个ostrstream,将旧的流灌入新流中,并向新流添加字符。

    如果使用自动分配存储空间的方法,则使用无参的构造函数,如下所示。


    ostrstream();

    此时,创建的流对象会在堆中分配一块存储空间,并自己维护。当流中的信息不断增加,原存储块不够用时,可以动态分配更多的存储空间,程序中可调用成员函数rdbuf返回缓冲区指针,以访问缓冲区的内容。

    当不知道数据需要多少空间时,这是一种很好的方法。但问题随之出现了,当空间不够,流对象自己移动存储块以分配更多空间时,之前调用rdbuf函数返回的缓冲区指针已失效,而且由于是由流对象管理内存,所以返回的指针何时失效,程序员是不知道的,ostrstream采用“冻结”(freeze)的方法解决这个问题,通过调用str成员函数返回指向缓冲区字符数组的指针,并将流本身“冻结”,冻结之后的输出操作均无效,做抛弃处理,而且ostrstream对象也不再负责存储空间的自动释放,必须由用户自己清理缓冲区,如下所示。


    delete os.str();

    来看一段示例代码14.32。

    代码14.32 ostrstream无参构造函数OstrstreamConstructor


    <—————————————文件名:example1432.cpp——————————————> 01 #include<iostream> 02 #include<strstream> 03 using namespace std; 04 const int Len=20; 05 int main() 06 { 07 int num=9; 08 char sz[]="Hello"; 09 ostrstream os;//创建ostrstream对象os,由其自己管理缓冲区 10 os<<"num="<<num<<endl;//输出格式化信息到流中,实际上是到字符数组中 11 os<<"sz="<<sz<<endl; 12 os<<ends;//输出字符串结束符 13 cout<<os. str();//返回指向缓冲区中字符数组的指针,冻结 14 os<<"C++"<<endl;//冻结之后的输出,抛弃 15 cout<<os. str(); 16 delete os. str();//用户清理os缓冲区 17 return 0; 18 }

    输出结果如下所示。


    num=9 sz=Hello num=9 sz=Hello

    【代码解析】代码第9行采用无参构造函数的形式创建了ostrstream流对象os,则os自动开辟缓冲区并维护其大小,采用str成员函数可以返回指向缓冲区中字符数组的指针,使用标准输出流对象cout对该指针输出,实现了对缓冲区中内容的访问,同时,str函数将os冻结,此后的“os<<"C++"<<endl;”操作被抛弃,不再写入流中。由于流对象已不再对缓冲区进行自动维护,因此需要用户在流对象被撤销前执行清理缓冲区内存,以免造成内存泄露。

    注意

    ostrstream都不会为operator<<调用自动添加'\0',所以要记得在所输出的信息最后加上<<ends,标识流的结尾。

    2.istrstream类流对象

    istrstream类流对象将字符数组当做信息传入源,将格式化的信息读出赋值给某些变量,因此,istrstream类流对象必须和某字符数组关联。

    istrstream类流对象的构造函数原形如下所示。


    istrstream(const char*str,int size=0);

    参数1表示字符数组,参数2表示缓冲区大小,当其为0时,表示istrstream类流对象关联到由第1个参数str指向的一片以空字符'\0'结尾的内存空间。如示例代码14.33所示。

    代码14.33 istrstream类流对象的使用IstrstreamSample


    <———————————-文件名:example1433.cpp———————————————-> 01 #include<iostream> 02 #include<strstream> 03 using namespace std; 04 const int Len=20; 05 int main() 06 { 07 char sz[]="H156 3. 1415";//注意:H前有个空格 08 istrstream is(sz);//创建istrstream对象is,与字符数组sz绑定 09 char c; 10 int i; 11 double d; 12 is. setf(ios_base:skipws);//设置格式状态字,对char型通过跳过前导空白 13 is>>c>>i>>d;//输入,从字符串(源头)到变量 14 cout<<"c is"<<c<<",i is"<<i<<",d is"<<d<<endl;//标准输出验证结果 15 return 0; 16 }

    输出结果如下所示。


    c is H,i is 156,d is 3.1415

    【代码解析】代码第8行创建的istrstream流对象is和字符数组sz关联,则sz就成了is操作的信息源,缓冲区大小采用了默认值0,意味着缓冲区的大小为strlen(sz)+1。前面介绍的格式状态字和流状态等对istrstream类对象同样有效,因此,语句“is.setf(ios_base:skipws);”用以设定在char型输入时跳过空白(默认不跳),这样char型变量c才能读到正确的字符‘H’,否则空白被赋值给c,后续的字符“H”无法赋值给int型变量i,输入流出错,流状态位failbit有效。