这张主要针对C++ 标准IO库的三个大类,iostream,fstream,sstream进行基本介绍。
iostream主要用来读写一个流(stream),fstream用来读写有名文件,sstream用来读写内存中的string(in-memory string)。
为了支持更多字符(如汉子等等),C++有wchar_t这种格式,对应的流数据的名字开头都有一个w,如wcin,wcout,wcerr等。
尽管有不同的头文件,不同的操作对象,不同的character size,但是操作符都是一样的:<< , >>。C++ 通过继承(inheritance)(以后会讲)来实现这一点。记住,ifstream和istringstream都继承于istream,同样的ofstream和ostringstream继承于ostream。
这点之前也提到过。IO类不支持赋值操作,因此传递或返回IO类的函数都要通过引用的方式。注意:由于对IO类的读写都是对它的一种修改,因此这些引用不能是const。
这个非常有用,IO类提供了一些函数和标志(flag),让我们能够得知并改变流的状态。
常用的状态有:
值得一提的是:clear()函数的作用是清空所有状态,执行后状态一定是0,即没有任何问题。clear(flags)函数的作用是把当前状态全部清空,然后设置成flags的状态。而setstate(flags)的作用是,在当前state上叠加flags的状态,即将flags中是1的地方置高,是0的地方保持不变。
badbit表明了系统级出错(system-level failure),例如无法挽回的读写错误,一般来说,出现了badbit后是无法挽回的。而failbit一般表明一些可以挽回的错误,例如本应读入一个数字,却读到了一个字符。当读到结尾(end-of-file)时,会同时置高eofbit和failbit。而goodbit在正常时是0(略奇怪吧),表明没有任何错误。如果badbit,failbit,eofbit中任何一个置高,那么判断条件就会是false(例如if(cin>>a)就会是false)。
一般来说,我们会调用s.good()和s.fail()来判断IO流的有效性。
一种常用的恢复流的写法:
is.clear( is.rdstate() & ~is.failbit & ~is.badbit );
我们知道,输出流一般先把数据存储在一个缓冲区内,等到合适的时刻再一次性完成输出,以提升系统效率。C————中有以下几种情况会导致输出流的输出操作被执行:
1.程序正常结束,所有输出缓冲区会在main return时被输出。
2.缓冲区满了。
3.我们explicitly用一些操作方法来输出它,如endl。
4.设置一个叫做unitbuf的操作符,这个操作符的作用是要求缓冲区在每次输出操作后都直接把缓冲区内容输出,不作缓冲停留。默认情况下,cerr的unitbuf是置高的,因此任何对cerr的输出都将被立刻执行。
5.一个输出流A可以和另一个流B绑定(tied to another stream)。这时,任何对B的操作(读或写),都将先清空A的缓冲区。
除了endl这个操作符能够强制清空缓冲区外,还有两个类似的操作符:flush和ends。
flush清空缓冲区,但不向缓冲区中添加任何字符。
ends清空缓冲区,同时向缓冲区最后添加一个null字符。
endl清空缓冲区,同时向缓冲区最后添加一个换行符。
前面已经讲过了unitbuf的作用,设置的方法是:
cout<<unitbuf;//all writes will be flushed immediately cout<<nounitbuf;//returns to normal buffering
这看似显而易见,但你应该足够重视它!很多时候,你在debug一个崩溃的程序时,最好确保你认为应该被输出的内容都确实被输出了(what you think should have been written was actually flushed).否则,你很可能为此浪费数小时。
Tying Input and Output Streams Together
正如刚刚说的,如果input和output流被绑在一切,任何对input的输入都会先导致输出流的输出。默认时,系统库把cin和cout绑在一起,因此对cin的输入,会导致cout的输出。
交互式程序应该好好利用这一点,保证用户输入前,必要的提示信息会被及时输出。
tie函数有两个重载函数。一个不接受任何参数,返回一个指向输出流的指针,这个指针指向的流表示当前流被绑定的对象(如果有的话)。另一个,接受一个指向输出流的指针,并且把自己和这个输出流绑在一起。
cin.tie(&cout);//cin和cout绑在一起 ostream *old_tie = cin.tie(nullptr);//首先把cin和nullptr绑在一起,即cin不再和任何ostream捆绑。然后old_tie接收返回参数,是cin的老的捆绑对象,即cout cin.tie(&cerr); cin.tie(old_tie);对于同一个输出流,可以同时和很多其他流绑定。
在之前的C++版本中,文件名必须是c-style字符串,而在C++ 11中,文件名既可以是字符串,也可以是string,方便!
如前所说,fstream是iostream的继承。因此,当任何需要iostream被使用的地方,我们都可以用fstream代替。
一个极大的好处是,如果有一个read函数,作用是通过iostream中的cin读入一串数字,那么我们可以不经任何修改地让这个函数调用fin,从而从一个文本中读入一串数字。不经修改,使得这个扩展非常实用!
open的作用自然是打开一个文件,close则是关闭这个文件。
如果open失败,那么failbit将会被置高,因为这种情况可能发生,因此在open之后最好检查一下。
fin.open(..); if(fin)...//good idea to do this!如果一个ofstream流已经open过一个文件,那么在此open会导致失败,failbit将会被置高。在open过一个文件后,如果想open另一个文件,应该先close,再open。
当一个fstream流被销毁时,close将会被自动执行。
每个流都有一个file mode,表示了这个文件将会被如何使用。
其中:
in:只能由ifstream或fstream设置
out:只能由ofstream或fstream设置
trunc:只能当out也被设置时才能设置
app:只有当trunc没被设置时才能设置,此时默认设置了out
默认时,设置了out的情况下,trunc也被默认设置了,即使我们没有explicitly设置trunc。如果要避免,则要么我们可以设置app;要么我们可以设置in,使得成为in and out模式(以后介绍)。
ate和binary可以和所有其他的混合使用,在ifstream,ofstream,fstream下都能使用。
默认时,ifstream是in模式,ofstream是out模式,fstream是in and out模式。
istringstream向一个string中写入内容,ostringstream从一个string中读入内容(听着怪怪的,确实如此),stringstream读写一个string。
stringstream的特殊操作:
什么时候用到istringstream
当我们既需要对整行(entire line)进行操作,同时也需要对一行中的单独内容(individual words)进行操作时,我们便需要istringstream。
string line; getline(cin,line); istringstream record(line); record>>info.name; record>>info.phone; ...而且可以使用>>操作符,非常方便。
什么时候用到ostringstream
当我们需要一点点构成我们的整行内容,但是希望直到构建完再一次性把整行输出时,会用到ostringstream。
具体的使用
istringstream is; ostringstream os; string s; is >> s;//ok!把is的内容写入s is << s;//error! os >>s;//error! os <<s;//ok!将s的内容写入os is.str()//返回is中存储的内容 os.str();//返回os中存储的内容有意思的是上面的.str()函数,可以作为左值,编译器不会报错,但是没有任何效果。
即,.str()函数仅应该被用来作为输出,而不应该被用来作为输入。