已经介绍的大部分IO库设施:
istream(输入流)类型,提供输入操作
ostream(输出流)类型,输出
cin,一个istream对象,从标准输入读取数据
cout,一个ostream对象,写入
cerr,ostream,用于输出程序错误信息,写入到标准错误
“>>”运算符,用来从一个istream对象读取输入数据
"<<"用来向一个ostream对象写入输出数据
getline函数,从一个给定的istream读取一行数据,存入一个给定的stream对象
IO类
默认情况下,我们已经使用过的IO类型和对象都是关联到控制台的。iostream定义了用于读写流的基本类型,fstream定义了读写命名文件的类型,sstream定义了读写内存string对象的类型。
IO对象无拷贝或赋值
由于不能拷贝IO对象,因此我们也不能将形参或返回类型设置为流类型。进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
条件状态
管理条件状态
流对象的rdstate成员返回一个iostate值,对应流的当前状态。setstate操作将给定条件位置位,表示发生了对应的错误。clear成员是一个重载的成员。
clear不接受参数的版本清除(复位)所有错误标志位。执行clear()后,调用good()会返回true,使用方法:
auto old_state=cin.rdstate();//记住cin当前状态
cin.clear(); //使用cin有效
process_input(cin);
cin.setstate(old_state);//将cin置为原有状态
带参数的clear接受一个iostate值,表示流的新状态。为了复位单一的条件状态位。我们首先用rdstate读出当前条件状态,然后用位操作将所需位复位来生成新的状态。例如,下面的代码将failbit和badbit复位,但保持eofbit不变:
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
练习8.1
接受一个istream&参数,返回类型也是istream&。此函数从给定流中读取数据,直至遇到文件结束标识时停止。他将读取的数据打印在标准输出,完成之后,在返回流之前,对流进行复位,使其处于有效状态
istream &func(istream &in)
{
std::string buf;
while(in>>buf)
std::cout<<buf<<std::endl;
in.clear();
return in;
}
int main()
{
func(cin);
}
管理输出缓冲
刷新输出缓冲区
IO库还有两个与endl相似的操纵符:flush、ends,flush刷新缓冲区,不附加任何额外字符;ends向缓冲区插入一个空字符,然后刷新缓冲区
cout<<“hi”<<flush;
cout<<“hi”<<ends;
unitbuf操纵符
如果想在每次输出操作后都刷新缓冲区,我们可以使用unitbuf操纵符。它告诉流在接下来每次写操作之后都进行一次flush操作。而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制:
cout<<unitbuf;
cout<<nounitbuf;
关联输入和输出流
当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将cout和cin关联在一起。
tie有两个重载版本,一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。tie的第二个版本接受一个指向ostream的指针,将自己关联到ostream。即x.tie(&o)将流x关联到输出流o
cin.tie(&cout);
//old_tie指向当前关联到cin的流
ostream *old_tie=cin.tie(nullptr);//cin不再与其他流关联
//将cin与cerr关联:cin应与cout关联
cin.tie(&cerr); //读取cin会刷新cerr而不是cout
cin.tie(old_tie); //重建cin与cout间的正常关联
为了将一个给定的流关联到一个新的输出流,我们将新流的指针传递给了tie。为了彻底解开流的关联,我们传递一个空指针。每个流同时最多关联到一个流,但多个流可以同时关联到一个ostream
文件输入输出
使用文件流对象
练习8.4
编写函数,以读模式打开一个文件,将其内容读入到一个string的vector中,将每一行作为一个独立的元素存于vector中
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
int main()
{//E:/123.txt 为文件data的文件名及其相对路径
//(是相对于可执行程序所在目录的相对路径)
ifstream in("E:/123.txt");
if(!in)
{
cerr<<"无法打开输入文件"<<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;
}
}
练习8.5
将每一个单词作为一个独立的元素存储
while(in>>line)
练习8.6
重写书店程序,从一个文件中读取交易记录。将文件名作为一个参数传递给main
#include <iostream>
#include <fstream>
#include "Sales_data.h"
using namespace std;
int main(int argc, char *argv[]) {
if (argc != 2) {
cerr << "请给出文件名" << endl;
return -1;
}
ifstream in(argv[1]);
if (!in) {
cerr << "无法打开输入文件" << endl;
return -1;
}
Sales_data total; // 保存当前求和结果的变量
if (total.read(in, total)) { // 读入第一笔交易记录
Sales_data trans; // 保存下一条交易数据的变量
while (trans.read(in, trans)) { // 读入剩余的交易
if (total.isbn() == trans.isbn()) // 检查 isbn
total = total.add(total, trans); // 更新变量 total 当前的值
else {
total.print(cout, total) << endl; // 输出结果
total = trans; // 处理下一本
}
}
total.print(cout, total) << endl; // 输出最后一条交易
}
else { // 没有输入任何信息
cerr << "没有数据" << endl; // 通知用户
return -1;
}
return 0;
}
文件模式
每个流都有一个关联的文件模式,用来指出如何使用文件
无论用哪种方式打开文件,我们都可以指定文件模式,调用open打开文件时或者用一个文件名初始化流来隐式打开文件也可以:
只可以对ofstream或fstream对象设定out模式
只可以对ifstream或fstream对象设定in模式
只有当out也被设定时才可设定trunc模式
只要trunc没被设定,就可以设定app模式。在app模式下,即使没有显式指定out模式,文件也总是以输出方式打开
ate和binary模式可用于任何类型的文件流对象,且可以与其它任何文件模式组合使用
每个文件流类型都定义了一个默认的文件模式,当我们未指定文件模式时,就使用默认模式。与ifstream关联的文件默认以in模式打开;与ofstream关联的文件默认以out模式打开;与ifstream关联的文件默认以in和out打开。
以out模式打开文件会丢弃已有数据
//file1都会被截断
ofstream out("file1");
ofstream out("file1",ofstream::out);
ofstream out("file1",ofstream::out | ofstream::trunc);
//为了保留文件内容,显式指定app模式
ofstream out("file2",ofstream::app);
ofstream out("file2",ofstream::out | ofstream::app);
保留被ofstream打开的文件中已有数据的唯一方法是显式指定app或in模式
每次调用open时都会确定文件模式
对于一个给定流,每当打开文件时,都可以改变其文件模式
ofstream out;
out.open("scratchpad");//模式隐含设置为输出和截断
out.close();
out.open("precious",ofstream::app);//模式为输出和追加
out.close();
第一个文件隐式地以out模式打开。out模式意味着同时使用trunc模式。因此当前目录下为scratchpad的文件将被清空。当打开precious的文件时,指定了append模式,文件已有数据得以保留,所有写操作在文件末尾
string流
sstream头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样istringstream从string读取数据,ostringstream向string写入数据,而头文件stringstream都可以
使用istringstream
当我们的某些工作是对整行文本进行处理,而其他一些工作是处理行内的单个单词时可以使用
假定有一个文件,列出了一些人和它们的电话号码。某些人只有一个电话号码,而另一些人有多个。我们输入格式为:
文件中每条记录都以一个人名开始,后面跟随一个或多个电话号码,首先定义一个简单的类来描述输入数据:
struct PersonInfo{
string name;
vector<string> phones;
};
我们的程序会读取数据文件,并创建一个PersonInfo的vector。在一个循环中处理输入数据:
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);
}
练习8.9
打印一个istringstream的内容
#include <iostream>
#include <sstream>
#include <string>
#include <stdexcept>
#include "Sales_data.h"
using namespace std;
istream &f(istream &in)
{
string v;
while(in>>v,!in.eof())
{
if(in.bad())
throw runtime_error("IO流错误");
if(in.fail())
{
cerr << "数据错误,请重试" << endl;
in.clear();
in.ignore(100,'\0');
continue;
}
cout<<v<<endl;
}
in.clear();
return in;
}
int main(int argc,char *argv[])
{
ostringstream msg;
msg<<"C++ P 第五"<<endl;
istringstream in(msg.str());
f(in);
return 0;
}
练习8.10
将来自文件中的一行保存在一个vector</string/>中,然后使用一个istringstream从vector读取数据元素,每次读取一个单词
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
int main(int argc,char *argv[])
{
ifstream in("E:/2.txt");
if(!in)
{
cerr<<"无法打开输入文件"<<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())
{
istringstream line_str(*it);
string word;
while(line_str>>word)
cout<<word<<'\n';
++it;
}
}
练习8.11
本节的程序在外层while循环定义了istringstream对象。如果record对象定义在循环之外,需要怎样修改
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
struct PersonInfo {
string name;
vector<string> phones;
};
int main(int argc,char *argv[])
{
string line, word; // 分别保存来自输入的一行和单词
vector<PersonInfo> people;
istringstream record;
// 逐行从输入读取数据,直至 cin 遇到文件尾(或其他错误)
while (getline(cin, line) && line != "Q") {
PersonInfo info;
record.clear();
record.str(line);
record >> info.name;
while (record >> word)
info.phones.push_back(word);
people.push_back(info);
}
for(auto &p:people)
{
cout<<p.name<<" ";
for(auto &s:p.phones)
{
std::cout<<s<<" ";
}
cout<<endl;
}
}
使用ostringstream
当我们逐步构造输出,希望最后一期打印时,使用ostringstream。对于上一节例子,我们可能想逐个验证电话号码并改变其格式。如果所有号码都是有效的,我们希望输出一个新的文件,包含改变格式后的号码。对于无效号码,我们不会将它们输出到新文件中,而是打印一条包含人名和无效号码的错误信息
对于每个人,直到验证完所有的电话号码后才可以进行输出操作,我们可以先将输出内容写入到一个内存ostringstream中:
for(const auto &entry:people)
{
ostringstream formatted,badNums;
for(const auto &nums:entry.phones)
{
if(!valid(nums)){
badNums<<" "<<nums;//将数的字符串形式存入badNums
}
else
//将格式化的字符串”写入“formatted
formatted<<" "<<format(nums);
}
if(badNums.str().empty())
os<<entry.name<<" "
<<formatted.str()<<endl;
else
cerr<<"input error: "<<entry.name
<<"invalid number:"<<badNums.str()<<endl;
}
假定已有两个函数,valid和format,分别完成电话号码验证和改变格式的功能。
练习8.13
从一个文件读取数据并输出
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
struct PersonInfo {
string name;
vector<string> phones;
};
string format(const string &s)
{
return s;
}
bool valid(const string &s)
{
return true;
}
int main(int argc,char *argv[])
{
string line, word; // 分别保存来自输入的一行和单词
vector<PersonInfo> people;
istringstream record;
ifstream in("E:/2.txt");
if (!in) {
cerr << "无法打开输入文件" << endl;
return -1;
}
while (getline(in, line)) {
PersonInfo info; // 创建一个保存此记录数据的对象
record.clear(); // 重复使用字符串流时,每次都要调用 clear
record.str(line); // 将记录绑定到刚读入的行(将 line 拷贝到 record 中)
record >> info.name; // 读取名字
while (record >> word) // 读取电话号码
info.phones.push_back(word); // 保持它们
people.push_back(info); // 将此记录追加到 people 末尾
}
ostringstream os;
for (const auto &entry : people) { // 对 people 中每一项
ostringstream formatted, badNums; // 每个循环步创建的对象
for (const auto &nums : entry.phones) { // 对每个数
if (!valid(nums))
badNums << " " << nums; // 将数的字符串形式存入 badNums
else
// 将格式化的字符串"写入" formatted
formatted << " " << format(nums);
}
if (badNums.str().empty()) // 没有错误的数
// 打印名字和格式化的数
os << entry.name << " " << formatted.str() << endl;
else
// 否则,打印名字和错误的数
cerr << "input error: " << entry.name
<< " invalid numbers(s) " << badNums.str() << endl;
}
cout<<os.str()<<endl;
}