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置位,返回trues.fail(); //若failbit或badbit置位,返回trues.bad(); //若badbit置位,返回true,系统级错误,IO无法再使用s.good(); //若流 s 处于有效状态,则返回 trues.clear(); //复位:s的所有条件状态位。返回void//其实就是将流的状态设置为有效,才能继续进行操作s.clear(flags); //复位:s对应条件状态位。返回void//flags的类型为strm::iostate,如cin.badbits.setstate(flags); //置位:s对应条件状态位。返回void//flags:如cin.failbits.rdstate(); //返回s的当前条件状态,返回值类型为strm::iostate/***************例子******************/string word;while (cin >> word); //判断流状态是否有效最常用方法//仅限于是否有效,如果失败,为什么失败就无法知道。auto old_state = cin.rdstate(); //记住 cin 的当前状态cin.clear(); //状态全部复位,使cin有效process_input(cin); //使用cincin.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,0showbase //对整型值输出表示进制的前缀*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.41421Precision: 12, Value : 1.41421356237Precision: 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.421scientific: 1.414214e+002fixed decimal: 141.421356hexadecimal: 0x1.1ad7bcp+7use defaults: 141.421
打印小数点
cout << 10.0 << endl; //打印 10cout << 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 cold: 3.14159next coli:-16 next cold:3.14159 next coli: -16next cold: 3.14159next coli:- 16next cold: 3.14159next coli: -#########16next cold: #####3.14159next col
非格式化IO操作
非格式化IO操作允许我们将一个流当作一个无解释的字节序列来处理。
单字节操作
每次一个字节地处理流,它们会读取而不是忽略空白符。
//文件尾标记:EOF,const型,在头文件cstdio中。is.get(ch); //从is读取下一个字节存入字符ch中,返回isos.put(ch); //将字符ch输出到os,返回osis.get(); //将is的下一个字节作为int返回//返回int的原因:除了ANSCII字符外,还有返回文件尾标记。//char型放不下,所以改成int型。//字符值是先转unsigned char再转int。is.unget(); //将is向后移动一个字节,从而最后读取的值又回到流中。返回isis.putback(ch); //特殊版的unget:将字符ch放回is,返回isis.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,返回isis.gcount()// 返回上一个非格式化读取操作从is读取的字节数。// 应该在任何后续未格式化输入操作之前调用gcount。// 如果在之前调用了peek 、unget或putback, 则gcount的返回值为0。os.write(source, size)// 将字符数组source中的size个字节写入os,返回osis.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)
操作示例
给定文件,内容如下:abcdefghij我们要在此文件的末尾写入新的一行,这一行包含文件中每行的相对起始位置,别忽略了行末尾的换行符。程序应该生成如下修改过的文件:abcdefghij5 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默认关联了coutostream *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同上,返回voidfs.close(); //关闭绑定的文件 。 返回 voidfs.is_open(); //与fs关联的文件是否成功打开且尚未关闭//*******************打开文件模式:modein //读模式打开,只能(i)fstream流out //写模式打开,默认trunc,只能(o)fstream流app //append模式,默认指定out模式没trunc就可以这顶appate //打开文件定位到末尾,任意流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一个之前,必须先closeout.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); //写模式 + appendofstream 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, phoneBadis >> 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();
