一、I/O流的概念及流类库结构

流用于程序与外界环境的信息交换,外界设备都可以抽象成文件

  • 当程序与外界环境进行信息交换时,存在着两个对象:程序中的对象、文件对象、流
  • 一种抽象,负责在数据的生产者和数据的消费者之间建立联系,并管理数据的流动。

流对象与文件操作

输入输出流 - 图1

提取与插入

  • 读操作在流数据抽象中被称为(从流中)提取 ,提取符:>>
  • 写操作被称为(向流中)插入。插入符:<<

1.1流类库结构

iostream 标准设备输入输出

fstream 磁盘文件输入输出

stringstream 内存字符串输入输出

15659274391778.png

15659274423015.png

二、输出流

2.1输出流基础概念

  • ostream
  • ofstream
  • ostringstream

预先定义的输出流对象

  • cout 标准输出
  • cerr 标准错误输出,没有缓冲,发送给它的内容立即被输出。
  • clog 类似于cerr,但是有缓冲,缓冲区满时被输出。

标准输出换向

  1. ofstream fout("b.out");
  2. streambuf* pOld =cout.rdbuf(fout.rdbuf()); //保存标准输出并转向
  3. //…
  4. cout.rdbuf(pOld); //恢复标准输出

构造输出流对象

  • ofstream类支持磁盘文件输出
  • 如果在构造函数中指定一个文件名,当构造这个文件时该文件是自动打开的ofstream myFile("filename");

  • 可以在调用默认构造函数之后使用open成员函数打开文件ofstream myFile; //声明一个静态文件输出流对象 myFile.open("filename"); //打开文件,使流对象与文件建立联系

  • 在构造对象或用open打开文件时可以指定模式ofstream myFile("filename", ios_base::out | ios_base::binary);

文件打开模式标记:

模式标记 适用对象 作用
ios::in ifstream fstream 打开文件用于读取数据。如果文件不存在,则打开出错。
ios::out ofstream fstream 打开文件用于写入数据。如果文件不存在,则新建该文件;如 果文件原来就存在,则打开时清除原来的内容。
ios::app ofstream fstream 打开文件,用于在其尾部添加数据。如果文件不存在,则新建该文件。
ios::ate ifstream 打开一个已有的文件,并将文件读指针指向文件末尾(读写指 的概念后面解释)。如果文件不存在,则打开出错。
ios:: trunc ofstream 单独使用时与 ios:: out 相同。
ios::binary ifstream ofstream fstream 以二进制方式打开文件。若不指定此模式,则以文本模式打开。
ios::in | ios::out fstream 打开已存在的文件,既可读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
ios::in | ios::out ofstream 打开已存在的文件,可以向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
ios::in | ios::out | ios::trunc fstream 打开文件,既可读取其内容,也可向其写入数据。如果文件本来就存在,则打开时清除原来的内容;如果文件不存在,则新建该文件。

文件输出流成员函数的三种类型

  • 与操纵符等价的成员函数。
  • 执行非格式化写操作的成员函数。
  • 其它修改流状态且不同于操纵符或插入运算符的成员函数。

文件输出流成员函数

open

把流与一个特定的磁盘文件关联起来。需要指定打开模式。

put

把一个字符写到输出流中。

write

把内存中的一块内容写到一个文件输出流中

seekp和tellp

操作文件流的内部指针

close

关闭与一个文件输出流关联的磁盘文件

错误处理

在写到一个流时进行错误处理

2.2向文本文件输出

标准输出设备显示器被系统看作文本文件,所以我们以向标准设备输出为例,介绍文本文件输出格式控制

  • 插入(<<)运算符
    • 为所有标准C++数据类型预先设计的,用于传送字节到一个输出流对象。

操纵算子(manipulator)

  • 插入运算符与操纵符一起工作
    • 控制输出格式。
  • 很多操纵符都定义在
    • ios_base类中(如hex())、头文件(如setprecision())。
  • 控制输出宽度
    • 在流中放入setw操纵符或调用width成员函数为每个项指定输出宽度。
  • setw和width仅影响紧随其后的输出项,但其它流格式操纵符保持有效直到发生改变。
  • dec、oct和hex操纵符设置输入和输出的默认进制。

例11-1 使用width控制输出宽度

  1. #include <iostream>
  2. using namespace std;
  3. int main() {
  4. double values[] = { 1.23, 35.36, 653.7, 4358.24 };
  5. for(int i = 0; i < 4; i++) {
  6. cout.width(10);
  7. cout << values[i] << endl;
  8. }
  9. return 0;
  10. }
  11. 输出结果:
  12. 1.23
  13. 35.36
  14. 653.7
  15. 4358.24

