C++ Primer(第五版)第八章-IO类读书笔记。
-
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作为序列类的参数。
方法:几经辗转,终于弄清楚该怎么做了:
-
用stringstream去初始化一个输出序列化类,将此刻的模型通过序列化类保存到stringstream中。
-
找到更好的模型了,需要覆盖之前保存的。调用stringstream的str()函数,传入
""
参数,这样就清空了之前保存的结果!然后再构建
-
-
IO类不可赋值和拷贝
所以只能传递引用;
同时,由于每次操作IO后IO类将会发生变化(内部指针、状态),所以一般不用const修饰。(不是不能,但是用const之后,既不能输入也不能输出,意义不大)
-
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 后,又可以从头开始读了。
-
管理输出缓冲
默认情况下,新建一个输出流,都会有缓冲。
以下情况下会清空输出流缓冲:
-
程序正常结束——main函数中的return操作包含刷新缓冲操作
-
缓冲区满
-
使用操纵符 : endl , ends , flush
-
使用控制符 : unibuf
-
如果该输出流关联到另外一个(多个)流,那么读、写(根据关联流的具体情况)关联的流,该输出流会先刷新缓冲
需要解释下上面的情况:
-
关于操纵符:
endl非常熟悉,在缓冲中加入换行符,并刷新;
flush,直接刷新。
ends,在缓冲中加入空字符(null , 即
\0
),刷新。cout << "info" << endl << "info" << flush << "info" << ends ; --------- info$ infoinfo^@
-
关于unibuf
cout << unibuf ; // open ... cout << nounibuf ; // close
cerr默认就是使用了unibuf控制符,这样关闭了缓冲,每次写都会直接输出。
-
关于关联流
使用
tie
来将流与输出流关连起来,有重载。cin.tie(cout) ; // 将cin与cout关连; cin.tie(nullptr) ; // 停止cin的关连 cin.tie() ; // 重载;返回目前cin关连的流。
-
-
文件流对象
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操作文件了… -
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) ; }