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对象不能拷贝、赋值。

  1. ofstream out1, out2;
  2. out1 = out2; //错误:不能对流对象赋值
  3. ofstream print(ofstream); //错误:不能初始化ofstream参数
  4. out2 = print(out2); //错误:不能拷贝流对象

条件状态

io流肯定会有出现错误的时候,有些可以复位,有些直接导致崩溃。

  1. stream::iostate; //当做bit集合使用,stream为io流机器无关类型,提供了表达条件状态的完整功能
  2. stream::badbit; //用来指出流已崩溃
  3. stream::failbit; //用来指出一个IO操作失败了(可恢复错误如文件达到结尾)
  4. stream::eofbit; //用来指出流到达了文件结束
  5. stream::goodbit; //0表示流未处于错误状态,流处于错误状态时,后续IO操作都会失败
  6. s.eof(); //若eofbit置位,返回true
  7. s.fail(); //若failbit或badbit置位,返回true
  8. s.bad(); //若badbit置位,返回true,系统级错误,IO无法再使用
  9. s.good(); //若流 s 处于有效状态,则返回 true
  10. s.clear(); //复位:s的所有条件状态位。返回void
  11. //其实就是将流的状态设置为有效,才能继续进行操作
  12. s.clear(flags); //复位:s对应条件状态位。返回void
  13. //flags的类型为strm::iostate,如cin.badbit
  14. s.setstate(flags); //置位:s对应条件状态位。返回void
  15. //flags:如cin.failbit
  16. s.rdstate(); //返回s的当前条件状态,返回值类型为strm::iostate
  17. /***************例子******************/
  18. string word;
  19. while (cin >> word); //判断流状态是否有效最常用方法
  20. //仅限于是否有效,如果失败,为什么失败就无法知道。
  21. auto old_state = cin.rdstate(); //记住 cin 的当前状态
  22. cin.clear(); //状态全部复位,使cin有效
  23. process_input(cin); //使用cin
  24. cin.setstate(old_state); //将cin置为原有状态
  25. //复位 failbit 和 badbit, 保持其他标志住不变
  26. cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

格式化IO操作

控制IO如何格式化的细节:整型值是几进制、浮点值的精度、一个输出元素的宽度。
标准库定义了一组操纵符来修改格式状态。
一个操纵符是一个函数或是一个对象,能用作<<、>>的运算对象,操纵符也返回它所处理的流对象,所以当操纵符改变流的格式状态时,通常改变后的状态对所有后续IO都生效。
自行修改格式状态,最好再最后恢复到标准库默认状态。

IO中的操纵符

  1. /*******************定义在iostream中的操纵符****************************/
  2. //*表示默认流状态
  3. boolalpha //将true和false输出为字符串
  4. *noboolalpha //将true和false输出为1,0
  5. showbase //对整型值输出表示进制的前缀
  6. *noshowbase //不生成表示进制的前缀
  7. showpoint //对浮点值总是显示小数点
  8. *noshowpoint //只有当浮点值包含小数部分时才显示小数点
  9. showpos //对非负数显示+
  10. *noshowpos //对非负数不显示+
  11. uppercase //在十六进制值中打印0x, 在科学记数法中打印E
  12. *nouppercase //在十六进制值中打印0x, 在科学记数法中打印e
  13. *dec //整型值显示为十进制
  14. hex //整型值显示为十六进制
  15. oct //整型值显示为八进制
  16. //只决定下一输出大小。
  17. left //在值的右侧添加填充字符,表示左对齐输出
  18. *right //在值的左侧添加填充字符,表示右对齐输出,右对齐是默认格式。
  19. internal //在符号和值之间添加填充字符
  20. //控制负数的符号的位置,它左对齐符号,右对齐值,用空格填满所有间空间。
  21. fixed //浮点值显示为定点十进制
  22. scientific //浮点值显示为科学记数法
  23. hexfloat //浮点值显示为十六进制(C++11新特性)
  24. defaultfloat //重置浮点数格式为十进制(C++11新特性)
  25. unitbuf //每次输出操作后都刷新缓冲区
  26. *nounitbuf //恢复正常的缓冲区刷新方式
  27. *skipws //输入运算符跳过空白符
  28. noskipws //输入运算符不跳过空白符
  29. flush //刷新 ostream 缓冲区
  30. ends //插入空字符,然后刷新 ostream 缓冲区
  31. endl //插入换行,然后刷新 ostream 缓冲区
  32. /*******************定义在iomanip中的操纵符****************************/
  33. setw(n) //指定下一个数字或字符串值的最小空间。
  34. setfill(c) //允许指定一个字符代替默认的空格来补白输出。
  35. setbase(b) //将整数输出为b进制
  36. setprecision(n) //将浮点精度设置为n