例11-2使用setw操纵符指定宽度

  1. //11_2.cpp
  2. #include <iostream>
  3. #include <iomanip>
  4. #include <string>
  5. using namespace std;
  6. int main() {
  7. double values[] = { 1.23, 35.36, 653.7, 4358.24 };
  8. string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
  9. for (int i = 0; i < 4; i++)
  10. cout << setw(6) << names[i]
  11. << setw(10) << values[i] << endl;
  12. return 0;
  13. }
  14. 输出结果:
  15. Zoot 1.23
  16. Jimmy 35.36
  17. Al 653.7
  18. Stan 4358.24

例11-3设置对齐方式

  1. //11_3.cpp
  2. #include <iostream>
  3. #include <iomanip>
  4. #include <string>
  5. using namespace std;
  6. int main() {
  7. double values[] = { 1.23, 35.36, 653.7, 4358.24 };
  8. string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
  9. for (int i=0;i<4;i++)
  10. cout << setiosflags(ios_base::left)//左对齐
  11. << setw(6) << names[i]
  12. << resetiosflags(ios_base::left)
  13. << setw(10) << values[i] << endl;
  14. return 0;
  15. }
  16. 输出结果:
  17. Zoot 1.23
  18. Jimmy 35.36
  19. Al 653.7
  20. Stan 4358.24

setiosflags操纵符

  • 这个程序中,通过使用带参数的setiosflags操纵符来设置左对齐,setiosflags定义在头文件iomanip中。
  • 参数ios_base::left是ios_base的静态常量,因此引用时必须包括ios_base::前缀。
  • 这里需要用resetiosflags操纵符关闭左对齐标志。setiosflags不同于width和setw,它的影响是持久的,直到用resetiosflags重新恢复默认值时为止 。
  • setiosflags的参数是该流的格式标志值,可用按位或(|)运算符进行组合

setiosflags的参数(流的格式标识)

  • ios_base::skipws 在输入中跳过空白 。
  • ios_base::left 左对齐值,用填充字符填充右边。
  • ios_base::right 右对齐值,用填充字符填充左边(默认对齐方式)。
  • ios_base::internal 在规定的宽度内,指定前缀符号之后,数值之前,插入指定的填充字符。
  • ios_base::dec 以十进制形式格式化数值(默认进制)。
  • ios_base::oct 以八进制形式格式化数值 。
  • ios_base::hex 以十六进制形式格式化数值。
  • ios_base::showbase 插入前缀符号以表明整数的数制。
  • ios_base::showpoint 对浮点数值显示小数点和尾部的0 。
  • ios_base::uppercase 对于十六进制数值显示大写字母A到F,对于科学格式显示大写字母E 。
  • ios_base::showpos 对于非负数显示正号(“+”)。
  • ios_base::scientific 以科学格式显示浮点数值。
  • ios_base::fixed 以定点格式显示浮点数值(没有指数部分) 。
  • ios_base::unitbuf 在每次插入之后转储并清除缓冲区内容。

精度

  • 浮点数输出精度的默认值是6,例如:3466.98。
  • 要改变精度:setprecision操纵符(定义在头文件iomanip中)。
    • 如果不指定fixed或scientific,精度值表示有效数字位数。
    • 如果设置了ios_base::fixed或ios_base::scientific精度值表示小数点之后的位数。

例11-4控制输出精度——未指定fixed或scientific

  1. //11_4_1.cpp
  2. #include <iostream>
  3. #include <iomanip>
  4. #include <string>
  5. using namespace std;
  6. int main() {
  7. double values[] = { 1.23, 35.36, 653.7, 4358.24 };
  8. string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
  9. for (int i=0;i<4;i++)
  10. cout << setiosflags(ios_base::left)
  11. << setw(6) << names[i]
  12. << resetiosflags(ios_base::left)//清除左对齐设置
  13. << setw(10) << setprecision(1) << values[i] << endl;
  14. return 0;
  15. }
  16. 输出结果:
  17. Zoot 1
  18. Jimmy 4e+001
  19. Al 7e+002
  20. Stan 4e+003

