330 likes | 450 Views
Unit 9 — I/O 流 类库 和 异 常处理. 第九 章 流类库 与输入 / 输出. C++ 的 标 准 库中包 含了一个 I/O 流类 库, “流 ”是对 数 据从一个对象到另一个对象的传 送过程的抽象,数 据的输入 / 输 出通 过输入 / 输出流来实现的。. 9.1 C++ 的基本流类体系. 9.4 文件的输入 / 输出. * 9.5 字符串流. * 9.2 输入 / 输出的控制格式. 9.3 标准设备的输出 / 输出. 9.6 文件与对象. 9.1 C++ 的基本流类体系. basic_ios.
E N D
Unit 9 —I/O流类库和异常处理 第九章 流类库与输入/输出 C++的标准库中包含了一个I/O流类库,“流”是对数据从一个对象到另一个对象的传送过程的抽象,数据的输入/输出通过输入/输出流来实现的。 9.1 C++的基本流类体系 9.4 文件的输入/输出 *9.5 字符串流 *9.2 输入/输出的控制格式 9.3 标准设备的输出/输出 9.6 文件与对象
9.1 C++的基本流类体系 basic_ios basic_streambuf 指针 basic_istream basic_ostream basic_iostream basic_ifstream basic_ofstream basic_fstream 提供完成插入(输出)操作的成员函数。 流类体系: 提供对流进行格式化输入输出和错误处理的操作。 提供完成提取(输入)操作的成员函数。 独立的类模板,basic_ios包含一个保护型basic_streambuf指针成员(聚合), 用于管理一个流的缓冲区。 提供输出文件相关操作 提供输入文件相关操作 仅为前两者的聚合,未增加成员。 所有标准I/O流类模板在头文件<iostream>中说明,包含头文件<ios>、<streambuf>、<istream>和<ostream>。而输入输出文件流则在头文件<fstream>中说明。 流类模板的层次并非指的模板之间的继承关系,而旨在表明类模板实例的继承关系。
9.1 C++的基本流类体系 输出/输出标准流对象: 1. C++流类库中定义了4个全局流对象:cin,cout,cerr和clog,用于完成人机交互的功能。 cin标准输入流对象——键盘; cout标准输出流对象——显示器; cerr和clog标准错误输出流对象——显示器。 2. cin、cout和clog是带缓冲区的,缓冲区由streambuf类对象来管理。而cerr为非缓冲区流,一旦错误发生立即显示。 3. 要使用这四个功能,必须包含<iostream>头文件。注意C++标准库<fstream>不包括<iostream>,两者是独立的。
9.1 C++的基本流类体系 对象提取运算符和插入运算符: 1.重载的提取运算符“>>”和插入运算符“<<”,执行字符序列的输入/输出操作。 提取:指输入操作,从流中提取一个字符序列,如“cin>>a;”。 插入:指输出操作,向流中插入一个字符序列,如“cout<<a;”。 2. cin使用提取运算符;cout、cerr和clog使用插入运算符。 文件的输入与输出: 文件输入输出完成磁盘文件的读取和永久保存的功能。 Windows下不同的C++平台,都为文件功能作了扩充,在VC++的MFC编程中采用了序列化(Serialization)。
9.3 标准设备的输入/输出 *9.3.1 提高标准输入/输出的健壮性 *9.3.2 标准输入/输出成员函数 9.3.3 重载插入和提取运算符
标准设备输入使用要点: • cin为缓冲流。键盘输入的数据首先保存在缓冲区中,当要提取时,系统将是从缓冲区中拿。如果键盘一次输入数据多于实际要提取的,则多余的数据会留在缓冲区,等着慢慢用;如果输入错了,必须在回车之前修改,一旦回车则数据传到缓冲区中。只有把输入缓冲区中的数据取完后,才要求输入新的数据。不可能用刷新来清除缓冲区,所以不能输错,也不能多输! • 输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state(枚举类型io_state)中对应位置位(置1),程序继续。所以要提高健壮性,就必须在编程中加入对状态字state的判断。 9.3.1 提高标准输入/输出的健壮性
标准设备输入使用要点: 9.3.1 提高标准输入/输出的健壮性 3. 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,则空格(ASCII码为32)无法用cin输入,字符串中也不能有空格。回车符也无法读入。 4. 输入数据以后再输入字符或字符串:如果数后直接加回车,应该用cin.get()提取回车。如果还有空格,则要清空缓冲区。
重载插入和提取运算符: 在用户定义类中,将重载的运算符说明为其友元函数: friend istream& operator>>(istream&,className&); friend ostream& operator<<(ostream&,className&); 说明: 函数的返回值是对输入或输出流的引用,保证在cin和cout中可以连续使用“>>”或“<<”运算符。 第1个参数是输入或输出流的引用,作为“>>”或“<<”的左操作数;第2个参数为用户定义类的引用,作为右操作数。 流用作函数参数,必须是引用调用,不能是传值调用。因为要处理的是流本身,而不是其副本。 9.3.3 重载插入和提取运算符 【作业H8_2】重载插入运算符“<<”。 【例9.6】用户定义的复数类Complex的输入与输出。
重载【作业H8_2】的插入运算符“<<”: 重载插入运算符“<<”声明为Scholar类的友元函数: friend ostream & operator<<(ostream & ,constmystring &); //流类作为形式参数必须是引用 重载插入运算符“<<”定义: ostream & operator<<(ostream & o,const Scholar & s){ returno<<s.Name<<'\t'; } Orderedlist类的打印函数简化为: template <typenameT,int size> void Orderedlist<T,size>::print(){ int i; for(i=0;i<=last;i++){ cout<<pslst[i]; //取代“pslst[i].show();”P320:slist[i].key.show() if(i%5==4) cout<<endl;} cout<<endl; } 输出格式统一,无论对于基本数据类型,还是用户定义类型均通用。 9.3.3 重载插入和提取运算符
【例9.6】用户定义复数类Complex的输入/输出: #include<iostream> using namespacestd; classComplex{ double Real, Image; public: Complex ( double r=0.0, double i=0.0):Real(r),Image(i) {} //这里省略若干成员函数, 详见【例4.7】 friendostream&operator<<(ostream&s, constComplex&z); friend istream &operator>>(istream&s, Complex&a); //流类作为形式参数必须是引用 }; ostream& operator<<(ostream&s, const Complex &z){ returns<<'('<<z.Real<<','<<z.Image<<')'; } 9.3.3 重载插入和提取运算符
9.3.3 重载插入和提取运算符 istream&operator>>(istream&s,Complex &a){ //格式为r,(r),(r,z) double re=0,im=0; char c=0; s>>c; if(c=='(') {//是否由括号开始 s>>re>>c; //实部 if(c==',') s>>im>>c; //虚部 if(c!=')') s.clear(ios::failbit); //漏了括号给一个操作失败标志 } else {//实数 s.putback(c); //无括号,返回一个字符到输入缓冲区 s>>re; } if(s) a=Complex(re,im); //当流s正常时,则复制 return s; } putback()声明: stream&istream::putback(char); 它将最后一次从输入流中取得的字符放回到输入流中。
9.3.3 重载插入和提取运算符 intmain(){ 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; }
0 1 2 3 4 5 6 7 8 … n-1 … 文件结束符 图9.2 C++把文件看作有序字节的流 9.4 文件的输入与输出 文件的基本概念: 文件指的是磁盘文件,分为两类:二进制文件和文本文件。文本文件也称ASCII码文件,以字符(character)为文件存取的最小信息单位;而二进制文件中存取的最小信息单位为字节(Byte)。 C++把每一个文件都看成一个有序的字节流,每一个文件以文件结束符(end of file marker)结束。
当打开一个文件时,该文件就和某个流关联起来了。对文件进行读写实际上受到一个文件定位指针(file position pointer)的控制。 输入流指针也称读指针,每一次提取操作将从读指针当前所指位置开始,每次提取操作自动将读指针向文件尾移动。 输出流指针也称写指针,每一次插入操作将从写指针当前位置开始,每次插入操作自动将写指针向文件尾移动。 9.4 文件的输入与输出 9.4.3 二进制文件的读写 9.4.1 文件的打开与关闭 9.4.2 文本文件的读写 *9.4.4 文件的随机访问
文件使用基本步骤: 1. 建立一个文件流对象; 2. 打开一个磁盘文件; 3. 对文件进行读写操作; 4. 关闭文件。 具体步骤,如下: 1.建立一个文件流对象,这对象又称为内部文件: ifstreamifile;//仅输入用 ofstreamofile;//仅输出用 fstreamiofile;//既输入又输出用 9.4.1 文件的打开与关闭
9.4.1 文件的打开与关闭 2.使用文件流对象的成员函数打开一个磁盘文件。这样文件流对象和磁盘文件之间就建立了联系。文件流中说明了3个打开文件的成员函数。 void ifstream::open(constchar*,int =ios::in, int=filebuf::openprot); voidofstream::open(const char *,int=ios::out,int=filebuf::openprot); void fstream::open(const char*,int=ios::in|ios::out, int=filebuf::openprot); 第1参数为要打开的磁盘文件名。 第2参数为打开方式,有输入(in),输出(out)等,打开方式在ios基类中定义为枚举类型。 第3参数为指定打开文件的保护方式,一般取默认。 本步骤的实例: iofile.open(“myfile.txt”, ios::in|ios::out);
9.4.1 文件的打开与关闭 ifstream、ofstream、fstream这3个文件流类都重载了一个带默认参数的构造函数,具有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=ios::in|ios::out,int=filebuf::openprot); 这样1,2两步也可以合成为一步: fstreamiofile(“myfile.txt”,ios::in|ios::out); 建议采用!
9.4.1 文件的打开与关闭 接着判断文件打开是否成功,若成功,则文件流对象值非零,不成功则为0(NULL)。文件流对象值物理上就是指它的地址。 打开一个文件的完整程序实例: fstreamiofile(“myfile.txt”,ios::in|ios::out); if(!iofile) //“!”为重载的运算符,见9.3.1节 { cout<<“不能打开文件:”<<“myfile,txt”<<endl; return -1; //失败,退回操作系统 }
9.4.1 文件的打开与关闭 3.使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写,这在下一节中讨论。 4.关闭文件。3个文件流类各有一个关闭文件的成员函数 : void ifstream::close(); voidofstream::close(); voidfstream::close(); 使用很方便,如: iofile.close();
关闭文件的过程: 系统把该文件相关联的文件缓冲区中的数据写到文件中,保证文件的完整,收回与该文件相关的内存空间,供再分配; 然后把磁盘文件名与文件流对象之间的关联断开,以防止对磁盘文件的误操作。再次使用文件时必须重新打开。 注意事项: 关闭文件并没有取消文件流对象,该流对象仍可与其他磁盘文件建立联系。 文件流对象在程序结束时,或它的生命期结束时,由析构函数撤消,同时释放内部分配的预留缓冲区。 9.4.1 文件的打开与关闭
9.4.2 文本文件的读写 文本文件的顺序读写: 顺序读写可用C++的提取运算符(>>)和插入运算符(<<)进行。 【例9.7】复制文件。 【例9.8】按行复制文本文件。 【例9.9】文本式数据文件的创建与读取数据。
【例9.7】复制文件,逐字符复制 int main() { charch; ifstreamsfile("d:\\Ex9_6\\Ex9_6.cpp"); ofstreamdfile("e:\\Ex9_6.cpp"); //只创建文件,不建立子目录 if(!sfile) { cout<<"不能打开源文件:"<<"d:\\Ex9_6\\Ex9_6.cpp"<<endl; return -1;} if(!dfile){ cout<<"不能打开目标文件:"<<"e:\\Ex9_6.cpp"<<endl; return -1;} sfile.unsetf(ios::skipws); //关键!把跳过空格控制位复位为0,即不//跳过空格,否则空格全部未拷 while (sfile>>ch) dfile<<ch; sfile.close(); //如没有这两个关闭函数,析构函数也可关闭 dfile.close(); return 0; } 9.4.2 文本文件的读写
注意事项: 1. 必须关闭“跳过空白”字ios::skipws,因为提取(“>>”)符在默认情况下是跳过空白(包括空格,制表符,回车等)的,这样复制的文件会缺少一些字符。 2. while( sfile>>ch)语句能确定文件是否复制结束。流类成员函数和运算符全是返回本类型的引用,这里就是流文件对象自身,当文件结束时,返回NULL,这时不再复制,退出循环。 3. 复制是按字节进行的,效率很低,按字节传递开销极大,但该程序能正确复制任意类型的文件,不仅对文本文件(看作按字符),二进制文件(看作按字节)也一样。如果是文本文件,可以按行进行复制。 4. !sfile中的!是重载的运算符,在状态函数中重载,当该操作出现不正常状态,返回true。 9.4.2 文本文件的读写
【例9.8】按行复制文本文件 int main(){ char filename[256],buf[100]; fstreamsfile,dfile; cout<<"输入源文件路径名:"<<endl; cin>>filename; 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; } 9.4.2 文本文件的读写
课本:while(sfile.getline(buf,100),sfile.eof()!=1)有误,少执行1次循环!课本:while(sfile.getline(buf,100),sfile.eof()!=1)有误,少执行1次循环! while(sfile.eof()!=1) { sfile.getline(buf,100);//按行复制, A if (sfile.gcount()<100) dfile<<buf<<‘\n’; //因回车符未送到,B else{dfile<<buf; //=99个字符,还未读到回车符,故不加'\n‘ sfile.clear(0); }//状态字被置为0x02,必须清0。 } sfile.close(); dfile.close(); return 0; } 9.4.2 文本文件的读写 课本:sfile.rdstate==0,有误,即便未读到回车换行符,rdstate也可能不为零,使得读取中断! A行中sfile.getline(buf,100)从源文件读一行字符,或读99个字符,效率大大提高。B行中,因getline()从源文件读到行结束(回车换行)符而停止,但回车换行符(’\n’)并不放在buf中,因此要加一个回车换行符。此程序只能用于文本文件。
【例9.9】文本文件的创建与读取数据 典型的C++数据存入文件和由文件获得数据的方法是把对象存入文件和由文件重构对象。本例重载了提取符“>>”完成对象重构,重载了插入运算符“<<”完成对象存入文件。 class inventory{ string Description; string No; intQuantity; double Cost; double Retail; public: inventory(string="#",string="0",int=0,double=0,double=0); friend ostream& operator<<(ostream&dist,inventory&iv); friendistream& operator>>(istream&sour,inventory&iv); }; //流类作为形式参数必须是引用 9.4.2 文本文件的读写
9.4.2 文本文件的读写 inventory::inventory(string des,stringno,intquan, 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; returndist; }//写入文件是自动把数转为数字串后写入 istream&operator>>(istream&sour,inventory&iv){ sour>>iv.Description>>iv.No>>iv.Quantity >>iv.Cost>>iv.Retail; return sour; }//从文件读出是自动把数字串转为数读出 资源获取是由构造函数实现,而资源释放是由析构函数完成。同样可以把由文件重构对象放在构造函数中,把对象存入文件则放在析构函数中。将在9.6节介绍。
intmain(){ inventory car1("夏利2000","805637928",156,80000,105000),car2; inventory motor1("金城25","93612575",302,10000,13000),motor2; ofstreamdistfile("d:\\Ex9_9.data"); distfile<<car1<<motor1; distfile.close(); cout<<car1; cout<<motor1; cout<<car2; cout<<motor2; ifstreamsourfile("d:\\Ex9_9.data"); //分两次打开,可避免读文件时,误改了源文件 sourfile>>car2>>motor2; sourfile.close(); cout<<car2; cout<<motor2; return 0; } 9.4.2 文本文件的读写
对二进制文件进行读写的成员函数: istream&istream::read(char *,int); //从二进制流提取 istream&istream::read(unsigned char*,int); istream&istream::read(signed char *,int); //第一个参数指定存放有效输入的变量地址; //第二个参数指定提取的字节数; //函数从输入流提取指定数量的字节送到指定存储单元 ostream&ostream::write(const char *,int);//向二进制流插入 ostream&ostream::write(const unsigned char *,int); ostream&ostream::write(const signed char *,int); //第一个参数指定输出对象的内存地址, //第二个参数指定插入的字节数, //函数从该地址开始将指定数量的字节插入输出流 9.4.3 二进制文件的读写
【例9.10】创建二进制数据文件及文件的读取 类inventory与【例9.9】的基本一样,只是用二进制文件的Bdatatofile()和Bdatafromfile()取代了文本文件的插入符<<和提取符>>。 void inventory::Bdatafromfile(ifstream&sour){ chark[20]; sour.read(k,20);//提取20个字符到k中 Description=k; sour.read(k,10);//提取10个字符到k中 No=k; sour.read((char*)&Quantity,sizeof(int));//取地址强制转换 sour.read((char *)&Cost,sizeof(double)); sour.read((char *)&Retail,sizeof(double)); } //由此可见读和写是完全对称的过程,次序决不能错 9.4.3 二进制文件的读写
9.4.3 二进制文件的读写 void inventory::Bdatatofile(ofstream&dist){ dist.write(Description.c_str(),20); 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("金城15","93612575",302,10000,13000),motor2; ofstreamddatafile("d:\\Ex9_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; c_str()为string类的成员函数, 实现将string类字符串转换成char*
9.4.3 二进制文件的读写 ddatafile.close(); ifstreamsdatafile("d:\\Ex9_10.data",ios::in|ios::binary); //重新打开文件,从头读取数据 car2.Bdatafromfile(sdatafile); //从文件读取数据复制到car2 if(sdatafile.eof()==0) cout<<"读文件成功"<<endl; cout<<"对象car2:"<<endl; cout<<car2; motor2.Bdatafromfile(sdatafile); //继续从文件读取数据复制到对象motor2 if(sdatafile.eof()==0) cout<<"读文件成功"<<endl; cout<<"对象motor2:"<<endl; cout<<motor2; sdatafile.close(); return 0; }
9.4.3 二进制文件的读写 二进制文件特点: 1. 可以控制字节长度,读写数据时不会出现二义性,可靠性高。同时不知数据格式无法解析数据,保密性好。 2. 读函数并不知道文件是否结束,需用ios::eof()自行判断文件是否结束,若为1则系统不会再读。 3. 如果写完数据后没有关闭文件,直接开始读,则必须把文件定位指针移到文件头。如关闭文件后重新打开,文件定位指针就在文件头。