控制bool值格式

  1. cout << boolalpha << true << false << endl; //bool值以0、1输出。
  2. cout << noboolalpha; //恢复至标准库默认格式。

控制整型格式

  1. cout << 20 << " " << 1024 << endl; //默认十进制格式
  2. cout << oct << 20 << " " << 1024 << endl; //八进制格式,只影响整型值。
  3. cout << hex << 20 << " " << 1024 << endl; //十六进制格式
  4. cout << dec << 20 << " " << 1024 << endl; //十进制格式
  5. //输出如下:
  6. //default: 20 1024
  7. //octal: 24 2000
  8. //hex: 14 400
  9. //decimal: 20 1024

在输出中指出进制

  1. cout << showbase; //以为进制格式显示整型值,如0x12十六进制,014八进制
  2. cout << 20 << " " << 1024 << endl; //默认十进制格式
  3. cout << oct << 20 << " " << 1024 << endl; //八进制格式,只影响整型值。
  4. cout << hex << 20 << " " << 1024 << endl; //十六进制格式
  5. cout << dec << 20 << " " << 1024 << endl; //十进制格式
  6. cout << noshowbase; //恢复默认状态
  7. //输出如下:
  8. //default: 20 1024
  9. //octal: 024 2000
  10. //hex: 0x14 400
  11. //decimal: 20 1024

控制浮点数格式

  • 以多高精度(多少个数字)打印浮点值
  • 数值是打印为十六进制、定点十进制还是科学记数法形式
  • 对于没有小数部分的浮点值是否打印小数点

默认六位数字精度打印,如果没有小数,不打印小数点。

指定打印精度

  1. cout << cout.precision(); //当前精度值
  2. cout.precision(12); //设置打印精度为12位数字。
  3. cout << setprecision(3); //设置打印精度为3。
  4. /**********************************/
  5. //返回当前精度值
  6. cout << "Precision:" << cout.precision() << ", Value:" << sqrt(2.0) << endl;
  7. cout.precision(12); //将打印精度设置为12位数字
  8. cout << "Precision: " << cout.precision() << ", Value : " << sqrt(2.0) << endl;
  9. cout << setprecision(3); //另一种设置精度的方法:setprecision操纵符
  10. cout << "Precision: " << cout.precision() << ", Value: " << sqrt(2.0) << endl;
  11. //输出结果如下:
  12. Precision: 6 , Value : 1.41421
  13. Precision: 12, Value : 1.41421356237
  14. Precision: 3 , Value : 1.41

指定浮点数计数法

  1. cout << 100*sqrt(2.0);
  2. cout << scientific << 100*sqrt(2.0);
  3. cout << fixed<< 100*sqrt(2.0);
  4. cout << hexfloat << 100*sqrt(2.0);
  5. cout << defaultfloat << 100*sqrt(2.0);
  6. default_format: 141.421
  7. scientific: 1.414214e+002
  8. fixed decimal: 141.421356
  9. hexadecimal: 0x1.1ad7bcp+7
  10. use defaults: 141.421

打印小数点

  1. cout << 10.0 << endl; //打印 10
  2. cout << showpoint << 10.0 //打印10.0000
  3. << noshowpoint << endl; //恢复小数点的默认格式

