710 likes | 835 Views
第 9 章 I/O 流类库. 主要内容 基本概念 C++ 的基本流类结构 Istream 类和 ostream 类 格式控制 文件的读 / 写 可流类. 输出. 输入. 9.1 基本概念. 设备间的数据传送 内存 显示屏 内存 文件 键盘 内存 文件 内存 键盘 文件 流 面向对象技术中,任何设备都可以表示为相应类的对象,设备之间的数据传送即对象之间的数据传送。
E N D
第9章 I/O流类库 主要内容 • 基本概念 • C++的基本流类结构 • Istream类和ostream类 • 格式控制 • 文件的读/写 • 可流类
输出 输入 9.1 基本概念 • 设备间的数据传送 • 内存 显示屏 • 内存 文件 • 键盘 内存 • 文件 内存 • 键盘 文件 • 流 • 面向对象技术中,任何设备都可以表示为相应类的对象,设备之间的数据传送即对象之间的数据传送。 • 数据从源对象到目的对象的传送可以抽象看做一个“流”。 • 当键入字符时,就可认为字符从键盘流入程序的数据结构中;
当写入磁盘文件时,也可认为程序流到磁盘上。当写入磁盘文件时,也可认为程序流到磁盘上。 例如: cout<<“数据”; cin>>变量名; • 流类 • 把实现设备之间信息交换的类称作流类。 • 流库 • 若干流类的集合称做流类库。 • C++流类库是用继承方法建立起来的一个输入/输出类库,它具有两个平行的基类即streambuf类和ios类,所有其它的流类都是从它们直接或间接地派生出来的。
9.2 C++的基本流库结构 • C++为何建立自己的输入输出系统 • C语言的输入/输出库函数不支持用户自定义的数据类型(如结构、类) ,用户也不能通过重载这些库函数来实现用户自定义数据类型的输入/输出。例如: struct my_struct{ int i; float f; char *str; }s; 如果:printf(“%my_struct”, s); //错误 因为printf只能识别系统预定义的类型。
C++流库结构 • 每个流都是一种与设备相联系的既有状态,又有操作的对象,即流对象。对流对象进行抽象就得到流类,流类形成的层次结构就构成流类库(或流库)。 • C++流库主要由两个流类层次组成: • 以streambuf类为父类的类层次 • 主要完成信息通过缓冲区的交换。 • 以ios类为父类的类层次 • 在streambuf类实现功能的基础上,增加了各种格式化的输入/输出控制方法。
streambuf filebuf strstreambuf stdiobuf streambuf类的派生层次 • C++流类库示意图: • streambuf类层次结构 • 缓冲区:是一个队列数据结构,由一字符序列和两个指针组成,这两个指针分别指向字符要被插入或被取出的位置。 • streambuf类为所有的streambuf类层次对象设置了一个固定的内存缓冲区,动态划分为两部分: • 用做输入的取区,用取指针指示当前取字符位置。 • 用做输出的存区,用存指针指示当前存字符位置。
filebuf类 • 是在fstream.h中定义的streambuf类的一个派生类,它使用文件来保存缓冲区中的字符序列。 • 将filebuf同某个文件的描述字相联系就称打开这个文件。 • 当写文件时,是将缓冲区的字符写到指定的文件中,之后刷新缓冲区; • 当读文件时,是将指定文件中的内容读到缓冲区中来。
strstreambuf类 • 扩展了streambuf类的功能,增加了动态内存管理功能,提供了在内存中进行提取和插入操作的缓冲区管理。 • stdiobuf类 • 主要用作C++语言的流类层次方法和C语言的标准输入/输出方法混合使用时系统的缓冲区管理。 在通过情况下,均使用这三个派生类,很少直接使用streambuf 类。
ios类层次结构 • ios类及其派生类是在streambuf类实现的通过缓冲区的信息交换的基础上,进一步增加了各种格式化的输入/输出控制方法。它们为用户提供使用流类的接口,它们均有一个指向streambuf的指针。 • ios类有四个直接派生类: istream ostream fstreambase strstreambase 这四种流作为流库中的基本流类。
istrstream istream_withassign istream ifstream strstream ios iostream stdiostream fstream ostream ofstream ostream_withassign ostrstream • ios类层次: ios类的派生层次
从上图可看出各个类的继承关系,如: class ios; class istream : virtual public ios; class ostream : virtual public ios; class iostream : public istream, public ostream; 在istream类、ostream类和iostream类的基础上,分别重载赋值运算符“=”,就派生出istream_withassign、 ostream_withassign和ipstream_withassign类,即: class istream_withasssign : public istream; class ostream_withasssign : public ostream; class iostream_withasssign : public iostream;
ios虚基类 主要完成流的状态设置、状态报告、显示精度、域宽、填充字符的设置,文件流的操作模式定义等。 • istream类和ostream类 对运算符>>和<<进行重载。 • iostream类 以istream类和ostream类为基类,多重继承派生。可同时进行输入输出操作。
9.3 istream类和ostream类 • 在编写C++程序时,使用标准的输入/输出流cin和cout进行输入/输出,是因为开始执行C++程序时,C++会自动打开几个预定义流: • 标准输入流cin(缺省为键盘) • 标准输出流cout(显示终端) • 非缓冲型的标准出错流cerr(显示终端) • 缓冲型的标准出错流clog(显示终端) 它们在iostream.h中说明为_withassign类的对象: extrean iostream_withassign cin; extrean iostream_withassign cout; extrean iostream_withassign cerr; extrean iostream_withassign clog;
istream类 • 在流库中提供主要的输入操作,默认对象是cin; • istream类是在include目录下的文件istream.h中,istream类中定义的运算符和成员函数包括输入运算符>>、get函数、getline函数、read函数等。 • istream类的定义:
class istream : virtual public ios{ public: istream(streambuf *); istream & operator>>(char *); istream & operator>>(int &); inline istream & operator>>(unsigned char *); int get(); istream & get(signed char *, int, char = ‘\n’); istream & get(streambuf &, char = ‘\n’); istream & get(unsigned char &); istream & getline(signed char *, int, char = ‘\n’); int peek(); //表示不输入并返回下一个字符 int gcount(); //用来返回上次输入的字符数 istream & read(signed char *, int); … };
>>叫做输入运算符,将数据从左边的流中读出。>>叫做输入运算符,将数据从左边的流中读出。 int i; cin>>i; //cin.operator>>(i); • 重载的输入运算符“>>”均返回流类istream的引用,因此输入运算符可连用。例如: int x; char y; cin>>x>>y; • 在读入一个字符串时,空格将作为一个串的终止。如: char ch[20]; cin>>ch; 键盘输入 great wall ch中仅存放great.
缺省情况下,输入运算符在读取数据时,跳过空白字符,如: 12345 //读入时跳过前面的空格 • 不同类型的变量一起输入时,系统除检查是否有空白外,还检查输入数据与匹配情况。例如: int i; foat x; cin>> i >> x; 若输入: 56.79 32.85 实际结果:i = 56, x = 0.79
系统用数据类型分隔输入数据,如果读取的数据类型与输入运算符的参数类型不符,它将返回零值,并终止输入,如:系统用数据类型分隔输入数据,如果读取的数据类型与输入运算符的参数类型不符,它将返回零值,并终止输入,如: int v[3]; for(int i=0; i<3; i++) { if(cin>>v[i]) { cout<<"v["<<i<<"]= " <<v[i]<<endl; continue; } cout<<i<<endl; } 键盘输入:11gh55 程序运行结果: v[0]=11 1 2
例9.1 设计一个从键盘输入中提取若干个字符或字符串的例子。 #include<iostream.h> void main(void) { int length = 6; char a, b[6], c[6]; cin.get(a); cin.get(); //跳过输入流中1个字符 cin.getline(b, length); cin.get(); cin.getline(c, length); cout<<a<<“”<<b<<“”<<c<<endl; }
为什么可直接使用“cin>>”进行输入? • 当开始执行C++程序时,系统自动为其建立一个istream_withassign类的对象cin。 • 而istream_withassign是istream的派生类,它继承了基类的所有公有成员,在istream类中重载了几乎所有系统预定义类型的输入运算符“>>”。 • 对象cin可调用这些“>>”,因此用户可直接使用“cin>>”输入系统预定义类型的变量。
ostream类 • ostream类在流库中提供主要的输出操作,默认对象是cout。 • ostream类是在include目录下的文件ostream.h中,ostream类中定义的运算符和成员函数包括输出运算符<<、put函数、write函数等。 • ostream类定义:
class ostream : virtual public ios{ public: ostream(streambuf * ); ostream & operator<<(const char *); ostream & operator<<(int); inline ostream & put(char); ostream & write(const char *, int); //… };
<<叫做输出运算符,将运算符右边的数据存储到左边的流中。<<叫做输出运算符,将运算符右边的数据存储到左边的流中。 cout<<“string”; cout.operator<<(“string”); • 所有重载的输出运算符均返回ostream的引用,利用该引用可继续调用下一个输出运算符函数,因而在一条语句中可以显示多个数据,如: int x; cout<<“x=“<<x; 系统执行如下:输出运算符按自左至右的顺序 cout.operator<<(“x=“).operator<<(x)
输出运算符重载之后,没有改变其优先级,如果输出的数据含有表达式,应注意运算顺序,如:输出运算符重载之后,没有改变其优先级,如果输出的数据含有表达式,应注意运算顺序,如: cout<<x+y<<‘\n’;//正确,+的优先级高 cout<<x&y<<endl;//y是非法流,&的优先级低 结合顺序实际为: (cout<<x)&(y<<‘\n’) //error 应该用括号改变顺序: cout<<(x&y)<<‘\n’;
struct mytype{ int i; float f; char c; }; mytype mt; cin>>mt; cout<<mt; 对于用户自定义结构体类型或类类型的输入或输出,在C++中可以通过重载运算符“<<”和“>>”来实现。 例9.2 设计一个包含几种典型情况的屏幕输出的例子。 P197
重载输出运算符“<<” • 通过重载输出运算符“<<”来实现用户自定义类型的输出,定义格式为: ostream & operator<<(ostream &os, user_type &d) { os<<d.item1; os<<d.item2; os<<d.item3; //… return os; } 下面举一个具体的例子:
#include<iostream.h> class three_d{ int x, y, z; public: three_d(int x1, int y1, int z1) : x(x1), y(y1), z(z1){} //… friend ostream & operator<<(ostream &, three_d & obj); }; ostream & operator<<(ostream &output, three_d & obj) { output<<“x=“<<obj.x<<endl; output<<“y=“<<obj.y<<endl; output<<“z=“<<obj.z<<endl; return output; } void main(void) { three_d obj1(10, 20, 30); cout<<obj1; }
说明: • 重载的operator<<函数必须返回一个输出流类ostream对象的引用,这样,重载的输出运算符“<<”可连用。 • 输出运算符“<<”是双目运算符,使用时必须是输出流对象为第一操作数,输出数据为第二操作数,次序不能改变。 • 一般情况下,重载输出运算符函数不能是类的成员函数。(因为如果一个运算符函数是类的成员,则其左运算数就应当是调用运算符函数的类的对象。但重载运算符时,其左边参数是流,而右边的参数是类的对象。)
重载输入运算符“>>” • 通过重载输入运算符“>>”来实现用户自定义类型的输入,定义格式为: istream & operator>>(istream &is, user_type &d) { is>>d.item1; is>>d.item2; is>>d.item3; //… return is; } 下面举一个具体的例子:
#include<iostream.h> class three_d{ int x, y, z; public: three_d(int x1, int y1, int z1) : x(x1), y(y1), z(z1){ } void print(){cout<<x<<" "<<y<<" "<<z<<endl;} //… friend istream & operator>>(istream &, three_d & obj); }; istream & operator>>(istream &input, three_d & obj) { input>>obj.x; input>>obj.y; input>>obj.z; return input; } void main(void) { three_d obj1(10, 20, 30); obj1.print(); cin>>obj1; obj1.print(); }
说明: • 重载的operator>>函数必须返回一个输入流类istream对象的引用,这样,重载的输入运算符“>>”可连用。 • 与重载输出运算符函数一样,重载输入运算符函数也不能是所操作类的成员函数,但可以是该类的友元函数或独立函数。
9.4 格式控制 • C++语言提供了两种格式控制方法 • 利用ios类中的格式控制成员函数 • 利用操作符
格式控制成员函数 • 状态设置和状态报告 • ios类中定义的long类型的保护数据成员x_flag用于存入控制输入输出格式的状态标志。 • 状态标志位共15个,各占一个二进制位,称为格式状态位。 • 若设置了某个状态标志位,则x_flags中的某一位为“1”,否则为“0”。 例如:若在状态标志中设定了skipws和dec,其它均未设定,则x_flags的值应为0000000000010001, 即为十六进制的0x0011。 这些状态值之间是或的关系,可以几个并存。
设置状态标志 long ios::setf(long flags) { x_flags = x_flags | flags; //(按位或) return x_flag; } 功能:把指定位的状态标志设置为1。 调用如:ut<<setf(ios::hex||ios::scientific); • 清除状态标志 long ios::unsetf(long flags) { x_flags = (x_flags) & (~flags); return x_flags; } 功能:把指定位的状态标志设置为0。
取状态标志 long ios::flags() const { return x_flags; } 功能:返回当前流的状态标志位数值。 long ios::flags(long flags) { long x = x_flags; x_flags = flag; return x; } 功能:设置指定位的状态标志,并返回设置前的状态标志位数值。 • 与setf()的差别:setf()是在原有的基础上追加设定,而flags(long flags)是用新设定覆盖以前的状态标志。
显示精度设置 在ios类中用x_precision来存放浮点数的输出显示精度。 int ios::precision() { return x_precision; } 功能:返回当前的显示精度 int ios::precision(int n) { int x = x_precison; x_precison = n; return x; } 功能:设置显示精度为n位, 并返回设置前的显示精度。
域宽设置 用于用户控制输出格式,在ios类中用x_width存放。 int ios::width() { return x_width; } 功能:返回当前的域宽值 int ios::width(int n) { int i = x_width; x_width = n; return i; } 功能:设置域宽为n位
填充字符设置 在ios类中用x_fill来存放填充字符,填充字符的作用是当输出值不满域宽时用填充字符来填充,缺省情况下填充字符为空格。与width()函数配合使用。 char ios::fill() { return x_fill; } 功能:返回当前的填充字符值 char ios::fill(char c) { char x = x_fill; x_fill = c; return x; } 功能:设置填充字符为c
x_width=0 123 x_precision=6 123 123.79 345.787 11 10 x_width=0 12 12 x_width=12 int i = 10; cout<<i<<' ' <<i++<<endl; cout<<i<<' '<<++i<<endl; cout<<"x_width="<<cout.width()<<endl; cout<<"x_width="<<cout.width(10)<<endl; cout.width(12); cout<<"x_width="<<cout.width()<<endl; cout<<123<<endl; cout<<"x_precision="<<cout.precision()<<endl; cout<<123<<' '<<123.78985<<' '<<345.787<<endl;
说明: • 未设置域宽时,x_width取值为0,当用width(n)设置域宽后,它只对最近跟着它的第一个输出有影响,第一个输出完成后,x_width 马上被自动置为0。 • x_precision与实际输出数值的精度不一致时,最终输出结果为: • 若实际输出数值的精度大于x_precision,则以x_precision作为精度四舍五入后输出; • 若实际输出数值的精度小于x_precision,则按实际精度输出。
操作符 • 使用ios类的成员函数来控制输入/输出格式时,必须缀上使用它的流对象(见例11.3),而且不能将它们直接嵌入到输入/输出语句中去。为此,C++还提供了操作符方法。 • 操作符是一个函数,它以一个流引用作为参数,并返回同一流的引用。因此它可以嵌入到输入/输出操作的链中。例如,hex操作符的函数体定义: ios & hex(ios & o) { o.setf(ios::hex); return o; }
C++提供的预定义的标准操作符。(见表11.1) • 无参数操作符 (P200表9.1) 例9.4 • 有参数操作符 (P201表9.2) 例9.5
9.5 文件的读/写 • 基本概念 • C++把文件看作字符/序列,即所谓流式文件。 • 根据数据的组织形式,文件可分为文本文件和二进制文件。 • 文本文件又称ASCII文件,它的每个字节存放一个ASCII代码,代表一个字符。这样便于对字符进行处理,也便于输出,但占用存储空间较多。 • 二进制文件是把内存中的数据,按其在内存中的存储形式原样写到磁盘上。用二进制形式输出数据,可节省存储空间和转换时间,但一个字节并不对应一个字符。一般情况下,中间结果常用二进制文件保存。 • 按操作方法,文件可分为输入文件和输出文件。
打开的文件 其它设备(变量) 文件流的写 • 文件流是文件与内存等设备之间的信息交换过程。 注意: • 文件流的读对应内存变量的输入 • 文件流的写对应内存变量的输出 文件流的读
C++中文件输入/输出的基本过程 • 在程序中要包含头文件fstream.h。 • 创建一个流。 • 将这个流与文件相关联,即打开文件。 • 进行文件的读写操作。 • 关闭文件。
9.5.1 文件的打开和关闭 • 文件的打开 • C++有三种类型的文件流: • 输入文件流ifstream • 输出文件流ofstream • 输入/输出文件流fstream 这些文件流类都定义在fstream.h文件中。各有4个重载的构造函数。 • 在C++中,打开一个文件,就是将这个文件与一个流建立关联;
打开文件的两种方式 • 在打开文件时要定义流类的对象,三个类的常用构造函数分别为: ifstream(const char *, int =ios::in, int =filebuf::openprot); ofstream(const char *, int =ios::out, int =filebuf::openprot); fstream(const char *, int, int =filebuf::openprot); 参数含义: 字符串形式的文件名、文件流的操作模式、打开文件的共享/保护模式。
一旦定义了一个对象,就建立了一个外存介质上的文件和内存的交换通道,并指定了数据交换的方向。一旦定义了一个对象,就建立了一个外存介质上的文件和内存的交换通道,并指定了数据交换的方向。 例如: ifstream ifs(“Data1.dat”); ofstream ofs(“Data2.dat”); fstream iofs(“Data3.data”, ios::in|ios::out); • ifstream类、ofstream类和fstream类中提供了专门的打开成员函数。 void ifstream::open(const char *, int =ios::in, int =filebuf::openprot); void ofstream::open(const char *, int =ios::out, int =filebuf::openprot); void fstream::open(const char *, int, int =filebuf::openprot);
例如: ifstream ifs; ofstream ofs; fstream iofs; ifs.open(“Data1.dat”); ofs.open(“Data2.dat”); iofs.open(“Data3.dat”, ios::in|ios::out); • 打开当前工作目录下的指定的文件; • 如果当前工作目录下不存在指定文件,则在当前工作目录下创建一个新的指定文件。 • 可指定文件目录,如: ifstream ifs(“e:\mytest\Data1.dat”);
文件的关闭 • 关闭文件即是使打开的文件与流“脱钩”。使用流类的成员函数close()完成,它不带参数,无返回值。 例如: out.close(); 将关闭与流out相连接的文件。 • 关闭文件的作用 • 把要写入文件的数据从缓冲区中完全写回磁盘。(内存和文件的数据交换是通过缓冲区完成的。) • 保证文件安全。