590 likes | 777 Vues
第 10 章 输入、输出流. 10 . 1 I/O 流的概念 10 . 2 文件流 10 . 3 文件流应用举例. 第 10 章 输入、输出流. 教学目标 : 1. 掌握流的概念、输入及输出流; 2. 学会应用流文件。 教学重点 : 1. I/O 流和文件的应用 教学难点 : 1. 流文件应用的理解。. 10 . 1 I/O 流的概念. 10.1.1 流的概念 10.1.2 输出流 10.1.3 输入流. 10 . 2 文件流. 10.2.1 输出文件流 10.2.2 输入文件流.
E N D
第10章 输入、输出流 10.1 I/O流的概念 10.2 文件流 10.3 文件流应用举例
第10章 输入、输出流 • 教学目标: 1.掌握流的概念、输入及输出流; 2.学会应用流文件。 • 教学重点: 1. I/O流和文件的应用 • 教学难点: 1.流文件应用的理解。
10.1 I/O流的概念 10.1.1 流的概念 10.1.2 输出流 10.1.3 输入流
10.2 文件流 • 10.2.1 输出文件流 • 10.2.2 输入文件流
basic_ios basic_streambuf 指针 basic_istream basic_ostream basic_iostream basic_ifstream basic_ofstream basic_ fstream 10.1.1 流的概念 流类体系:以抽象类模板basic_ios为基类,流类模板派生体系见图10.1。整个流类模板体系的标准I/O在头文件<iostream>中说明,包含头文件<ios>、<streambuf>、<istream>和<ostream>。而输入输出文件流部分在头文件<fstream>中说明。 图10.1 主要输入/输出流模板层次
流类体系说明: basic_streambuf不是basic_ios的派生类,而是一个独立的类,只是basic_ios有一个保护访问限制的指针指向它。 类basic_streambuf的作用是管理一个流的缓冲区。 basic_ios类模板提供了对流进行格式化输入输出和错误处理的成员函数。所有派生都是公有派生。basic_istream类模板提供完成提取(输入)操作的成员函数,而basic_ostream类模板提供完成插入(输出)操作的成员函数。basic_iostream类是前两者的聚合,并没有增加成员。派生全部为公有派生。 *从技术上讲,模板并不能派生其他模板。但是这些模板只是预先定义了数据类型(如char或wchar_t等等)的模板实例,这些实例都是类,所以可以继承。
10.1.2 输出流 标准输入/输出流对象: 在C++的流类库中定义了四个全局流对象:cin,cout,cerr和clog。可以完成人机交互的功能。 cin标准输入流对象,键盘为其对应的标准设备。 cout标准输出流对象,显示器为标准设备。 cerr和clog标准错误输出流,输出设备是显示器。 其中cin、cout和clog是带缓冲区的,缓冲区由streambuf类对象来管理。而cerr为非缓冲区流,一旦错误发生立即显示。 要使用这四个功能,必须包含<iostream>文件。
10.1.3 输入流 提取运算符和插入运算符: 重载的提取运算符“>>”(stream_extraction operator)和插入运算符“<<”(stream_insertion operator),执行输入/输出操作。 “提取”的含义是指输入操作,可看作从流中提取一个字符序列。 “插入”的含义是指输出操作,可看作向流中插入一个字符序列。 cin使用提取运算符。 cout、cerr和clog使用插入运算符。 文件: 文件处理完成永久保存的功能。 在Windows下不同的C++平台,都为文件功能作了扩充,在VC++的MFC编程中采用了序列化(Serialization)。
10.2 文件流 • 10.2.1 输出文件流 • 10.2.2 输入文件流
格式控制符: C++在类ios中提供格式化输入输出。这些格式是对所有文本方式的输入输出流均适用。格式控制符定义为公有的无名的枚举类型: enum{ skipws=0x0001, //跳过输入中的空白字符 left=0x0002, //输出左对齐 right=0x0004, //输出右对齐 internal=0x0008, //在输出符号或数制字符后填充 dec=0x0010, //在输入输出时将数据按十进制处理 oct=0x0020, //在输入输出时将数据按八进制处理 hex=0x0040, //在输入输出时将数据按十六进制处理 showbase=0x0080, //在输出时带有表示数制基的字符
showpoint=0x0100, //输出浮点数时,必定带小数点 uppercase=0x0200, //输出十六进制,用大写 showpos=0x0400, //输出正数时,加”+”号 scientific=0x0800, //科学数方式输出浮点数 fixed=0x1000, //定点数方式输出实数 unitbuf=0x2000, //插入后,立即刷新流 stdio=0x4000} //插入后,立即刷新stdout和stderr 该枚举量说明中每一个枚举量实际对应两字节数据(16位)中的一个位,所以可以同时采用几个格式控制,只要把对应位置1即可,这样既方便又节约内存。取多种控制时,用或“|”运算符来合成,合成为一个长整型数,在ios中为: protected: long x_flags; 点击访问x_flags的重载函数定义
10.2.1 输出文件流 输入输出流格式控制标志: protected: int x_precision; //标志浮点数精度,默认为6位 int x_width; //输出域宽,默认域宽为0, //重设域宽只对其后第一输出项有效,如域宽不足,则不受限制 char x_fill; //标志域宽有富余时填入的字符 点击访问格式控制标志相关接口函数 【例10.1】整型数输出。 【例10.2】浮点数输出。
【例10.2】程序执行后输出: 默认域宽为:0位 默认精度为:6位 默认表达方式:31.4159 科学数表达方式:3.141593e+001 定点表达方式:31.415927 9位科学数表达方式:3.141592654e+001 流操作子(setiosflags stream manipulator): 可代替流格式控制成员函数 点击查阅流操作算子 【例10.2_1】采用流操作算子的浮点数输出。
标准设备输入使用要点: • cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输入过多,会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后,才要求输入新的数据。不可能用刷新来清除缓冲区,所以不能输错,也不能多输! • 输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state(枚举类型io_state)中对应位置位(置1),程序继续。所以要提高稳健性,就必须在编程中加入对状态字state的判断。
3. 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,则空格(ASCII码为32)无法用cin输入,字符串中也不能有空格。回车符也无法读入。 4.输入数以后再输入字符或字符串:如果数后直接加回车,应该用cin.get()提取回车。如果还有空格,则要清空缓冲区。
程序运行状态: 状态字state为整型,其的各位在ios中说明: enum ios_state{ goodbit=0x00, //流正常 eofbit=0x01, //输入流结束忽略后继提取操作;或文件结束已无数据可取 failbit=0x02, //最近的I/O操作失败,流可恢复 badbit=0x04, //最近的I/O操作非法,流可恢复 hardfail=0x08 // I/O出现致命错误,流不可恢复,VC6.0++不支持 } 点击查看读取状态的有关操作 例10.3】提高输入的稳健性。输入时需要故意输错,以测试稳健性。
10.2.2 输入文件流 输入流成员函数声明: 字符输入: int istream::get(); //提取一个字符,包括空格,制表,backspace和回车等, //与cin有所不同.注意返回为整型 istream&istream::get(char &); istream&istream::get(unsignedchar &); 提取一个字符,放在字符型变量中
字符串输入: istream&istream::get(char *,int,char=’\n’); istream&istream::get(unsignedchar *,int,char=’\n’); istream&istream::getline(char *,int,char=’\n’); istream&istream::getline(unsigned char *,int,char=’\n’); 提取的串放在第一个参数为开始地址的存储区(不查边界);第二个参数为至多提取的字符个数(指定为n,最多取n-1个,再加一个字符串结束符);第三个参数为结束字符,遇此字符则结束,默认为回车换行符。 get系列函数要求单独提取结束字符。getline提取字符串时如遇到指定结束符则提取该结束符,但不保存在串中。这两个函数都会在提取的一系列字符后加一个串结束符,返回值为对象本身(*this)。
其他函数: 函数gcount()返回最后一次提取的字符数量,包括回车: int istream::gcount(); 函数ignore()读空(指定一个大的数量)缓冲区: istream&istream::ignore(int=1,int=EOF); 第一个参数为要提取的字符数量,默认为1;第二个参数为结束字符,提取该结束字符,但对所提取的字符不保存不处理,作用是空读。第二个参数的默认值EOF为文件结束标志。 在iostream中EOF定义为-1,在int get()函数中,读入输入流结束标志Ctrl+Z(^Z)时,函数返回EOF,为了能表示EOF的“-1”值,返回类型为int。采用cin.eof()函数,当前所读为EOF则返回非零,注意函数自身未从流中读取。 【例10.4】ignore()和gcount()函数使用。
输出流成员函数声明: ostream&ostream::put(char); //输出参数字符 ostream&ostream::put(unsignedchar); ostream&ostream::put(signedchar); ostream&ostream::flush(); //刷新一个输出流,用于cout和clog
重载插入和提取运算符: 重载必须保留原来的使用特性。重载只能在用户定义类中,将重载的运算符的函数说明为该类的友元函数: friend istream&operator>>(istream&,className&); friend ostream&operator<<(ostream&,className&); 函数的返回值是对输入或输出流的引用,这是为了保证在cin和cout中可以连续使用“>>”或“<<”运算符,与所有“>>”和“<<”重载函数一致。第一个参数是输入或输出流的引用,作为“>>”或“<<”的左操作数;第二个参数为用户定义类的引用,作为右操作数,流用作函数参数,必须是引用调用,不能是传值调用。 【例10.6】用户定义的复数类Complex的输入与输出。
0 1 2 3 4 5 6 7 8 … n-1 … 文件结束符 图10-1 C++把文件看作有序的n个字节的流 文件的基本概念: 本节中文件指的是磁盘文件。C++根据文件(file)内容的数据格式,可分为两类:二进制文件和文本文件。文本文件由字符序列组成,也称ASCII码文件,在文本文件中存取的最小信息单位为字符(character),而二进制文件中存取的最小信息单位为字节(Byte)。 C++把每一个文件都看成一个有序的字节流,见图9.3,每一个文件或者以文件结束符(end of file marker)结束,或者在特定的字节号处结束。
文件使用步骤: 1.说明一个文件流对象,这又被称为内部文件: ifstream ifile;//只输入用 ofstream ofile;//只输出用 fstream iofile;//既输入又输出用
2.使用文件流对象的成员函数打开一个磁盘文件。这样在文件流对象和磁盘文件名之间建立联系。文件流中说明了三个打开文件的成员函数。2.使用文件流对象的成员函数打开一个磁盘文件。这样在文件流对象和磁盘文件名之间建立联系。文件流中说明了三个打开文件的成员函数。 void ifstream::open(constchar*,int =ios::in, int=filebuf::openprot); void ofstream::open(const char *,int=ios::out, int=filebuf::opernprot); void fstream::open(const char*,int, int=filebuf::openprot); 第一个参数为要打开的磁盘文件名。第二个参数为打开方式,有输入(in),输出(out)等,打开方式在ios基类中定义为枚举类型。第三个参数为指定打开文件的保护方式,一般取默认。所以第二步可如下进行: iofile.open(“myfile.txt”,ios::in|ios::out);
三个文件流类都重载了一个带默认参数的构造函数,功能与open函数一样:三个文件流类都重载了一个带默认参数的构造函数,功能与open函数一样: ifstream::ifstream(const char*,int=ios::in,int=filebuf::openprot); ofstream::ofstream(constchar*,int=ios::out, int=filebuf::openprot); fstream::fstream(const char*,int, int=filebuf::operprot); 所以1,2两步可合成: fstream iofile(”myfile.txt”,ios::in|ios::out);
打开文件也应该判断是否成功,若成功,文件流对象值为非零值,不成功为0(NULL),文件流对象值物理上就是指它的地址。打开文件也应该判断是否成功,若成功,文件流对象值为非零值,不成功为0(NULL),文件流对象值物理上就是指它的地址。 因此打开一个文件完整的程序为: fstream iofile(”myfile.txt”,ios::in|ios::out); if(!iofile){ //“!”为重载的运算符,见9.3.1节 cout<<”不能打开文件:”<<”myfile,txt”<<endl; return-1; }//失败退回操作系统
3.使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写,这在下一节中讨论。3.使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写,这在下一节中讨论。 4.关闭文件。三个文件流类各有一个关闭文件的成员函数: void ifstream::close(); void ofstream::close(); void fstream::close(); 使用很方便,如: iofile.close();
关闭文件时,系统把该文件相关联的文件缓冲区中的数据写到文件中,保证文件的完整,收回与该文件相关的内存空间,可供再分配,把磁盘文件名与文件流对象之间的关联断开,可防止误操作修改了磁盘文件。如又要对文件操作必须重新打开。 关闭文件并没有取消文件流对象,该文件流对象又可与其他磁盘文件建立联系。文件流对象在程序结束时,或它的生命期结束时,由析构函数撤消。它同时释放内部分配的预留缓冲区。
文件打开方式: 是由在ios类中定义的公有枚举成员决定: enum open_mode{ in=0x01, out=0x02, ate=0x04, app=0x08, trunc=0x10, binary=0x80 };
打开方式解释: in标识打开文件用于输入操作(从文件读取)。打开方式只要含in,如文件不存在则返回失败。在打开为输入输出方式时(同时用out),编程应注意判断是否失败,失败时千万不可再写入文件。 out标识打开文件用于输出操作(写入文件)。如文件不存在,则建立新文件,如文件存在,未同时设app, in则文件清空。 trunc标识打开文件,并清空它(文件长度截为0)。文件不存在则建立新文件,与out默认操作相同。但与in配合,文件不存在则返回失败。 app标识打开文件用于输出,原文件内容保留,新数据接在尾部 ate意思是at end,标识打开文件,文件指针在文件尾,但文件指针可以移动,即新数据可写到任何位置。文件是否清空由其它标识决定。 以上三个标识最好配合out、in等一起用,因为不同的C++平台,要求不同,一起用不会出错。不一起用,至少VC++不认这种格式。 binary标识以二进制方式打开文件。同时用out时,如文件不存在,则建立新文件,并且新文件能用,不必清状态字。
文本文件的顺序读写: 顺序读写可用C++的提取运算符(>>)和插入运算符(<<)进行。 【例10.7】复制文件。 【例10.8】按行复制文本文件。 【例10.9】文本式数据文件的创建与读取数据。 资源获取是由构造函数实现,而资源释放是由析构函数完成。所以与内存动态分配一样,由文件重构对象放在构造函数中,把对象存入文件则放在析构函数中。参见10.6节。
对二进制文件进行读写的成员函数: istream&istream::read(char *,int); //从二进制流提取 istream&istream::read(unsigned char*,int); istream&istream::read(signedchar *,int); //第一个参数指定存放有效输入的变量地址, //第二个参数指定提取的字节数, //函数从输入流提供指定数量的字节送到指定地址开始的单元 ostream&ostream::write(constchar *,int); //向二进制流插入 ostream&ostream::write(constunsignedchar *,int); ostream&ostream::write(const signedchar *,int); //第一个参数指定输出对象的内存地址, //第二个参数指定插入的字节数, //函数从该地址开始将指定数量的字节插入输入输出流
文件结束判断: 读函数并不能知道文件是否结束,可用状态函数int ios::eof()来判断文件是否结束。必须指出系统是根据当前操作的实际情况设置状态位,如需根据状态位来判断下一步的操作,必须在一次操作后立即去调取状态位,以判断本次操作是否有效。 【例10.10】创建二进制数据文件,以及数据文件的读取。这两项操作设计为成员函数。给出与【例10.9】不同的读写方式:
二进制文件优点: 可以控制字节长度,读写数据时不会出现二义性,可靠性高。同时不知格式是无法读取的,保密性好。文件结束后,系统不会再读(见eofbit的说明),但程序不会自动停下来,所以要判断文件中是否已没有数据。如写完数据后没有关闭文件,直接开始读,则必须把文件定位指针移到文件头。如关闭文件后重新打开,文件定位指针就在文件头。
文件的随机访问: 在C++中可以由程序控制文件指针的移动,从而实现文件的随机访问,即可读写流中任意一段内容。一般文本文件很难准确定位,所以随机访问多用于二进制文件。 如【例10.9】中对象中两个字符串是按实际串长存放的,不是按数组元素来存放的,而【例10.10】中是按数组长度来存放的,每个对象数据长度固定,所以便于随机访问。 随机访问指针控制字: 在ios类中说明了一个公有枚举类型: enum seek_dir{ beg=0, //文件开头 cur=1, //文件指针的当前位置 end=2 //文件结尾 };
输入流指针控制字设置成员函数: istream&istream::seekg(streampos);//指针直接定位 istream&istream::seekg(streamoff, ios::seek_dir); //指针相对定位 long istream::tellg();//返回当前指针位置 流的指针位置类型streampos和流的指针偏移类型streamoff定义为长整型,也就是可访问文件的最大长度为4G 例:datafile.seekg(-20L,ios::cur); //表示将文件定位指针从当前位置向文件头部方向移20个字节。 datafile.seekg(20L,ios::beg); //表示将文件定位指针从文件头向文件尾方向移20个字节。 datafile.seekg(-20L,ios::end); //表示将文件定位指针从文件尾向文件头方向移20个字节。 tellg()和seekg()往往配合使用。 //指针不可移到文件头之前或文件尾之后。
输出流指针控制字设置成员函数: ostream&ostream::seekp(streampos); ostream&ostream::seekp(streamoff,ios::seek_dir); long ostream::tellp(); 为了便于记忆,函数名中g是get的缩写,而p是put的缩写。对输入输出文件定位指针只有一个但函数有两组,这两组函数功能完全一样。 【例10.11】使用随机访问对【例10.10】进行改造。
字符流概念: 字符串(string)也可以看作字符流。可以用输入输出操作来完成串流的操作。串流与内存相关,所以也称内存流。 串流类包括ostrstream、istrstream、strstream,它们在<strstrea.h>中说明。串流类对象可以保存字符,也可以保存整数、浮点数。串流类对象采用文本方式。 其构造函数常用下面几个: istrstream::istrstream(constchar * str); istrstream::istrstream(constchar * str,int); ostrstream::ostrstream(char *,int,int=ios::out); strstream::strstream(char *,int,int); 其中第二个参数说明数组大小,第三参数为文件打开方式。 【例10.12】
规范化操作: 在面向对象的程序设计中,信息总是放在对象的数据成员里。这些信息最终应该保存到文件中。当程序开始运行时要由打开的文件重新创建对象。在运行过程中,放在对象的数据成员里的信息得到利用和修改。运行结束时必须把这些信息重新保存到文件中,然后关闭文件。 在面向对象的C++程序设计中,文件应该在构造函数中打开,并创建对象;而在析构函数中保存和关闭文件,并撤销对象。当撤销对象时,能自动释放资源。释放资源包括将对象中的信息再次存入磁盘文件。程序运行中,总要对保存在对象的数据成员里的信息进行操作,这时应该将信息适时保存到相应的磁盘文件中,以免数据意外丢失。这些都是常规操作,是面向对象的C++程序设计的固定框架。
【例10.13】将商店的货物,定义为一个货物数组类。数组对象动态建立,初始为2个元素,不够用时扩充一倍。用文本数据文件建立数组元素对象,要求放在构造函数中,而数据的保存和文件的关闭放在析构函数中。第一次运行时,建立空的数据文件,由键盘输入建立数组元素对象,并写入文件,程序退出时,关闭文件;下一次运行由该文件构造对象,恢复前一次做过的工作。【例10.13】将商店的货物,定义为一个货物数组类。数组对象动态建立,初始为2个元素,不够用时扩充一倍。用文本数据文件建立数组元素对象,要求放在构造函数中,而数据的保存和文件的关闭放在析构函数中。第一次运行时,建立空的数据文件,由键盘输入建立数组元素对象,并写入文件,程序退出时,关闭文件;下一次运行由该文件构造对象,恢复前一次做过的工作。 这是一个标准的面向对象的程序设计,也是对前面各章内容的小结。注意,本例使用了多重的插入运算符重载。
10.3 文件流应用举例 int main(){ Complex a,b,c; cout<<"输入一个实数"<<endl; cin>>a; cout<<"输入一个用括号括起来的实数"<<endl; cin>>b; cout<<"输入一个用括号括起来复数"<<endl; cin>>c; cout<<"a="<<a<<'\t'<<"b="<<b<<'\t‘ << "c="<<c<<'\n'; return 0; }
【例10.7】复制文件。 int main(){ char ch; ifstream sfile("d:\\chapter_10\\Ex_7.cpp"); ofstream dfile(“d:\\chapter_10\\newFile.cpp"); //只能创建文件,不能建立子目录,如路径不存在则失败 if(!sfile){ cout<<"不能打开源文件:"<<" d:\\chapter_10\\Ex_7.cpp"<<endl; return -1;} if(!dfile){ cout<<"不能打开目标文件:"<<“d:\\chapter_10\\newFile.cpp"<<endl; return -1;} sfile.unsetf(ios::skipws); //关键!把跳过空格控制位置0,即不跳过空格,否则空格全部未拷 while(sfile>>ch)dfile<<ch; sfile.close(); //如没有这两个关闭函数,析构函数也可关闭 dfile.close(); return 0;}
首先必须设置关闭跳过空白,因为提取(“>>”)运算符在默认情况下是跳过空白(包括空格,制表,backspace和回车等)的,这样复制的文件会缺少一些字符。首先必须设置关闭跳过空白,因为提取(“>>”)运算符在默认情况下是跳过空白(包括空格,制表,backspace和回车等)的,这样复制的文件会缺少一些字符。 第二,该程序能确定文件是否复制结束。流类成员函数和运算符全是返回本类型的引用,这里就是流文件对象自身,当文件结束时,返回NULL,这时不再复制,退出循环。 第三,复制是按字节进行的,效率很低,按字节传递开销极大,但该程序能正确复制任意类型的文件,不仅是文本文件(看作按字符),二进制文件(看作按字节)也一样可正确完成。如果是文本文件,我们可以按行进行复制。 第四,!sfile中的!是重载的运算符,在状态函数中重载,当该操作出现不正常状态,返回true。
【例10.8】按行复制文本文件。 int main(){ char filename[256],buf[100]; fstream sfile,dfile; cout<<"输入源文件路径名:"<<endl; cin>>filename; //对路径名而言空格是无关紧要的,否则要用getline()等成员函数 sfile.open(filename,ios::in);//打开一个已存在的文件 while(!sfile){ cout<<"源文件找不到,请重新输入路径名:"<<endl; sfile.clear(0); //清状态字 cin>>filename; sfile.open(filename,ios::in); } cout<<"输入目标文件路径名:"<<endl; cin>>filename; //只能创建文件,不能建立子目录,如路径不存在则失败
dfile.open(filename,ios::out); if(!dfile){ cout<<"目标文件创建失败"<<endl; return -1;} while(sfile.getline(buf,100)){ //按行复制 A行 if(sfile.gcount()<100) dfile<<buf<<'\n'; //因回车符未送到 B行 else dfile<<buf; } //本行大于99个字符,还未读到回车符,所以不加'\n' sfile.close(); dfile.close(); return 0;} A行中sfile.getline(buf,100)从源文件读一行字符,或读99个字符,效率大大提高。B行中,因从源文件读字符是遇到行结束(回车换行)符停止的,所以文件中应有一个回车换行符;但getline()回车换行符并不放在buf中,因此要加一个回车换行符,但此程序只能用于文本文件。
【例10.9】文本式数据文件的创建与读取数据。【例10.9】文本式数据文件的创建与读取数据。 典型的C++数据存入文件和由文件获得数据的方法是把对象存入文件和由文件重构对象。本例对提取和插入运算符进行了重载,只用一个“>>”完成重构对象,而只用一个“<<”完成对象存入文件。 class inventory{ string Description; string No; int Quantity; double Cost; double Retail; public: inventory(string="#",string="0",int=0,double=0,double=0); friend ostream&operator<<(ostream&dist,inventory&iv); friend istream&operator>>(istream&sour,inventory&iv); }; //流类作为形式参数必须是引用
inventory::inventory(string des,string no,int quan, double cost,double ret){ Description=des; No=no; Quantity=quan; Cost=cost; Retail=ret;} ostream &operator<<(ostream&dist,inventory&iv){ dist<<left<<setw(20)<<iv.Description<<setw(10)<<iv.No; dist<<right<<setw(10)<<iv.Quantity<<setw(10)<<iv.Cost <<setw(10)<<iv.Retail<<endl; return dist; } //写入文件是自动把数转为数字串后写入 istream&operator>>(istream&sour,inventory&iv){ sour>>iv.Description>>iv.No>>iv.Quantity >>iv.Cost>>iv.Retail; return sour; } //从文件读出是自动把数字串转为数读出,函数体内>>功能不变
int main(){ inventory car1("夏利2000","805637928",156,80000,105000),car2; inventory motor1("金城125","93612575",302,10000,13000),motor2; ofstream distfile("d:\\chapter10_9.data"); distfile<<car1<<motor1; //注意ofstream是ostream的派生类 distfile.close(); cout<<car1; cout<<motor1; cout<<car2; cout<<motor2; ifstream sourfile("d:\\chapter10_9.data"); //这样分两次打开,可避免读文件时,误改了源文件 sourfile>>car2>>motor2; sourfile.close(); cout<<car2; cout<<motor2; return 0; }
【例10.10】创建二进制数据文件,及数据文件的读取。【例10.10】创建二进制数据文件,及数据文件的读取。 类inventory与【例10.9】基本一样,只是用二进制文件的Bdatatofile()和Bdatafromfile()取代了文本文件的提取运算符>>和插入运算符<<。 void inventory::Bdatafromfile(ifstream&sour){ char k[20]; sour.read(k,20); Description=k; sour.read(k,10); No=k; sour.read((char*)&Quantity,sizeof(int)); sour.read((char*)&Cost,sizeof(double)); sour.read((char*)&Retail,sizeof(double)); } //由此可见读和写是完全对称的过程,次序决不能错
void inventory::Bdatatofile(ofstream&dist){ dist.write(Description.c_str(),20); //由string类的c_str()函数转为char* dist.write(No.c_str(),10); dist.write((char*)&Quantity,sizeof(int)); dist.write((char*)&Cost,sizeof(double)); dist.write((char*)&Retail,sizeof(double));} int main(){ inventory car1("夏利2000","805637928",156,80000,105000),car2; inventory motor1("金城125","93612575",302,10000,13000),motor2; ofstream ddatafile("d:\\chapter10_10.data",ios::out|ios::binary); car1.Bdatatofile(ddatafile); motor1.Bdatatofile(ddatafile); cout<<"对象car1:"<<endl; cout<<car1; cout<<"对象motor1:"<<endl; cout<<motor1; cout<<"对象car2:"<<endl; cout<<car2; cout<<"对象motor2:"<<endl; cout<<motor2;