输出空白(对齐)

  1. int i = -16;
  2. double d = 3.14159;
  3. //补白第一列,使用检出中最小12个位置
  4. cout<<"i:" << setw(12) << i << "next col" << '\n'
  5. <<"d:" << setw(12) << d << "next col" << '\n';
  6. cout << left //补白第一列,左对齐所有列
  7. << "i:" << setw(12) << i << "next col" << '\n'
  8. << "d:" << setw(12) << ct<< "next col "<< '\n'
  9. <<right; //恢复正常对齐
  10. cout << right //补白第一列,右对齐所有列
  11. << "i:" << setw(12) << i << "next col" << '\n
  12. << "d:" << setw(12) << d << "next col" << '\n';
  13. cout << internal //补白第一列,但补在域的内部
  14. << "i:" << setw(12) << i << "next col" << '\n'
  15. << "d:" << setw(12) << d << "next col" << '\n';
  16. cout << setfill('#') //补白第一列,用#作为补白宇符
  17. << "i:" << setw(12) << i << "next col" << '\n'
  18. << "d:" << setw(12) << d << "next col" << '\n'
  19. << setfill(''); //恢复正常的补白宇符
  20. //输出结果如下。
  21. i: -16next col
  22. d: 3.14159next col
  23. i:-16 next col
  24. d:3.14159 next col
  25. i: -16next col
  26. d: 3.14159next col
  27. i:- 16next col
  28. d: 3.14159next col
  29. i: -#########16next col
  30. d: #####3.14159next col

非格式化IO操作

非格式化IO操作允许我们将一个流当作一个无解释的字节序列来处理。

单字节操作

每次一个字节地处理流,它们会读取而不是忽略空白符。

  1. //文件尾标记:EOF,const型,在头文件cstdio中。
  2. is.get(ch); //从is读取下一个字节存入字符ch中,返回is
  3. os.put(ch); //将字符ch输出到os,返回os
  4. is.get(); //将is的下一个字节作为int返回
  5. //返回int的原因:除了ANSCII字符外,还有返回文件尾标记。
  6. //char型放不下,所以改成int型。
  7. //字符值是先转unsigned char再转int。
  8. is.unget(); //将is向后移动一个字节,从而最后读取的值又回到流中。返回is
  9. is.putback(ch); //特殊版的unget:将字符ch放回is,返回is
  10. is.peek(); //将下一个字节作为int返回,但不从流中删除它
  11. //返回int的原因:除了ANSCII字符外,还有返回文件尾标记。
  12. //char型放不下,所以改成int型。
  13. //字符值是先转unsigned char再转int。
  14. int ch; //使用一个int,而不是一个char来保存get()的返回值
  15. char ch; //错误!!永远读取下去,不会停止
  16. while((ch = cin.get()) != EOF)
  17. cout.put(ch); //循环读取并输出输入中的所有数据

多字节操作

  1. is.get(cp, size, delim)
  2. // 从is中读取最多size个字节到字符数组cp中
  3. // 在以下情况下停止读取:
  4. // 1、读取了size个字符
  5. // 2、遇到分隔符delim,delim还在流中,不会保存到数组。
  6. // 3、遇到文件尾
  7. is.getline(cp, size, delim)
  8. // 与上get类似,区别是会读取delim并丢弃。
  9. is.read(cp, size)
  10. // 读取最多size个字节,存入字符数组cp,返回is
  11. is.gcount()
  12. // 返回上一个非格式化读取操作从is读取的字节数。
  13. // 应该在任何后续未格式化输入操作之前调用gcount。
  14. // 如果在之前调用了peek 、unget或putback, 则gcount的返回值为0。
  15. os.write(source, size)
  16. // 将字符数组source中的size个字节写入os,返回os
  17. is.ignore(size=1, delim=EOF)
  18. // 读取并忽略最多size个字符,包括delim。

随机访问

比如首先读取最后一行,然后读取第一行。

为了支持随机访问,IO类型维护一个标记来确定下一个读写操作在哪里,标准库提供了一对函数,来将标记定位到指定位置,以及获取标记当前位置。

  • seek:定位到流中给定的位置
  • tell:获得当前位置

