IO类
头文件 | 类型 | 说明 |
---|---|---|
iostream | (w)istream | 从流读取数据 |
(w)ostream | 向流写入数据 | |
(w)iostream | 读写流 | |
fstream | (w)ifstream | 从文件读取数据 |
(w)ofstream | 向文件写入数据 | |
(w)fstream | 读写文件 | |
sstream | (w)istringstream | 从string读取数据 |
(w)ostringstream | 向string写入数据 | |
(w)stringstream | 读写 string | |
w开头的处理wchar_t宽字符 |
istream、ostream是基类,利用多态,所有stream都可以像istream、ostream、iostream一样操作。
IO对象不能拷贝、赋值。
ofstream out1, out2;
out1 = out2; //错误:不能对流对象赋值
ofstream print(ofstream); //错误:不能初始化ofstream参数
out2 = print(out2); //错误:不能拷贝流对象
条件状态
io流肯定会有出现错误的时候,有些可以复位,有些直接导致崩溃。
stream::iostate; //当做bit集合使用,stream为io流机器无关类型,提供了表达条件状态的完整功能
stream::badbit; //用来指出流已崩溃
stream::failbit; //用来指出一个IO操作失败了(可恢复错误如文件达到结尾)
stream::eofbit; //用来指出流到达了文件结束
stream::goodbit; //0表示流未处于错误状态,流处于错误状态时,后续IO操作都会失败
s.eof(); //若eofbit置位,返回true
s.fail(); //若failbit或badbit置位,返回true
s.bad(); //若badbit置位,返回true,系统级错误,IO无法再使用
s.good(); //若流 s 处于有效状态,则返回 true
s.clear(); //复位:s的所有条件状态位。返回void
//其实就是将流的状态设置为有效,才能继续进行操作
s.clear(flags); //复位:s对应条件状态位。返回void
//flags的类型为strm::iostate,如cin.badbit
s.setstate(flags); //置位:s对应条件状态位。返回void
//flags:如cin.failbit
s.rdstate(); //返回s的当前条件状态,返回值类型为strm::iostate
/***************例子******************/
string word;
while (cin >> word); //判断流状态是否有效最常用方法
//仅限于是否有效,如果失败,为什么失败就无法知道。
auto old_state = cin.rdstate(); //记住 cin 的当前状态
cin.clear(); //状态全部复位,使cin有效
process_input(cin); //使用cin
cin.setstate(old_state); //将cin置为原有状态
//复位 failbit 和 badbit, 保持其他标志住不变
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
格式化IO操作
控制IO如何格式化的细节:整型值是几进制、浮点值的精度、一个输出元素的宽度。
标准库定义了一组操纵符来修改格式状态。
一个操纵符是一个函数或是一个对象,能用作<<、>>的运算对象,操纵符也返回它所处理的流对象,所以当操纵符改变流的格式状态时,通常改变后的状态对所有后续IO都生效。
自行修改格式状态,最好再最后恢复到标准库默认状态。
IO中的操纵符
/*******************定义在iostream中的操纵符****************************/
//*表示默认流状态
boolalpha //将true和false输出为字符串
*noboolalpha //将true和false输出为1,0
showbase //对整型值输出表示进制的前缀
*noshowbase //不生成表示进制的前缀
showpoint //对浮点值总是显示小数点
*noshowpoint //只有当浮点值包含小数部分时才显示小数点
showpos //对非负数显示+
*noshowpos //对非负数不显示+
uppercase //在十六进制值中打印0x, 在科学记数法中打印E
*nouppercase //在十六进制值中打印0x, 在科学记数法中打印e
*dec //整型值显示为十进制
hex //整型值显示为十六进制
oct //整型值显示为八进制
//只决定下一输出大小。
left //在值的右侧添加填充字符,表示左对齐输出
*right //在值的左侧添加填充字符,表示右对齐输出,右对齐是默认格式。
internal //在符号和值之间添加填充字符
//控制负数的符号的位置,它左对齐符号,右对齐值,用空格填满所有间空间。
fixed //浮点值显示为定点十进制
scientific //浮点值显示为科学记数法
hexfloat //浮点值显示为十六进制(C++11新特性)
defaultfloat //重置浮点数格式为十进制(C++11新特性)
unitbuf //每次输出操作后都刷新缓冲区
*nounitbuf //恢复正常的缓冲区刷新方式
*skipws //输入运算符跳过空白符
noskipws //输入运算符不跳过空白符
flush //刷新 ostream 缓冲区
ends //插入空字符,然后刷新 ostream 缓冲区
endl //插入换行,然后刷新 ostream 缓冲区
/*******************定义在iomanip中的操纵符****************************/
setw(n) //指定下一个数字或字符串值的最小空间。
setfill(c) //允许指定一个字符代替默认的空格来补白输出。
setbase(b) //将整数输出为b进制
setprecision(n) //将浮点精度设置为n
控制bool值格式
cout << boolalpha << true << false << endl; //bool值以0、1输出。
cout << noboolalpha; //恢复至标准库默认格式。
控制整型格式
cout << 20 << " " << 1024 << endl; //默认十进制格式
cout << oct << 20 << " " << 1024 << endl; //八进制格式,只影响整型值。
cout << hex << 20 << " " << 1024 << endl; //十六进制格式
cout << dec << 20 << " " << 1024 << endl; //十进制格式
//输出如下:
//default: 20 1024
//octal: 24 2000
//hex: 14 400
//decimal: 20 1024
在输出中指出进制
cout << showbase; //以为进制格式显示整型值,如0x12十六进制,014八进制
cout << 20 << " " << 1024 << endl; //默认十进制格式
cout << oct << 20 << " " << 1024 << endl; //八进制格式,只影响整型值。
cout << hex << 20 << " " << 1024 << endl; //十六进制格式
cout << dec << 20 << " " << 1024 << endl; //十进制格式
cout << noshowbase; //恢复默认状态
//输出如下:
//default: 20 1024
//octal: 024 2000
//hex: 0x14 400
//decimal: 20 1024
控制浮点数格式
- 以多高精度(多少个数字)打印浮点值
- 数值是打印为十六进制、定点十进制还是科学记数法形式
- 对于没有小数部分的浮点值是否打印小数点
指定打印精度
cout << cout.precision(); //当前精度值
cout.precision(12); //设置打印精度为12位数字。
cout << setprecision(3); //设置打印精度为3。
/**********************************/
//返回当前精度值
cout << "Precision:" << cout.precision() << ", Value:" << sqrt(2.0) << endl;
cout.precision(12); //将打印精度设置为12位数字
cout << "Precision: " << cout.precision() << ", Value : " << sqrt(2.0) << endl;
cout << setprecision(3); //另一种设置精度的方法:setprecision操纵符
cout << "Precision: " << cout.precision() << ", Value: " << sqrt(2.0) << endl;
//输出结果如下:
Precision: 6 , Value : 1.41421
Precision: 12, Value : 1.41421356237
Precision: 3 , Value : 1.41
指定浮点数计数法
cout << 100*sqrt(2.0);
cout << scientific << 100*sqrt(2.0);
cout << fixed<< 100*sqrt(2.0);
cout << hexfloat << 100*sqrt(2.0);
cout << defaultfloat << 100*sqrt(2.0);
default_format: 141.421
scientific: 1.414214e+002
fixed decimal: 141.421356
hexadecimal: 0x1.1ad7bcp+7
use defaults: 141.421
打印小数点
cout << 10.0 << endl; //打印 10
cout << showpoint << 10.0 //打印10.0000
<< noshowpoint << endl; //恢复小数点的默认格式
输出空白(对齐)
int i = -16;
double d = 3.14159;
//补白第一列,使用检出中最小12个位置
cout<<"i:" << setw(12) << i << "next col" << '\n'
<<"d:" << setw(12) << d << "next col" << '\n';
cout << left //补白第一列,左对齐所有列
<< "i:" << setw(12) << i << "next col" << '\n'
<< "d:" << setw(12) << ct<< "next col "<< '\n'
<<right; //恢复正常对齐
cout << right //补白第一列,右对齐所有列
<< "i:" << setw(12) << i << "next col" << '\n
<< "d:" << setw(12) << d << "next col" << '\n';
cout << internal //补白第一列,但补在域的内部
<< "i:" << setw(12) << i << "next col" << '\n'
<< "d:" << setw(12) << d << "next col" << '\n';
cout << setfill('#') //补白第一列,用#作为补白宇符
<< "i:" << setw(12) << i << "next col" << '\n'
<< "d:" << setw(12) << d << "next col" << '\n'
<< setfill(''); //恢复正常的补白宇符
//输出结果如下。
i: -16next col
d: 3.14159next col
i:-16 next col
d:3.14159 next col
i: -16next col
d: 3.14159next col
i:- 16next col
d: 3.14159next col
i: -#########16next col
d: #####3.14159next col
非格式化IO操作
非格式化IO操作允许我们将一个流当作一个无解释的字节序列来处理。
单字节操作
每次一个字节地处理流,它们会读取而不是忽略空白符。
//文件尾标记:EOF,const型,在头文件cstdio中。
is.get(ch); //从is读取下一个字节存入字符ch中,返回is
os.put(ch); //将字符ch输出到os,返回os
is.get(); //将is的下一个字节作为int返回
//返回int的原因:除了ANSCII字符外,还有返回文件尾标记。
//char型放不下,所以改成int型。
//字符值是先转unsigned char再转int。
is.unget(); //将is向后移动一个字节,从而最后读取的值又回到流中。返回is
is.putback(ch); //特殊版的unget:将字符ch放回is,返回is
is.peek(); //将下一个字节作为int返回,但不从流中删除它
//返回int的原因:除了ANSCII字符外,还有返回文件尾标记。
//char型放不下,所以改成int型。
//字符值是先转unsigned char再转int。
int ch; //使用一个int,而不是一个char来保存get()的返回值
char ch; //错误!!永远读取下去,不会停止
while((ch = cin.get()) != EOF)
cout.put(ch); //循环读取并输出输入中的所有数据
多字节操作
is.get(cp, size, delim)
// 从is中读取最多size个字节到字符数组cp中
// 在以下情况下停止读取:
// 1、读取了size个字符
// 2、遇到分隔符delim,delim还在流中,不会保存到数组。
// 3、遇到文件尾
is.getline(cp, size, delim)
// 与上get类似,区别是会读取delim并丢弃。
is.read(cp, size)
// 读取最多size个字节,存入字符数组cp,返回is
is.gcount()
// 返回上一个非格式化读取操作从is读取的字节数。
// 应该在任何后续未格式化输入操作之前调用gcount。
// 如果在之前调用了peek 、unget或putback, 则gcount的返回值为0。
os.write(source, size)
// 将字符数组source中的size个字节写入os,返回os
is.ignore(size=1, delim=EOF)
// 读取并忽略最多size个字符,包括delim。
随机访问
比如首先读取最后一行,然后读取第一行。
为了支持随机访问,IO类型维护一个标记来确定下一个读写操作在哪里,标准库提供了一对函数,来将标记定位到指定位置,以及获取标记当前位置。
- seek:定位到流中给定的位置
- tell:获得当前位置
所有流都有这两个方法,但是不一定会做事。istream、ostream类型通常不支持随机访问,一般是fstream、sstream类型 。
一个流中只有一个标记,没有区分读标记、写标记。由于只有单一的标记,因此只要我们在读写操作间切换,就必须进行seek来重定位标记。
注意,随机IO本质上是依赖于系统的 。
//g:get输入流,p:put输出流
tellg() //返回输入流标记位置,返回pos_type类型
tellp() //返回输出流标记位置,返回pos_type类型
//标记重定位到指定的绝对地址
//pos:类型pos_type(机器相关)
seekg(pos) //在is中将标记重定位到pos绝对地址
seekp(pos) //在os中将标记重定位到pos绝对地址
//标记重定位到from之前、之后off个字符位置,
//off:类型off_type(机器相关),正数向前,负数向后
//from取值:
//• beg, 偏移量相对于流开始位置,如fstream::beg
//• cur, 偏移量相对于流当前位置
//• end, 偏移量相对于流结尾位置
seekp(off, from)
seekg(off, from)
操作示例
给定文件,内容如下:
abcd
efg
hi
j
我们要在此文件的末尾写入新的一行,这一行包含文件中每行的相对起始位置,别忽略了行末尾的换行符。
程序应该生成如下修改过的文件:
abcd
efg
hi
j
5 9 12 14
代码如下:
//思路:一行一行读取,获得每行长度,累加字节数就是下一行的起始位置,别漏了换行符。
int main(){
//以读写方式打开文件,并定位到文件尾
fstream inOut("copyOut", fstream::ate|fstream::in|fstream::out);
if(!inOut){ //打开文件失败
cerr << "Unabletoopenfile!" << endl;
return EXIT_FAILURE;
}
//inOut以ate模式打开,因此一开始就定义到其文件尾
auto end_mark = inOut.tellg(); //记住原文件尾位置
inOut.seekg(0, fstream::beg); //重定位到文件开始
size_t cnt = 0; //字节数累加器
string line; //保存输入中的每行
//继续读取的条件:还未遇到错误且还在读取原数据
while(inOut && inOut.tellg() != end_mark && getline(inOut, line)){
cnt += line.size() + 1; //加1表示换行符
auto mark = inOut.tellg(); //记住读取位置
inOut.seekp(0,fstream::end); //将写标记移动到文件尾
inOut << cnt; //捡出累计的长度
if(mark != end_mark) inOut << " "; //如果不是最后一行,打印一个分隔符
inOut.seekg(mark); //恢复读位置
}
inOut.seekp(0, fstream::end); //定位到文件尾
inOut << "\n"; //在文件尾输出一个换行符
return 0;
}
管理输出缓冲
程序崩溃,输出缓冲不会刷新。
输出缓冲刷新的可能原因:
- main函数return。
- 缓冲区满。
- endl操纵符显式刷新。
- unitbuf设置内部状态。
- 输出流关联到另一个流。标准库默认将cin、cerr关联cout,读cin导致cout刷新。
为什么要输出流和输入流关联?**
比如程序当前需要用户输入字符,很可能需要先给提示信息,那可以考虑cin触发cout将提示信息输出就能很好解决问题。所以这在交互式系统中很常见。
流关联关系:其他流对输出流是多对(1, 0)的关系。要么关联1个,要么没关联,输出流可以关联输出流。
cout << "hi! " << endl; //输出hi+换行,然后刷新缓冲区
cout << "hi! " << flush; //输出hi, 然后刷新缓冲区
cout << "hi!" << ends; //输出hi+space,然后刷新缓冲区
//unitbuf理解起来就是只能缓冲一个的单元buf,就是相当于没有缓冲(每次都会触发刷新嘛)。
cout << unitbuf; //后续输出都立即刷新缓冲
cout << nounitbuf; //回到正常的缓冲方式
cin.tie(&cout); //cin默认关联了cout
ostream *old_tie = cin.tie(nullptr); //cin解关联,同时返回当前关联流
cin.tie(&cerr); //读取cin刷新cerr,这不是个好主意
cin.tie(old_tie); //重建cin和cout间的正常关联
fstream:文件IO
支持的操作
fstream fs; //创建一个未绑定的文件流
fstream fs(s); //默认模式打开ss可以是文件、string或C风格字符数组
fstream fs(s, mode); //mode模式打开s,s同上
fs.open(s); //打开并绑定s,s同上,返回void
fs.close(); //关闭绑定的文件 。 返回 void
fs.is_open(); //与fs关联的文件是否成功打开且尚未关闭
//*******************打开文件模式:mode
in //读模式打开,只能(i)fstream流
out //写模式打开,默认trunc,只能(o)fstream流
app //append模式,默认指定out模式没trunc就可以这顶app
ate //打开文件定位到末尾,任意流
trunc //截断文件,就是定位到文件头,会覆盖原内容,只能在out模式下
binary //二进制方式读、写,任意流
例子
ifstream in(ifile); //默认in模式打开文件
ofstream out; //输出文件流,未关联到任何文件
out.open("fuck1"); //默认out+trunc模式打开文件。失败,failbit置位
if(out){ //打开后判断是个好习惯,good()返回true,goodbit置位
out.open("fuck1"); //失败,一次只能绑定一个文件。
out.close(); //再open一个之前,必须先close
out.open("fuck2"); //默认out+trunc模式打开另一个文件
}
ofstream out("file1"); //写模式并trunc截断
ofstream out2("file1", ofstream::out); //同上
ofstream out3("file1", ofstream::out | ofstream::trunc); //同上
ofstream app("file2", ofstream::app); //写模式 + append
ofstream app2("file2", ofstream:: out | ofstream::app); //同上
app2.close(); //open和close必须成双成对。
app2.open("file2", ofstream:: out | ofstream::app)
streamstream:string流
支持的操作
string s;
sstream strm; //创建一个未绑定string的流
sstream strm(s); //保存一个s的拷贝,此构造函数是explicit的
strm.str(); //返回保存的string的拷贝
strm.str(s); //s拷贝到strm中
例子
//文件target内容格式如下:名字后面接一个或多个电话号码
//morgan 2015552368 8625550123
//drew 9735550130
//lee 6095550132 2015550175 8005550000
//检查valid检查每个电话号码,分别输出正确的、错误的电话号码。
string tempLine, tempName, tempPhoneNo;
ifstream file("target"); //打开文件
while(getline(file, tempLine)){ //打开失败或者到达结尾,循环都会结束
istringstream is(tempLine); //读取一行
ostringstream phoneOK, phoneBad
is >> tempName; //名字
while(is >> tempPhoneNo){
phoneNoOK << tempName << ":";
phoneNoBad << tempName << ":"
if(valid(tempPhoneNo)){
phoneNoOK << tempPhoneNo << " ";
}
else phoneNoBad << tempPhoneNo << " ";
}
cout << phoneNoOK.str() << endl; //输出这个名字全部正确的号码
cout << phoneNoBad.str() << endl; //输出这个名字全部错误的号码
}
file.close();