1. 概述
为了概览一下C++的IO库,先来张cplusplus.com网站的图片:
从这个图片我们基本上可以得到C++ IO库的所有基本信息:头文件、类、继承关系。为了更清楚的看出各个类的继承关系,我根据上面的图重新画了一个:
从上面两个图可以看出,C++的IO都是流式的,主要分为三大类:普通流、文件流、字符串(string)流。这三大类的流各有各的特点,但也有许多共性,我们先来看一些共性。
1.1 条件状态
其实所谓的条件状态很容易理解,就是标识流此刻的状态。条件状态(均为ios_base::iostate类型,实质是一种枚举类型,定义在ios_base.h头文件中)共有四种:
状态 | 条件状态检测函数 | 状态说明 |
badbit | s.bad() | 用于指出被破坏的流,不可恢复 |
eofbit | s.eof() | 用于指出流已经到达文件结束符 |
failbit | s.fail() | 用于指出失败的IO操作 |
goodbit | s.good() | 用于指出流是OK的 |
除了上面四个条件状态检测函数,还有几个操作条件状态的函数:
函数名 | 函数作用 |
s.clear() | 将流s的状态设置为goodbit状态 |
s.clear(flag) | 将流s的状态设置为flag状态,flag的类型是ios_base::iostate类型 |
s.setstate(flag) | 给流s添加指定条件 |
s.rdstate() | 返回流s的当前条件,返回值类型为ios_base::iostate类型 |
关于条件状态的使用,看下面几个代码片段:
#include <iostream>
#include <fstream>
#include <string>
#include <stdexcept>
using namespace std;
int main()
{
int ival;
while (cin >> ival, !cin.eof())
{
if (cin.bad())
throw runtime_error("IO stream corrupted");
if(cin.fail())
{
cerr << "bad data, try again" << endl;
cin.clear(istream::failbit);
continue;
}
cout << "ival:" << ival << endl;
}
return 0;
}
这段代码是《C++ Primer》第四版上面的一个例子,该段代码的作用是循环不断读入cin,直到到达文件结束符或者发生不可恢复的读取错误为止。但是这个例子是有问题的,具体见我之前的博客《关于<C++ Primer>中cin.fail可能造成死循环的问题分析》。
2.2 刷新缓冲区
缓冲区和IO就像孪生兄弟一样,有IO的地方,必然不能少了IO。说实话,我对C++的缓冲区还没有研究过,这里就拿C的缓冲区来说吧(我想应该差不了多少)。在标准C中,共有三种类型的缓冲:
(1)全缓冲(fully buffered)——只有填满缓冲区候才进行实际的I/O操作。这类缓冲通常用在文件操作中。
(2) 行缓冲(line-buffered)——当输入或输出中遇到换行符时才进行实际的I/O操作。键盘输入就是标准的行缓冲。
(3)无缓冲(non-buffered)——即没有缓冲。标准C的I/O库不对字符进行缓冲存储。
言归正传,这里我们的重点不是讲缓冲区的概念及类型,而是看在C++中,哪些情况可以刷新缓冲区(即数据真正的写到设备或文件):
- 程序正常结束。当程序正常从main函数return时,所有的输出缓冲会被刷新。
- 缓冲区满了以后,缓冲区也会被刷新。
- 我们使用一些强制刷新缓冲区的操作。比如:(1)endl——插入换行符,然后刷新缓冲区。(2)ends——插入一个null,然后刷新缓冲区。(3)flush——什么也不加,直接刷新缓冲区。
- 使用unitbuf操作符。该操作符告诉流没进行一次连续的输出后,就刷新缓冲区。使用nounitbuf可以复原系统默认设置。
- 当我们把输出流和另外一个流绑定在一起的时候,后者的读或写之前都会刷新前者的缓冲区。比如标准输出流cout默认是和cin与cerr绑定在一起的,这样cin读或者cerr写都会刷新cout流。这个是很重要的,比如讲cout与cin绑定在一起就可以保证我们每次读之前输出缓冲区已经被刷新了——例如,一般我们的程序在获取输入前都会输出一段提示,如果此时不先刷新输出缓冲区的话,该缓冲区内的内容就可能被当成输入的一部分。当然,一个流一次最多只能另外绑定一个流。
最后,需要特别注意的是,当程序崩溃、异常终止时,缓冲区并不会被刷新。最常见的例子就是,我们的程序在一些异常场景下打了日志,但这些日志还处于缓冲区时程序突然异常终止了,那此时缓冲区里面的日志并不会打印出来。这是我们需要注意的。
2.3 其他
(1)所有的IO对象都是不能拷贝和赋值的:
ofstream out1, out2;
out1 = out2; // 错误:不能给IO类型赋值
ofstream print(ofstream); // 错误:不能拷贝
out2 = print(out2); // 错误:不能拷贝
因为IO类型不能拷贝,所以函数的参数和返回值也不能是IO类型,只能使用引用类型。
(2)读写IO对象都会改变对象的状态,所以IO的引用也不能是const的。
2. 文件流
文件IO类型都定义在fstream头文件中,共有三类:ifstream(读文件)、ofstream(写文件)、fstream(读写文件)。fstream将ifstream和ofstream的功能合并在一起,所以这里直接介绍fstream。由上面的继承关系可以看出fstream继承自iostream,所以它拥有iostream的特性,同时它还有自己特有(其他IO类型没有)的一些操作:
fstream fstrm | 创建一个未和任何文件绑定的文件流 |
fstream fstrm(s) | 创建一个和文件s绑定的文件流,s可以是string或char*;打开文件的模式取决于默认值 |
fstrean fstrm(s, mode) | 和上面的一样,但指定打开文件的模式 |
fstrm.open(s) fstrm.open(s,mode) |
两个函数都是打开文件s,并将其与文件流fstrm绑定在一起。前者使用流默认的文件模式,后者使用指定的模式,返回void |
fstrm.close( ) | 关闭fstrm流绑定的文件,返回void。当fstrm被析构时,也会自动调用close |
fstrm.is_open( ) | 检查与文件流fstrm绑定的文件是否成功打开或者有没有被关闭 |
C++中的文件模式有以下几种:
in | 用于打开一个输入流,ifstream流的默认值,ofstream不能使用该模式 |
out | 用于打开一个输出流,oftream流的默认值,ifstream不能使用该模式 |
app | 以追加写的方式打开一个输出流 |
ate | 打开文件后,立刻定位到文件尾,可用于任何文件流 |
trunc | 截断文件,即清空已有内容,仅用于输出流,且不可和app同时使用 |
binary | 以二进制模式操作,可用于任何文件流 |
3. string流
sstream头文件中定义了三种用于操作“内存IO”的类型:istringstream(读string)、ostringstream(写string)、stringstream(读写string)。这里我们要注意一下,虽然这里我们把string流划分到IO去了,但其实它们的操作对象其实都是在内存中的string对象,并非平常说的那种IO。和文件流一样,string流继承自iostream,所以它也有iostream的特性,也有它自己特有的一些操作,这里以stringstream为例:
sstream strm | 创建一个未和任何string类型绑定的stringstream对象。sstream是sstream头文件中定义的一种类型 |
sstream strm(s) | 创建一个sstream流,该流与s的拷贝绑定 |
strm.str( ) | 返回与strm流绑定的string |
strm.str(s) | 拷贝string s到流strm,返回void |
4. sstream和strstream
上面我们介绍了sstream头文件里面包含的string流,其实还有一个strstream流,在strstream头文件中定义。它和sstream特别像,但是sstream是基于string实现的,而strstream是基于char*实现的。而strstream已经逐渐被废弃了,我们最好也不要再使用了,因为它非常的不安全。gcc的strstream头文件中是这样说的(其实是它包含的backward_warning.h中说的):
This file includes at least one deprecated or antiquated header which
may be removed without further notice at a future date. Please use a
non-deprecated interface with equivalent functionality instead. For a
listing of replacement headers and interfaces, consult the file
backward_warning.h. To disable this warning use -Wno-deprecated.
/*
A list of valid replacements is as follows:Use: Instead of:
<sstream>, basic_stringbuf <strstream>, strstreambuf
<sstream>, basic_istringstream <strstream>, istrstream
<sstream>, basic_ostringstream <strstream>, ostrstream
<sstream>, basic_stringstream <strstream>, strstream
<unordered_set>, unordered_set <ext/hash_set>, hash_set
<unordered_set>, unordered_multiset <ext/hash_set>, hash_multiset
<unordered_map>, unordered_map <ext/hash_map>, hash_map
<unordered_map>, unordered_multimap <ext/hash_map>, hash_multimap
<functional>, bind <functional>, binder1st
<functional>, bind <functional>, binder2nd
<functional>, bind <functional>, bind1st
<functional>, bind <functional>, bind2nd
<memory>, unique_ptr <memory>, auto_ptr
*/
这里列举出了旧的C++中存在的一些类型,后面会逐渐废弃的类型,后续我们也要避免使用。这里举一个例子:
/*
* main.cpp
*
* Created on: 2015年10月26日
* Author: Allan
*/
#include <iostream>
#include <sstream>
#include <string>
#include <strstream>
using namespace std;
string int2string_v1(int i)
{
ostringstream os;
os << i;
return os.str();
}
string int2string_v2(int i)
{
ostrstream os;
os << i;
return os.str();
}
int main()
{
cout << int2string_v1(10) << endl;
cout << int2string_v2(10) << endl;
return 0;
}
上面的这段代码分别使用sstream的string流和strstream流实现了同一个功能:将整数转为string。但是,程序运行后,我们会发现v1版本工作正常,但v2版本有些问题。这是因为sstream的str成员函数返回string,而strstream的str成员函数返回char*,但是返回的这个char*并没有包含字符串结束符’’,这就是为什么说strstream类型不安全。刚才我们介绍过ends,它会给缓冲后面加一个null,所以如果你一定要使用strstream,那ends对你就非常重要了。比如将上面的int2string_v2版本里os << i;改为os << i << ends;就可以正常工作了。但考虑到各方面因素,还是不推荐使用strstream。
评论已关闭