所有流都有这两个方法,但是不一定会做事。istream、ostream类型通常不支持随机访问,一般是fstream、sstream类型 。

一个流中只有一个标记,没有区分读标记、写标记。由于只有单一的标记,因此只要我们在读写操作间切换,就必须进行seek来重定位标记。

注意,随机IO本质上是依赖于系统的 。

  1. //g:get输入流,p:put输出流
  2. tellg() //返回输入流标记位置,返回pos_type类型
  3. tellp() //返回输出流标记位置,返回pos_type类型
  4. //标记重定位到指定的绝对地址
  5. //pos:类型pos_type(机器相关)
  6. seekg(pos) //在is中将标记重定位到pos绝对地址
  7. seekp(pos) //在os中将标记重定位到pos绝对地址
  8. //标记重定位到from之前、之后off个字符位置,
  9. //off:类型off_type(机器相关),正数向前,负数向后
  10. //from取值:
  11. //• beg, 偏移量相对于流开始位置,如fstream::beg
  12. //• cur, 偏移量相对于流当前位置
  13. //• end, 偏移量相对于流结尾位置
  14. seekp(off, from)
  15. seekg(off, from)

操作示例

  1. 给定文件,内容如下:
  2. abcd
  3. efg
  4. hi
  5. j
  6. 我们要在此文件的末尾写入新的一行,这一行包含文件中每行的相对起始位置,别忽略了行末尾的换行符。
  7. 程序应该生成如下修改过的文件:
  8. abcd
  9. efg
  10. hi
  11. j
  12. 5 9 12 14
  13. 代码如下:
  14. //思路:一行一行读取,获得每行长度,累加字节数就是下一行的起始位置,别漏了换行符。
  15. int main(){
  16. //以读写方式打开文件,并定位到文件尾
  17. fstream inOut("copyOut", fstream::ate|fstream::in|fstream::out);
  18. if(!inOut){ //打开文件失败
  19. cerr << "Unabletoopenfile!" << endl;
  20. return EXIT_FAILURE;
  21. }
  22. //inOut以ate模式打开,因此一开始就定义到其文件尾
  23. auto end_mark = inOut.tellg(); //记住原文件尾位置
  24. inOut.seekg(0, fstream::beg); //重定位到文件开始
  25. size_t cnt = 0; //字节数累加器
  26. string line; //保存输入中的每行
  27. //继续读取的条件:还未遇到错误且还在读取原数据
  28. while(inOut && inOut.tellg() != end_mark && getline(inOut, line)){
  29. cnt += line.size() + 1; //加1表示换行符
  30. auto mark = inOut.tellg(); //记住读取位置
  31. inOut.seekp(0,fstream::end); //将写标记移动到文件尾
  32. inOut << cnt; //捡出累计的长度
  33. if(mark != end_mark) inOut << " "; //如果不是最后一行,打印一个分隔符
  34. inOut.seekg(mark); //恢复读位置
  35. }
  36. inOut.seekp(0, fstream::end); //定位到文件尾
  37. inOut << "\n"; //在文件尾输出一个换行符
  38. return 0;
  39. }

管理输出缓冲

程序崩溃,输出缓冲不会刷新。
输出缓冲刷新的可能原因:

  • main函数return。
  • 缓冲区满。
  • endl操纵符显式刷新。
  • unitbuf设置内部状态。
  • 输出流关联到另一个流。标准库默认将cin、cerr关联cout,读cin导致cout刷新。


