C++ Primer(第五版)第八章-IO类读书笔记。

  1. IO类

    头文件 类名称
    iostream istream , ostream , iostream
    fstream ifstream , ofstream , fstream
    sstream istringstream , ostringstream , stringstream

    继承关系

                                 <- istringstream
                                 <- ifstream
                     <-  istream <- 
     ios_base <- ios  |            |- iostream <-| fstream
                     <-  ostream <-            <-| stringstream
                                 <- ofstream
                                 <- ostringstream                    
    
    
    
     #include<sstream>
    
     stringstream s ; 
     s << 1 << "\n" << 2 ; // 输入到流中 
     cout << s << endl ;   // 其实只输出了地址
     cout << s.str() << endl ; // 输出了其中的内容
     int b ; 
     while(s >> b) cout << b << endl ; // 从流中读取
    
     结果:
    
     0xbfd0e838
     1
     2
     1
     2
    

    按照官方文档,stringstream 使用了stringbuf类。

    update

    最近在工作中用了下stringstream , 来说说过程:

    需求:需要通过boost::archive::text_oarchive来序列化一个Model(CNN 库中的Model),这个序列化类需要一个文件流作为参数。本来刚开始直接输出到文件后,但是后来需要在合适时刻保持最佳的模型,所以想在内存中缓存一个。于是就想到了用stringstream作为序列类的参数。

    方法:几经辗转,终于弄清楚该怎么做了:

    1. 用stringstream去初始化一个输出序列化类,将此刻的模型通过序列化类保存到stringstream中。

    2. 找到更好的模型了,需要覆盖之前保存的。调用stringstream的str()函数,传入""参数,这样就清空了之前保存的结果!然后再构建

  2. IO类不可赋值和拷贝

    所以只能传递引用;

    同时,由于每次操作IO后IO类将会发生变化(内部指针、状态),所以一般不用const修饰。(不是不能,但是用const之后,既不能输入也不能输出,意义不大)

  3. IO类的状态判断

    以下均在IO类的命名空间下。

    状态类型: iostate

    状态取值: badbit , goodbit , failbit , eofbit

    状态函数: bad() , good() , fail() , eof()

    当然建议使用状态函数来获得状态结果了,因为返回的就是true/false,而不是状态值;

    可以使用函数: rdstate() 来获得真实的状态值,即返回类型为iostate , 可以挨个与各状态取值判断:

     ios::iostate state = s.rdstate() ;
     cout << (state == ios::goodbit) << (state == ios::badbit) << (state == ios::failbit) 
          << (state == ios::eofbit) << endl ;
    

    但是根据测试结果,似乎这样判断的结果与通过状态函数的结果不一致。具体原因未深究。

    使用函数clear()来复位状态。

    以下代码:

     s << 1 << "\n" << 2 ; 
    
     int b ; 
     while(s >> b) cout << b << endl ;
     s.clear() ;
     cout << "----------" << endl ;
     while(s >> b) cout << b << endl ;
    

    即在上次已经输出完毕后,通过重置状态,使得其又可以从头开始读取!猜测clear还会清除读取位置的指针!试试:

     s << 1 << "\n" << 2 ; 
     int b ; 
     s >> b ; 
     cout << b << endl ;
     s.clear() ;
     s >> b ; 
     cout << b << endl ;
    

    然而结果令人失望… 即,它只能清除这几个标志位!不能重置读取位置的游标!所以,上面之所以可以重复读取,应该是因为到达EOF后,类内部自动将游标归0,同时置eofbit位。这样清除eofbit 后,又可以从头开始读了。

  4. 管理输出缓冲

    默认情况下,新建一个输出流,都会有缓冲。

    以下情况下会清空输出流缓冲:

    1. 程序正常结束——main函数中的return操作包含刷新缓冲操作

    2. 缓冲区满

    3. 使用操纵符 : endl , ends , flush

    4. 使用控制符 : unibuf

    5. 如果该输出流关联到另外一个(多个)流,那么读、写(根据关联流的具体情况)关联的流,该输出流会先刷新缓冲

    需要解释下上面的情况:

    1. 关于操纵符:

      endl非常熟悉,在缓冲中加入换行符,并刷新;

      flush,直接刷新。

      ends,在缓冲中加入空字符(null , 即\0),刷新。

       cout << "info" << endl 
            << "info" << flush 
            << "info" << ends ;
       ---------
       info$
       infoinfo^@
      
    2. 关于unibuf

       cout << unibuf ; // open
       ...
       cout << nounibuf ; // close
      

      cerr默认就是使用了unibuf控制符,这样关闭了缓冲,每次写都会直接输出。

    3. 关于关联流

      使用tie来将流与输出流关连起来,有重载。

       cin.tie(cout) ; // 将cin与cout关连;
       cin.tie(nullptr) ; // 停止cin的关连
      
       cin.tie() ; // 重载;返回目前cin关连的流。
      
  5. 文件流对象

    1

    旧版标准库只允许构造函数传入的文件名为const char* , 但是C++11标准已经允许可以使用string类型来指定文件路径了。

    同时看C++11的constructor定义觉得很有意思:

     default (1)         fstream();
     initialization (2)  explicit fstream (const char* filename,
                             ios_base::openmode mode = ios_base::in | ios_base::out);
                         explicit fstream (const string& filename,
                             ios_base::openmode mode = ios_base::in | ios_base::out);
     copy (3)    fstream (const fstream&) = delete;
     move (4)    fstream (fstream&& x);
    

    禁止了默认拷贝,定义右值引用的移动。

    2

    从文件流对象来看,C++不允许直接操作文件,而是通过关联流来操作。那么C语言是通过什么来操作文件的呢?应该一个结构体吧——都应该不能直接操作文件吧。

    其次,关联的文件流对象被销毁时,其close函数会自动被调用——所以,其实只要程序正常结束,不显式调用close也是可以的吧。

    最后,一个文件流只能关联一个文件(或者不关联文件)。所以要想关联另一个文件,必须先关闭当前的关联文件(如果有)。

    3

    文件流打开的方式:

    方式(ios_base命名空间下) 说明
    in
    out
    app 附加,每次写操作前定位到文件末尾。
    trunc 额外标志符,表示打开时清空文件(仅在out设置时可用!)。
    ate 额外标识符,表示已打开文件就定位到末尾
    binary 额外标识符,二进制模式打开

    ofstream默认打开模式为ios_base::out , 而out模式自动会使用trunc,又trunc只能在out下使用,所以trunc没有必要显式地存在…

    上述说明只使用out,则打开时文件会被清空;要想在out模式打开时不清空,可以+上app模式,但是此时都只能在末尾写;另外,可以加上in模式,及同时可读写状态!这是比较自由的方式。不过,此时就没有必要使用ofstream了,可以直接用fstream操作文件了…

  6. string流

    记录一段比较有意义的代码:使用istringstream + >> 做类似split的操作:

    数据:

     morgan 04510001 1800451544x
     xiaoxiao 0451666666 1820987123x
     haha 010888888 1546767676x
    

    程序:

     string line , word ;
     vector<PersonInfo> people ;
     while(getline(cin , line))
     {
         PersonInfo info ;
         istringstream record(line) ;
         record >> info.name ;
         while(record >> word)
         {
             info.phones.push_back(word) ;
         }
         people.push_back(info) ;
     }