1 / 149

第八章 C++ 的 I/O 流库

第八章 C++ 的 I/O 流库. C++ 为什麽要建立自己的输入输出系统? 1 C 虽然具有一个灵活和功能强大的输入输出系统, 但它并 不支持自定义类型 。例如: class account { char name[30]; double balance; public: account (); account (char*, double); … };. … account acnt; scanf (“% account ”, &acnt); // 错误,不支持 account 类型

moral
Download Presentation

第八章 C++ 的 I/O 流库

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第八章 C++ 的 I/O 流库

  2. C++为什麽要建立自己的输入输出系统? 1 C虽然具有一个灵活和功能强大的输入输出系统, 但它并不支持自定义类型。例如: class account { char name[30]; double balance; public: account(); account(char*, double); … };

  3. account acnt; scanf(“%account”, &acnt); // 错误,不支持 account 类型 account acnt1(John, 3000.0); printf(“%account”, acnt1); // 错误,不支持 account 类型 … 因为输入函数 scanf 和输出函数 printf的格式串形参 只能与系统预定义类型匹配,而无法识别用户的自 定义类型,并且也不能通过重载定义 scanf 和 printf 函数的新版本,使它们的格式串形参能匹配任意用 户自定义类型。

  4. 2 面向对象程序设计必须定义众多的用户自定义类, 如何以面向对象的设计原则和方法为自定义类设计 既规格统一,又适应个性化的输入输出操作行为是 十分必要的。因此 C++必须建立一个能通过对输入 输出操作重载的方法实现对任意自定义类型对象输 入输出支持的系统。

  5. 本章要点 1 C++流库结构 流库的概念、流库的组成。 2 标准输入输出流 输入输出流类的定义、输入输出运算符、输入输出 的格式控制。 3 自定义类的输入输出 输入输出运算符的重载。 4 文件的输入输出流 文件的打开、关闭和读写。 5 使用 MFC的对话框类实现输入输出

  6. 8.1 C++流库结构 8.1.1 流库的概念 流(stream)是从源(数据的生产者)到目标(数 据的使用者)被传输数据的引用。每个流都是一个与 某种数据传输设备相关联的对象。 流具有方向性: 输入流是与输入设备(如键盘)关联的流。 输出流是与输出设备(如显示器屏幕)关联的流。 输入输出流是与输入输出设备(如磁盘)关联的流。

  7. C++中包含的预定义流: cin输入流,与输入设备关联。 cout输出流,与输出设备关联。 cerr非缓冲型错误信息流,与错误输出设备关联; clog缓冲型错误信息流,与错误输出设备关联。 在缺省情况下,指定的输入设备是控制台键盘,输 出设备是控制台显示器终端。在任何情况下,指定的 错误输出设备总是控制台显示器终端。

  8. cin和 cout的使用方法我们已经很熟悉了。cerr和 clog均是用来输出错误信息,它们的使用方法与 cout 基本相同,只不过它们所关联的设备始终是控制台显 示器,而不随着 cout 关联设备的改变而变化。cerr和 clog之间的区别是: cerr对输出的错误信息不缓冲,因而发送给它的任何内 容都立即输出。 clog输出的错误信息被缓冲,当缓冲区满时才进行输 出,也可以通过刷新流的方式(遇到操纵符 endl 或 flush)强迫刷新缓冲区导致显示输出。

  9. 下面给出一段使用预定义输入输出流信息的程序:下面给出一段使用预定义输入输出流信息的程序: cout << "What was the total dollar amount of last month’s sales?"; cin >> sales; cout << "How many units did you sell?"; cin >> num; if (num == 0) { cerr << "The average can not be computed.\n"; } else { avgsales = sales / num; cout << "The average selling price per nuit was "; cout << avgsales << "\n"; }

  10. C++流库是用面向对象的设计方法建立起来的输入 输出类库,它具有两个平行的根基类streambuf和 ios, 库中所有其他的类均从它们直接或间接派生。系统中 预定义流,cin、cout、cerr、clog 都是流库中相应类的 对象。

  11. 8.1.2 streambuf类 ·streambuf类是流库的根基类,它为输入输出物理设 备提供缓冲区和流处理的一些通用方法。 ·C++将输入输出流均视为字节流,因此缓冲区是由 一个字符串和两个指针组成的。这两个指针分别指 向数据流在输入缓冲区中的插入位置和在输出缓冲 区的提取位置。 streambuf类提供对缓冲区的底层操作,例如设置缓 冲区、对缓冲区指针进行操作、从缓冲区取字节、向 缓冲区存储字节等。streambuf类有三个派生类,filebuf 类、strstreambuf类和 conbuf类。它们的派生层次关系 如图所示:

  12. filebuf strstreambuf conbuf ⑴filebuf类扩展了 streambuf类的功能,用于文件流与 文件缓冲区相关联,实现对文件缓冲区中的字节序 列的读写操作: ① 写文件:缓冲区内容按字节写到指定的文件中, 然后刷新缓冲区。 ② 读文件:指定文件内容按字节读到缓冲区中。 ③ 打开文件:filebuf与被读或写的文件相关联。 streambuf

  13. 关闭文件:filebuf与被读或写的文件解除关联。 ⑵strstreambuf类扩展了 streambuf类的功能,提供了将 内存作为输入输出设备时,进行提取和插入操作的 缓冲区管理。 ⑶conbuf类扩展了 streambuf类的功能,用于控制台显 示器输出缓冲区关联和管理,提供光标控制、颜色 设置、活动窗口定义、清屏、行清除等屏幕控制功 能(注意,这些屏幕控制功能在有些编译器版本中 不提供,例如 visual C++)。 通常情况下,对设备缓冲区的操作一般使用上述三 个派生类,很少直接使用基类 streambuf。

  14. 8.1.3 ios类 ios类及其派生类为用户提供使用流的接口,该类具 有一个streambuf类型指针指向流的缓冲区。ios类及其 派生类对象通过该指针使用 streambuf及其派生类对象 完成输入输出时的格式化或非格式化转换,并检查输 入输出的错误。ios 是流库中的另一个根基类,它有四 个直接派生类: ⑴输入流类 istream: class istream : virtual public ios; ⑵输出流类 ostream: class ostream : virtual public ios;

  15. ⑶文件流类 fstreambase: class fstreambase : virtual public ios; ⑷ 字符串流类 strstreambase: class strstreambase : virtual public ios; 这四个派生类成为流类库中的基本流类,它们又组 合派生出以下的流类: ⑴输入文件流类 ifstream: class ifstream : public istream, public fstreambase; ⑵输入字符串流类 istrstream: class istrstream : public istream, public strstreambase; ⑶输出文件流类 ofstream: class ofstream : public ostream, public fstreambase;

  16. ⑷ 输出字符串流类 ostrstream: class ostrstream : public ostream, public strstreambase; ⑸控制台输出流类 constream: class constream : public ostream; ⑹输入输出流类 iostream: class iostream : public istream, public ostream; ⑺输入输出文件流类 fstream: class fstream : public iostream, public fstreambase; ⑻输入输出字符串流类 strstream: class strstream : public iostream, public strstreambase;

  17. 从上述的 istream、ostream 和 iostream 又分别派生出 具有赋值运算符“=”重载的新类: ⑴带赋值的输入流类 istream_withassign: class istream_withassign : public istream; ⑵带赋值的输出流类 ostream_withassign: class ostream_withassign : public ostream; ⑶带赋值的输入输出流类 iostream_withassign: class iostream_withassign : public iostream; 上述流类的派生层次结构如下图所示。

  18. istream fstreambase strsteambase ostream ifstream istrstream ofstream ostrstream constream istream_withassign fstream ostream_withassign strstream iostream iostream_withassign ios

  19. 系统预定义流 cin、cout、cerr和 clog在系统头文件 iostream.h中被定义: extern _CRTIMP istream_withassigncin; extern ostream_withassign _CRTIMP cout; extern ostream_withassign _CRTIMP cerr; extern ostream_withassign _CRTIMP clog; 显然,用户也可以用 istream和 ostream等流类定义 自己的流对象,例如:istreamis; ostreamos; 对预定义类型数据的输入输出操作已经定义为流类 的操作,而对用户自定义类型对象的输入输出操作则 可以通过重载运算符 “>>”和“<<” 得以实现。

  20. Java的 I/O流类在构造和使用上与 C++有一些有趣 的差别: 1 C++的 I/O流类库是把所有的输入输出功能封装在 数量相对较少的几个流类中,自定义类则是通过重 载机制使得流类对象能够对自定义类对象进行输入 输出操作。 而 Java则几乎为每一种情况提供了一个独立的类。 例如,底层的字节流操作类、按数据类型划分的各 种高级操作类,对底层 I/O包装后进行某种特定操 作(如 Unicode读写)的类,随机读写操作类,数 组读写类等等。

  21. 2 C++的流在缺省情况下是进行缓冲的,但 Java的流 在缺省情况下是不缓冲的。但可以调用提供了缓冲 功能的类对一个Java的流进行缓冲。 3 在 Java中,所有的整数和浮点数都是以大端字节顺 序进行输入输出的,与底层平台无关。这与 C++中 的情况不同,因此 Java所产生的数据文件的可移植 性更高。

  22. 4 在典型的 C++程序中,字符均使用 ASCII编码形式 表示(无论是单独使用的字符还是字符串中的字 符)。这与字符从输入设备读入或写到输出设备都 保持 ASCII编码形式是一致的。 一个典型的 Java程序中,字符始终是用两个字节的 Unicode形式表示的。当从输入设备读入 ASCII编码 形式的字符时,必须先转换为 Unicode形式后再写 入内存;当将 Unicode形式字符写到输出设备时, 必须先转换为 ASCII编码形式后再写入输出设备。 Java的流类的层次结构如下图所示:

  23. ByteArrayOutputStream ByteArrayInputStream FileOutputStream FileInputStream PipedOutputStream PipedInputStream ObjectOutputStream ObjectInputStream Implements ObjectOutput Implements ObjectInput FilterOutputStream SequenceInputStream FilterInputStream PrintStream BufferedOutputStream PushbackInputStream DataOutputStream BufferedInputStream Implements DataOutput DataInputStream Implements DataInput RandomAccessFile LineNumberInputStream Implements DataInput, DataOutput OutputStream InputStream

  24. BufferedWriter BufferedReader CharArrayWriter PushbackReader PipedWriter CharArrayReader FilterWriter PipedReader OutputStreamWriter FilterReader PrintWriter FileWriter StringWriter PushbackReader InputStreamReader StringReader FileReader 返回 Writer Reader

  25. 8.2 标准输入输出流 8.2.1 输入输出流类定义 istream类和 ostream类在流库中分别提供基本的标 准输入输出操作,是使用流库的主要接口,在系统头 文件 iostream.h中,它们的类定义分别如下: class istream:virtual public ios { public: istream(streambuf*); //构造函数 // 从输入流中将字符读取到给定指针 char* 指向的内存,直 // 至遇到分界符、文件结束符或读至(len-1)个字符为止。 istream& get(signed char*, int, char = ‘\n’); istream& get(unsigned char*, int, char = ‘\n’);

  26. // 从输入流读取字符到给定的 streambuf,直至遇到分界符。istream& get(streambuf&, char = ‘\n’); // 从输入流读取单个字符到指定的引用 char 变量中。 istream& get(unsigned char&); istream& get(signed char&); int get(); // 从输入流读取下一个字符或 EOF。 // 连同分界符一起从流中读取字符到 char* 指向的内存中。 istream& getline(signed char*, int, char = ‘\n’); istream& getline(unsigned char*, int, char = ‘\n’);

  27. // 从读入流读取给定数目的字符到 char* 指向的内存空间。 // 如果发生错误时,getcount 函数可得到实际读入的数目。 istream& read(signed char* int); istream& read(unsigned char* int); int peek(); // 只读不取下一个字符 int getcount(); // 返回已读入的字符数目。 istream& putback(char); // 将字符回放到输入流中。 // 在输入流中越过 n 个字符,若遇到 EOF 时停止。 istream& ignore(int = 1, int = EOF); // 在输入流中移动读文件指针到给定的位置 streampos // ( typedef long streampos ) istream& seekg(streampos);

  28. // 按给定偏移 streamoff ( typedef long streamoff)移动输入 // 文件指针,指示偏移方向的枚举值 ios::seek_dir 的取值: // beg = 从文件开始;cur = 从当前位置;end = 从文件尾。 istream& seekg(streamoff, ios::seek_dir); streampos tellg(); // 返回当前文件指针位置。 // 同步输入流的内部缓冲区和外部字符源,该函数是可以在 // 派生类中重新定义的虚函数。 int sync(); // 可重载的输入运算符。 istream& operator >>(signed char*); istream& operator >>(signed char&); istream& operator >>(int&); … //对系统所有的预定义类型都给出了>>的重载定义。 };

  29. class ostream:virtual public ios { public: ostream(streambuf*); // 构造函数 ostream& flush(); // 输出流刷新。 ostream& seekp(streampos); //写文件指针移到指定位置。 ostream& seekp(streamoff, ios:seek_dir); // 按指定方向和偏移量移动文件指针到新位置。 ostream& put(char); // 将指定字符输出到流中。 // 将由char*指向的内存空间的n个字符输出到流中。 ostream& write(const signed char*, int); ostream& write(const unsigned char*, int);

  30. streampos tellp(); // 返回输出流中当前指针位置。 // 可重载的输出运算符 ostream& operator <<(short); ostream& operator <<(int); ostream& operator <<(float); … // 对系统所有的预定义类型都给出了 << 的重载定义。 };

  31. 8.2.2 输入输出运算符的使用 8.2.3 格式控制的输入输出 输入输出的格式控制可以使得人机交互界面更加友 好、美观。在 C 程序中,输入输出是使用 C运行库的 scanf和 printf 函数完成的,输入输出的格式是通过这两 个函数的格式描述串参数控制的。在 C++程序中,虽 仍然可以使用 scanf和 printf 函数,但在面向对象的程 序设计中,输入输出是使用流类库中的输入输出流完 成的,因此输入输出的格式控制必须使用流类库提供 的格式控制的方法: ⑴使用 ios类的格式控制成员函数; ⑵使用被称为格式操纵符的特殊函数。

  32. 1 用ios 类成员函数进行输入输出格式化 ios 类提供了用于输入输出格式控制的成员函数。 这些函数进行格式控制的方法是修改以下属性: ·格式标志属性x_flags:标志的不同状态值指定输 入输出数据的不同格式(如对齐规则、数值转换 基、数字表示规则等)。 ·输出域宽属性x_width: 指定输出数据所占显示 区域的宽度。 ·填充字符属性x_fill:指定输出显示域中数据为占 空间的填充字符; ·输出精度属性x_precision:指定浮点数输出的小 数部分显示位数。

  33. ⑴格式标志 C++中每个流对象的输入输出格式都是依据指定 格式进行的,也就是说,流对象的每次输入“<<” 或输出“>>” 操作是按照当前格式标志 x_flags 中 的格式状态完成的。该属性是一个 protected成 员,在类外访问该属性是通过公有的格式控制成 员函数实现的。注意,该属性可以在 ios的派生 类内被访问。为了便于提供格式控制成员函数的 参数和参数具有良好的可读性,ios类定义了一 个公有的无名枚举数据成员,用户可以使用这些 特定的枚举元素,形成所需要的格式状态传递给 相应的格式控制成员函数。

  34. class _CRTIMP ios { public: … enum { skipws = 0x0001, // 跳过输入中的空白,用于输入 left = 0x0002, // 左对齐输出,用于输出 right = 0x0004, // 右对齐输出,用于输出 internal = 0x0008, // 符号或基数指示符与数字之间添加填充符,用于输出 dec = 0x0010, // 基数为 10进制,用于输入输出 oct = 0x0020, // 基数为 8进制,用于输入输出 hex = 0x0040, // 基数为 16进制,用于输入输出

  35. showbase = 0x0080, // 显示基数指示符,用于输出 showpoint = 0x0100, // 显示小数点,用于输出 uppercase = 0x0200, // 16进制输出时,数基指示符和 // 数值中的字母一律为大写,用于输出 showpos = 0x0400, // 正数前显示"+"符号,用于输出 scientific = 0x0800, // 科学表示法浮点数,用于输出 fixed = 0x1000, // 定点形式显示浮点数,用于输出 unitbuf = 0x2000, //输出后立即刷新流,用于输出。 stdio = 0x4000, // 刷新 stdout和 stderr,用于输出。 }; … inline long flags() const; // 返回当前格式标志。

  36. inline long flags(long _l); // 设置指定格式 _l,返回原有格式。 inline long setf(long _f, long _m); // 依据掩码 _m 设置指定格式 _f,返回原有格式。inline long unsetf(long _l); // 清除指定格式 _l,并返回原有标志。 inline int width() const; // 返回当前域宽值。 inline int width(int _i); // 设置指定域宽 _i,并返回原有域宽值。 inline ostream* tie(ostream* _os); // 将流连接到_os 指向输出流,并返回原来的流指针。 inline ostream* tie() const; // 返回原来的流指针。

  37. inline fill() const; // 返回当前填充字符。 inline char fill(char _c); // 设置指定填充字符,并返回原有填充字符。 inline int precision(int _i); // 设置指定浮点数精度,并返回原有浮点数精度。 inline int precision() const; // 返回当前的浮点数精度。 inline int rdstate() const; // 返回当前的错误状态。 inline void clear(int _i = 0); // 根据掩码 _i 设置或清除错误状态位。 protected: …

  38. long x_flags; int x_precision; char x_fill; int x_width; … };

  39. 格式枚举元素值有一个共同的特点,即使用不同格式枚举元素值有一个共同的特点,即使用不同 位为 1 二进制数表示不同的格式值,也就是说, 枚举元素值的二进制表示只有一位为 1。例如: skipws 0x0001: 0000 0000 0000 0001 left 0x0002: 0000 0000 0000 0010 right 0x0004: 0000 0000 0000 0100 显然,所需要的特定格式标志将可以是一个枚举 元素值或几个的枚举元素进行或运算组合而成, 例如,欲设置左对齐 10进制科学表示法显示浮 点数的输出格式,则格式标志可以通过 ios::left | ios::dec | ios::scientific 得到,16进制值为 0x0812,10进制值为 2066。

  40. ⑵用成员函数对格式标志进行操作 ① 置格式标志 所谓设置格式标志是将格式属性x_flags 的某 一位置1,使该位所对应的格式标志有效。设 置格式标志的成员函数是 setf,调用该成员函 数的一般形式为: 流对象.setf (ios::格式标志值); 例如: istream isobj; ostream osobj; isobj.setf(ios::skipws); osobj.setf(ios::left);

  41. 注意: •isobj和 osobj 为 istream和 ostream 的用户定 义对象。在编程中使用最多的是通过系统预 定义流对象设置格式,例如: cin.setf(ios::skipws); cout.setf(ios::left); •所设置的格式标志不改变格式属性x_flags 的 原有的有效位,即在原有基础上追加设置。 例如,原来的状态标志字为: 0x0011: 0000 0000 0001 0001 执行 cout.setf(ios::left) 后, x_flags 的值变为: 0x0013: 0000 0000 0001 0011

  42. 例如: #include <iostream.h> main() { cout.setf(ios::showpos | ios::scientific); cout << 567 << " " << 567.89 << endl; } 程序执行结果: +567 +5.6789e02

  43. ② 清除格式标志 与设置格式标志操作相反,是将格式属性 x_flags 的某一位清 0,使该位所对应的格式特 性失效。清除格式的成员函数是 unsetf,调用 该成员函数的一般形式为: 流对象.unsetf(ios::格式标志值); 注意:与设置格式标志相似,所清除的格式标 志只是使保存在 x_flags中的当前格式属性的 相应位失效,而不改变格式属性其余的有效 位。例如,原格式属性为:

  44. 0x0013: 0000 0000 0001 0011 执行 cout.unsetf(ios::left)后,x_flags 的值变为: 0x0011: 0000 0000 0001 0001

  45. ③ 取状态属性 取出保存在 x_flags中的格式属性。完成这一 操作的成员函数 flags,该函数有两个重载版 本,调用它们的格式有两种: long flags(); long flags(long flags); 前一种形式用于返回当前格式属性;后一种是 不仅将当前格式属性返回,并且将格式属性设 置为指定值 flags。 注意,带有参数的成员函数 flags与成员函数 setf不同,它对格式属性的修改是覆盖原值, 而不是在原值的基础上追加设置。例如:

  46. #include <iostream.h> void showflags(long f) // 显示二进制形式的状态字 { long i; for (i = 0x8000; i; i = i >> 1) if (i & f) cout << "1"; else cout << "0"; cout << endl; }

  47. main() { long f = cout.flags(); showflags(f); cout.setf(ios::showpos | ios::scientific); f = cout.flags(); showflags(f); cout.unsetf(ios::scientific); f = cout.flags(); showflags(f); f = cout.flags(ios::oct); showflags(f); f = cout.flags(); showflags(f); return 1; }

  48. 程序执行结果: 0000000000000000 0000110000000000 0000010000000000 0000010000000000 0000000000100000 分析程序的执行结果,可以清楚地看到格式属 性值的变化情况。

  49. ④ 设置域宽 域宽主要用来控制一个数据输出时所占显示区 域的宽度,在 ios类中,域宽存放在保护类数 据成员 x_width中。设置域宽的成员函数有两 个,调用它们的一般形式为: 流对象.width(); 流对象.width(域宽值); 第一种形式只用来返回当前的域宽值,后者用 来设置指定域宽,并返回原来的域宽值。

  50. ⑤ 设置显示的精度 在ios类中,控制浮点数显示精度位数是被保 存在保护类数据成员 x_precision 中的。设置显 示精度的成员函数有两个,调用它们的一般形 式为: 流对象.precision(); 流对象.precision(精度位数); 第一种形式只用来返回当前的显示精度,后者 用来重新设置显示精度,并返回原来的显示精 度。

More Related