例11-4控制输出精度——指定fixed

  1. //11_4_2.cpp
  2. #include <iostream>
  3. #include <iomanip>
  4. #include <string>
  5. using namespace std;
  6. int main() {
  7. double values[] = { 1.23, 35.36, 653.7, 4358.24 };
  8. string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
  9. cout << setiosflags(ios_base::fixed);
  10. for (int i=0;i<4;i++)
  11. cout << setiosflags(ios_base::left)
  12. << setw(6) << names[i]
  13. << resetiosflags(ios_base::left)//清除左对齐设置
  14. << setw(10) << setprecision(1) << values[i] << endl;
  15. return 0;
  16. }
  17. 输出结果:
  18. Zoot 1.2
  19. Jimmy 35.4
  20. Al 653.7
  21. Stan 4358.2

2.3向二进制文件输出

二进制是一种读写效率比较高的方式,不用进行额外的转化,通常用于存储不用被人看到的数据。比如说结构体数据。

  • 使用ofstream构造函数中的模式参量指定二进制输出模式;
  • 以通常方式构造一个流,然后使用setmode成员函数,在文件打开后改变模式;
  • 通过二进制文件输出流对象完成输出。

例11-5 向二进制文件输出

  1. //11_5.cpp
  2. #include <fstream>
  3. using namespace std;
  4. struct Date {
  5. int mon, day, year;
  6. };
  7. int main() {
  8. Date dt = { 6, 10, 92 };
  9. ofstream file("date.dat", ios_base::binary);
  10. file.write(reinterpret_cast<char *>(&dt),sizeof(dt));
  11. file.close();
  12. return 0;
  13. }

2.4向字符串输出

将字符串作为输出流的目标,可以实现将其他数据类型转换为字符串的功能

字符串输出流( ostringstream )

  • 用于构造字符串
  • 功能
    • 支持ofstream类的除open、close外的所有操作
    • str函数可以返回当前已构造的字符串
  • 典型应用
    • 将数值转换为字符串

例11-6用ostringstream将数值转换为字符串

  1. //11_6.cpp
  2. #include <iostream>
  3. #include <sstream>
  4. #include <string>
  5. using namespace std;
  6. //函数模板toString可以将各种支持“<<“插入符的类型的对象转换为字符串。
  7. template <class T>
  8. inline string toString(const T &v) {
  9. ostringstream os; //创建字符串输出流
  10. os << v; //将变量v的值写入字符串流
  11. return os.str(); //返回输出流生成的字符串
  12. }
  13. int main() {
  14. string str1 = toString(5);
  15. cout << str1 << endl;
  16. string str2 = toString(1.2);
  17. cout << str2 << endl;
  18. return 0;
  19. }
  20. 输出结果:
  21. 5
  22. 1.2

三、输入流

重要的输入流类

  • istream类最适合用于顺序文本模式输入。cin是其实例。
  • ifstream类支持磁盘文件输入。
  • istringstream

构造输入流对象

  • 如果在构造函数中指定一个文件名,在构造该对象时该文件便自动打开。 ifstream myFile(“filename”);
  • 在调用默认构造函数之后使用open函数来打开文件。 ifstream myFile; //建立一个文件流对象 myFile.open(“filename”); //打开文件”filename”
  • 打开文件时可以指定模式 ifstream myFile(“filename”, ios_base::in | ios_base::binary);

3.1使用提取运算符从文本文件输入

  • 提取运算符(>>)对于所有标准C++数据类型都是预先设计好的。
  • 是从一个输入流对象获取字节最容易的方法。
  • ios类中的很多操纵符都可以应用于输入流。但是只有少数几个对输入流对象具有实际影响,其中最重要的是进制操纵符dec、oct和hex。

输入流相关函数

  • open 把该流与一个特定磁盘文件相关联。
  • get 功能与提取运算符(>>)很相像,主要的不同点是get函数在读入数据时包括空白字符。 对应的是put
  • getline 功能是从输入流中读取多个字符,并且允许指定输入终止字符,读取完成后,从读取的内容中删除终止字符。
  • read 从一个文件读字节到一个指定的内存区域,由长度参数确定要读的字节数。当遇到文件结束或者在文本模式文件中遇到文件结束标记字符时结束读取。
  • seekg 用来设置文件输入流中读取数据位置的指针。
  • tellg 返回当前文件读指针的位置。
  • close 关闭与一个文件输入流关联的磁盘文件。

3.2 输入流应用举例

例11-7 get函数应用举例

  1. //11_7.cpp
  2. #include <iostream>
  3. using namespace std;
  4. int main() {
  5. char ch;
  6. while ((ch = cin.get()) != EOF)
  7. cout.put(ch);
  8. return 0;
  9. }

