C++不直接处理输入输出,而是通过一族定义在标准库中的类型来处理IO。这些类型支持从设备读取数据、向设备写入数据的IO操作,设备可以是文件、控制台窗口等。还有一些类型允许内存IO,即从string读取数据,向string写入数据。
IO类
头文件 | 类型 |
---|---|
iostream | istream,wistream从流读取数据 ostream,wostream向流写入数据 iostream,wiostream 读写流 |
fstream | ifstream,wifstream从文件读取数据ofstream,wofstream 向文件写入数据 fstream,wfstream 读写文件 |
sstream | istringstream,wistringstream从string读取数据 ostringstream,wostringstream 向string写入数据 stringstream,wstringstream 读写string |
ps:为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵宽字符类型的数据。宽字符版本的类型和函数的名字以一个w开始。
IO类型间的关系
标准库通过继承机制,使我们能忽略这些不同类型的流之间的差异。类型istringstream 和 ifstream 都继承自istream。类似的类型ostringstream 和 ofstream都继承自ostream。我们是如何使用cin和cout的,就可以同样地使用这些类型的对象。
IO对象无拷贝或赋值
ofstream out1,out2;
out1 = out2; //error:不能对流对象赋值
ofstream print(ofstream);//error:不能初始化
out2 = print(out2); //error:不能拷贝流对象
1. 不能将形参或返回类型设置为流类型
2. 进行IO操作的函数通常以引用的方式传递和返回流
3. 读写IO对象会改变其状态,所以引用不能是 const 的
条件状态
一个流一旦发生错误,其上后续的操作都会失败,只有当一个流处于无错状态时,我们才可以从他读取数据,向它写入数据。由于流可能处于错误状态,所以代码通常在使用一个流之前先检查它是否处于良好状态。确定一个流对象的状态的最简单的方法是将它当做一个条件来用。比如:
while (cin >> word)
ok; 读操作成功`
查询流的状态
将流作为条件使用,只是告诉我们流是否有效,而无法告诉我们具体发生了什么,有时我们需要知道流为什么会失败。
IO库定义了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。
IO库条件状态
头文件 | 类型 |
---|---|
s.eof() | 若流s的eofbit置位,则返回true |
s.fail() | 若流s的failbit或badbit置位,则返回true |
s.bad() | 若流s的badbit置位,则返回true |
s.good() | 若流s处于有效状态,则返回true |
s.clear() | 若流s中的所有条件状态位复位,就将流的状态设置为有效。返回void |
s.clear(flags) | 根据给定的flag标志位,将流s中对应条件状态位复位。返回void |
s.setstate(flags) | 根据给定的flag标志位,将流s中对应条件状态位置位。返回void |
s.rdstate() | 返回流s的当前条件状态。 |
管理条件状态
auto old_state = cin.rdstate(); //记住cin的当前状态
cin.setstate(old_state); //将cin置为原有状态
例子:编写函数,接受一个istream&参数,并返回istream&。此函数从给定流中读取数据,直至遇到文件结束标识时停止,将读取的数据打印在标准输出上。完成操作后,在返回流之前,将流进行复位,使其处于有效状态。
/*************************************************************************
> File Name: 8.1.cpp
> Author: Tanswer
> Mail: 98duxm@gmail.com
> Created Time: 2016年10月27日 星期四 21时23分45秒
************************************************************************/
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
istream &function(istream &in)
{
int word;
while(in >> word , !in.eof())
{
if(in.bad())
throw runtime_error("IO Stream error.");
cout << word << endl;
}
in.clear();
return in;
}
int main()
{
cout << "please input some words,enter ctrl+Z to end\n";
function(cin);
return 0;
}
什么情况下, while(cin >> i) /*...*/
循环会终止?
答:遇到无效输入,或者文件结束符或者IO流错误。
管理输出缓冲
每个输出流都管理一个缓冲区,用来保存程序读写的数据。有了缓冲机制,操作系统将程序的多个输出操作组合成单一的系统级写操作。由于系统的写操作很耗时,允许操作系统将多个输出组合成单一的系统级写操作能带来很大的性能提升。
导致缓冲刷新的原因:
- 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
- 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。
使用操作符如endl 来显式刷新缓冲区。
cout << “hi” << endl; //输出hi和一个换行,然后刷新缓冲区
cout << “hi” << flush; //输出hi,然后刷新缓冲区,不附加任何字符
cout << “hi” << ends; //输出hi 和一个空字符,然后刷新缓冲区- 在每个输出之后,都可以用操作符 unitbuf 设置流的内部状态,来清空缓冲区。默认情况下,对err是设置unitbuf的,因此写到cerr的内容都是立即刷新的。
- 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如:在默认情况下,cin和cerr都关联到cout。因此读cin和cerr都会导致cout的缓冲区被刷新。
如果想在每次输出操作后都刷新缓冲区,这样做:
cout << unitbuf; //此后所有输出操作后都会立即刷新缓冲区
cout << nounitbuf; //重置流,回到正常的系统管理的缓冲区刷新机制
ps:如果程序崩溃,输出缓冲区是不会被刷新的,当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。
文件输入输出
fstream | 特有的操作 |
---|---|
fstream fstrm | 创建一个未绑定的文件流。fstream是头文件fstream中定义的一个类型 |
fstream fstrm(s) | 创建一个fstream,并打开名为s的文件。s可以是string类型或者是一个指向C风格字符串的指针。这些构造函数都是exolicit的。默认的文件模式mode依赖于fstream的类型 |
fstream fstrm(s,mode) | 与前一个构造函数类似,但按指定mode打开文件 |
fstrm.open(s) | 打开名为s的文件,并将文件与fstrm绑定。s可以是一个string或者是一个指向C风格字符串的指针。默认的文件mode依赖于fstream的类型。返回void |
fstrm.close() | 关闭与fstrm绑定的文件。返回void |
fstrm.is_open() | 返回一个bool值,指出与fstrm关联的文件是否成功打开且尚未关闭 |
使用文件流对象
当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。创建文件流对象时,我们可以提供文件名(可选的)。如果提供了一个文件名,则open会自动被调用:
ifstream in(file); //构造一个ifstream并打开给定文件
ofstream out; //输出文件流未关联到任何文件
用fstream替代iostream&
在要求使用基类型对象的地方,我们可以用继承类型来替代。这意味着接受一个iostream类型引用或者指针参数的函数,可以用一个对应的fstream或sstream类型来调用。
成员函数open和close
如果我们定义了一个空文件流对象,可以 随后调用open来将它和文件关联到一起:
ifstream in(filename);
ofstream out; //输出文件未与任何文件关联
out.open(filename); //打开指定文件
因为打开文件可能会失败,通常进行open是否成功的检测是一个好习惯。
if(out) //检查open是否成功`
//成功后我们可以使用文件
为了将文件流对象关联到另外一个文件,必须首先关闭已经关联的文件。一旦文件成功关闭,我们可以打开新的文件:
in.close(); //关闭文件
in.open(filename_2); //打开另一个文件
自动构造和析构
while(condition)
{
ifstream input(file..n);
if(input)
.....
else
...
}
每个循环步构造一个新的名为input的ifstream的对象,并打开给定的文件,如果open成功或者失败,分别执行对象的操作。因为input是while循环的局部变量,所以它在每个循环步中都要创建和销毁一次。当一个fstream离开其作用域时,与之关联的文件会自动关闭(当一个fstream对象被销毁时,close会自动被调用)。
文件模式
每个流都有一个文件模式(file mode),用来指出如何使用文件。
文件模式 |
---|
in —————- 以读方式打开(ifstream默认) |
out ————–以写方式打开(ofstream默认) |
app ————–每次写操作前均定位到文件末尾 |
ate —————-打开文件后立即定位到文件末尾 |
trunc ————截断文件 |
binary ———–以二进制方式进行IO |
指定文件模式时有如下限制:
- 只可以对 ofstream 或 fstream 对象设定 out 模式。
- 只可以对 ifstream 或 fstream 对象设定 in 模式。
- 只有当 out 被设定时,才可设定 trunc 模式。
- 只要trunc没被设定,就可设定app模式。在app模式下,即使没有显式指定out模式,文件也总是以输出方式被打开。
- 默认情况下,即使我们没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作。
- ate和binary模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。
每个文件流都定义了一个默认的文件模式,当我们未指定文件模式时,就使用此默认模式。
以out模式打开的文件会丢弃已有数据
默认情况下,当我们打开一个ofstream时,文件的内容会被丢弃。保留被ofstream打开的文件的内容的方法是:显式指定app模式或in模式。
//在这几条语句中,file1都被截断
ofstream out("file1"); //隐含以输出模式打开文件并截断
ofstream out1("file1",ofstream::out); //隐含地截断文件
ofstream out2("file1",ofstream::out | ofstream::trunc);
//为了保留内容,我们这样做
ofstream app("file1",ofstream::app); //隐含为输出模式
ofstream app("file1",ofstream::out | ofstream::app);
ofstream app("file1",ofstream::out | ofstream::in);
ps:在每次打开文件时,都要设置文件模式,可能是显式设置,也可能是隐式设置。当程序未指定模式时,就使用默认值。
例题:编写函数,以读模式打开一个文件,将其内容读入到一个string的vector中,将每一行作为一个独立的元素存于vector中。
/*************************************************************************
> File Name: 8.4.cpp
> Author: Tanswer
> Mail: 98duxm@gmail.com
> Created Time: 2016年10月28日 星期五 17时45分43秒
************************************************************************/
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <fstream>
using namespace std;
int main()
{
ifstream in("8.4.txt"); //此目录下存在8.4.txt文件
if(!in)
{
cerr << "can't open the file" << endl;
return -1;
}
string line;
vector<string> words;
while(getline(in,line)) //行独立
{
words.push_back(line);
}
in.close();
vector<string>::const_iterator it = words.begin();
while(it != words.end())
{
cout << *it << endl;
++it;
}
return 0;
}
输出结果:
string流
stringstream 特有的操作 |
---|
sstream strm; ———–strm是一个未绑定的stringstream对象,sstream 是头文件sstream中定义的一个类型 |
sstream strm(s); ——-strm是一个sstream对象,保存string s的一个拷贝。 |
strm.str() ——————- 返回strm所保存的string的拷贝 |
strm.str(s) ——————将string s拷贝到strm 中。返回void |
例子:编写程序,将来自文件中的行保存在一个vector中。然后使用一个istringstream从vector读取数据,每次读取一个单词。
/*************************************************************************
> File Name: 8.10.cpp
> Author: Tanswer
> Mail: 98duxm@gmail.com
> Created Time: 2016年10月28日 星期五 20时12分44秒
************************************************************************/
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <fstream>
#include <sstream> //不要忘了头文件 我开始忘了。。。
using namespace std;
int main()
{
ifstream in("8.4.txt");
if(!in)
{
cerr << "Can't open input file" << endl;
return -1;
}
string line;
vector<string> words;
while(getline(in,line))
{
words.push_back(line); //保存进vector<string>
}
in.close();
//从vector中读取数据
vector<string>::const_iterator it = words.begin();
while(it != words.end())
{
//将line_str绑定到刚读入的行
istringstream line_str(*it);
string word;
while(line_str >> word) //读入到word
cout << word << endl; //输出
++it;
}
return 0;
}