为什么要输出流和输入流关联?**
比如程序当前需要用户输入字符,很可能需要先给提示信息,那可以考虑cin触发cout将提示信息输出就能很好解决问题。所以这在交互式系统中很常见。
流关联关系:其他流对输出流是多对(1, 0)的关系。要么关联1个,要么没关联,输出流可以关联输出流。

  1. cout << "hi! " << endl; //输出hi+换行,然后刷新缓冲区
  2. cout << "hi! " << flush; //输出hi, 然后刷新缓冲区
  3. cout << "hi!" << ends; //输出hi+space,然后刷新缓冲区
  4. //unitbuf理解起来就是只能缓冲一个的单元buf,就是相当于没有缓冲(每次都会触发刷新嘛)。
  5. cout << unitbuf; //后续输出都立即刷新缓冲
  6. cout << nounitbuf; //回到正常的缓冲方式
  7. cin.tie(&cout); //cin默认关联了cout
  8. ostream *old_tie = cin.tie(nullptr); //cin解关联,同时返回当前关联流
  9. cin.tie(&cerr); //读取cin刷新cerr,这不是个好主意
  10. cin.tie(old_tie); //重建cin和cout间的正常关联

fstream:文件IO

支持的操作

  1. fstream fs; //创建一个未绑定的文件流
  2. fstream fs(s); //默认模式打开ss可以是文件、string或C风格字符数组
  3. fstream fs(s, mode); //mode模式打开s,s同上
  4. fs.open(s); //打开并绑定s,s同上,返回void
  5. fs.close(); //关闭绑定的文件 。 返回 void
  6. fs.is_open(); //与fs关联的文件是否成功打开且尚未关闭
  7. //*******************打开文件模式:mode
  8. in //读模式打开,只能(i)fstream流
  9. out //写模式打开,默认trunc,只能(o)fstream流
  10. app //append模式,默认指定out模式没trunc就可以这顶app
  11. ate //打开文件定位到末尾,任意流
  12. trunc //截断文件,就是定位到文件头,会覆盖原内容,只能在out模式下
  13. binary //二进制方式读、写,任意流

例子

  1. ifstream in(ifile); //默认in模式打开文件
  2. ofstream out; //输出文件流,未关联到任何文件
  3. out.open("fuck1"); //默认out+trunc模式打开文件。失败,failbit置位
  4. if(out){ //打开后判断是个好习惯,good()返回true,goodbit置位
  5. out.open("fuck1"); //失败,一次只能绑定一个文件。
  6. out.close(); //再open一个之前,必须先close
  7. out.open("fuck2"); //默认out+trunc模式打开另一个文件
  8. }
  9. ofstream out("file1"); //写模式并trunc截断
  10. ofstream out2("file1", ofstream::out); //同上
  11. ofstream out3("file1", ofstream::out | ofstream::trunc); //同上
  12. ofstream app("file2", ofstream::app); //写模式 + append
  13. ofstream app2("file2", ofstream:: out | ofstream::app); //同上
  14. app2.close(); //open和close必须成双成对。
  15. app2.open("file2", ofstream:: out | ofstream::app)

streamstream:string流

支持的操作

  1. string s;
  2. sstream strm; //创建一个未绑定string的流
  3. sstream strm(s); //保存一个s的拷贝,此构造函数是explicit的
  4. strm.str(); //返回保存的string的拷贝
  5. strm.str(s); //s拷贝到strm中

例子

  1. //文件target内容格式如下:名字后面接一个或多个电话号码
  2. //morgan 2015552368 8625550123
  3. //drew 9735550130
  4. //lee 6095550132 2015550175 8005550000
  5. //检查valid检查每个电话号码,分别输出正确的、错误的电话号码。
  6. string tempLine, tempName, tempPhoneNo;
  7. ifstream file("target"); //打开文件
  8. while(getline(file, tempLine)){ //打开失败或者到达结尾,循环都会结束
  9. istringstream is(tempLine); //读取一行
  10. ostringstream phoneOK, phoneBad
  11. is >> tempName; //名字
  12. while(is >> tempPhoneNo){
  13. phoneNoOK << tempName << ":";
  14. phoneNoBad << tempName << ":"
  15. if(valid(tempPhoneNo)){
  16. phoneNoOK << tempPhoneNo << " ";
  17. }
  18. else phoneNoBad << tempPhoneNo << " ";
  19. }
  20. cout << phoneNoOK.str() << endl; //输出这个名字全部正确的号码
  21. cout << phoneNoBad.str() << endl; //输出这个名字全部错误的号码
  22. }
  23. file.close();