例11-8为输入流指定一个终止字符:

  1. //11_8.cpp
  2. #include <iostream>
  3. #include <string>
  4. using namespace std;
  5. int main() {
  6. string line;
  7. cout << "Type a line terminated by 't' " << endl;
  8. getline(cin, line, 't');
  9. cout << line << endl;
  10. return 0;
  11. }

例11-9 从文件读一个二进制记录到一个结构中

  1. //11_9.cpp
  2. #include <iostream>
  3. #include <fstream>
  4. #include <cstring>
  5. using namespace std;
  6. struct SalaryInfo {
  7. unsigned id;
  8. double salary;
  9. };
  10. int main() {
  11. SalaryInfo employee1 = { 600001, 8000 };
  12. ofstream os("payroll", ios_base::out | ios_base::binary);
  13. os.write(reinterpret_cast<char *>(&employee1), sizeof(employee1));
  14. os.close();
  15. ifstream is("payroll", ios_base::in | ios_base::binary);
  16. if (is) {
  17. SalaryInfo employee2;
  18. is.read(reinterpret_cast<char *>(&employee2), sizeof(employee2));
  19. cout << employee2.id << " " << employee2.salary << endl;
  20. } else {
  21. cout << "ERROR: Cannot open file 'payroll'." << endl;
  22. }
  23. is.close();
  24. return 0;
  25. }

例11-10用seekg函数设置位置指针

  1. //11_10.cpp, 头部分省略
  2. int main() {
  3. int values[] = { 3, 7, 0, 5, 4 };
  4. ofstream os("integers", ios_base::out | ios_base::binary);
  5. os.write(reinterpret_cast<char *>(values), sizeof(values));
  6. os.close();
  7. ifstream is("integers", ios_base::in | ios_base::binary);
  8. if (is) {
  9. is.seekg(3 * sizeof(int));
  10. int v;
  11. is.read(reinterpret_cast<char *>(&v), sizeof(int));
  12. cout << "The 4th integer in the file 'integers' is " << v << endl;
  13. } else {
  14. cout << "ERROR: Cannot open file 'integers'." << endl;
  15. }
  16. return 0;
  17. }

例11-11 读一个文件并显示出其中0元素的位置

  1. //11_11.cpp, 头部分省略
  2. int main() {
  3. ifstream file("integers", ios_base::in | ios_base::binary);
  4. if (file) {
  5. while (file) {//读到文件尾file为0
  6. streampos here = file.tellg();
  7. int v;
  8. file.read(reinterpret_cast<char *>(&v), sizeof(int));
  9. if (file && v == 0)
  10. cout << "Position " << here << " is 0" << endl;
  11. }
  12. } else {
  13. cout << "ERROR: Cannot open file 'integers'." << endl;
  14. }
  15. file.close();
  16. return 0;
  17. }

将字符串作为文本输入流的源,可以将字符串转换为其他数据类型

3.3 字符串输入流( istringstream)

  • 用于从字符串读取数据
  • 在构造函数中设置要读取的字符串
  • 功能
    • 支持ifstream类的除open、close外的所有操作
  • 典型应用
    • 将字符串转换为数值

例11-12用istringstream将字符串转换为数值

  1. //11_12.cpp, 头部分省略
  2. template <class T>
  3. inline T fromString(const string &str) {
  4. istringstream is(str); //创建字符串输入流
  5. T v;
  6. is >> v; //从字符串输入流中读取变量v
  7. return v; //返回变量v
  8. }
  9. int main() {
  10. int v1 = fromString<int>("5");
  11. cout << v1 << endl;
  12. double v2 = fromString<double>("1.2");
  13. cout << v2 << endl;
  14. return 0;
  15. }
  16. 输出结果:
  17. 5
  18. 1.2

四、输入/输出流

两个重要的输入/输出流

  • 一个iostream对象可以是数据的源或目的。
  • 两个重要的I/O流类都是从iostream派生的,它们是fstream和stringstream。这些类继承了前面描述的istream和ostream类的功能。

fstream类

  • fstream类支持磁盘文件输入和输出。
  • 如果需要在同一个程序中从一个特定磁盘文件读并写到该磁盘文件,可以构造一个fstream对象。
  • 一个fstream对象是有两个逻辑子流的单个流,两个子流一个用于输入,另一个用于输出。

stringstream类

  • stringstream类支持面向字符串的输入和输出
  • 可以用于对同一个字符串的内容交替读写,同样是由两个逻辑子流构成。

参考