700 likes | 797 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 文本文件的读写 classinventory; ostream& operator<<(ostream &, inventory &); 对于VC++6.0编译器,这种声明必不可少!否则编译器报错友元运算符定义中inventory 的私有数据不可访问。
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 文本文件的读写
9.4.3 二进制文件的读写 对二进制文件进行读写的成员函数: 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.10】创建二进制数据文件及文件的读取 用Bdatatofile()和Bdatafromfile()取代了文本文件的插入符<<和提取符>>,实现二进制数据的存与取。 9.4.3 二进制文件的读写 class inventory{ string Description, No; intQuantity; double Cost; double Retail; public: inventory(string=“#”,string=“#”,int=0,double=0,double=0); friend ostream& operator<<(ostream&dist,inventory&iv); void Bdatatofile(ofstream&dist);//文件流作形参须引用 void Bdatafromfile(ifstream&sour); }; classinventory; ostream& operator<<(ostream &, inventory &); 对于VC++6.0编译器,这种声明必不可少!否则编译器报错友元运算符定义中inventory 的私有数据不可访问。
9.4.3 二进制文件的读写 voidinventory::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)); } //参见写过程,读写完全对称,次序决不能错
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; 9.4.3 二进制文件的读写 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. 如果写完数据后没有关闭文件,直接开始读,则必须把文件定位指针移到文件头。如关闭文件后重新打开,文件定位指针就在文件头。
9.6 文件与对象 对象与文件的关系: 在面向对象的程序设计中,对象的数据成员是数据的临时载体,而对象的操作是数据处理的手段,这些数据只有导入文件才能永久保存。要想再次使用这些数据,必须由这一文件重新创建对象,由对象的操作完成数据处理。最终要把这些处理过的数据重新保存到文件中。 具体实施:创建对象:在构造函数中打开文件,并读取数据来初始化对象的成员数据; 撤销对象:在析构函数中打开文件,将数据保存到文件中。当撤销对象时,文件流相关资源被自动释放。
9.6 文件与对象—例9.13 介绍一个标准的面向对象的程序设计框架,也是对前面各章内容的总结。(注意:本例使用了多重的插入运算符重载) 【例9.13】定义数组类Array,可存放若干货物Inventory对象。Array类的对象数组动态建立,初始为2个元素,不够用时扩充一倍。用文本数据文件建立数组元素对象,要求放在构造函数中,而数据的保存放在析构函数中。首次程序运行时,建立空的数据文件,由键盘输入建立的数组元素对象,并写入文件,程序退出时,关闭文件;下一次运行由该文件构造对象,恢复前一次做过的工作。
9.6 文件与对象—例9.13 class inventory{ string Description; //商品名称 string No; //货号 int Quantity; //数量 double Cost; //价格 double Retail; //零售 public: inventory (string ="#",string="#",int =0,double =0,double =0); friendostream & operator<<(ostream&dist,inventory&iv);//存对象 friendistream & operator>>(istream&sour,inventory&iv); //建对象 bool operator == (inventory &); //货号为关键字 booloperator <= (inventory &); }; inventory::inventory(string des,string no, intquan,doublecost,double ret){ Description=des; No=no; Quantity=quan; Cost=cost; Retail=ret;}
9.6 文件与对象—例9.13 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; }
9.6 文件与对象—例9.13 istream &operator >> (istream &sour, inventory &iv){ if ( sour == cin) { cout<<"请输入货物名称:"<<endl; sour>>iv.Description; cout<<"请输入货号:"<<endl; sour>>iv.No; cout<<"请输入货物数量:"<<endl; sour>>iv.Quantity; cout<<"请输入货物价格:"<<endl; sour>>iv.Cost; cout<<"请输入货物零售价格:"<<endl; sour>>iv.Retail; } elsesour>>iv.Description>>iv.No>>iv.Quantity >>iv.Cost>>iv.Retail; return sour; }
9.6 文件与对象—例9.13 bool inventory::operator == (inventory & inven) { return No == inven.No; } bool inventory::operator <= (inventory & inven) { return No <= inven.No; } template < typename T > class Array{ T *elements; int Subscript; //已用最大下标值 intmaxSize; fstreamdatafile; //文件流对象成员,聚合 public: Array(int=2); //默认元素数为2 ~Array(); boolIsFull() const { return Subscript == maxSize-1;} void renews(); //内存扩大一倍 void show(){ cout<<“已用最大下标值”<<Subscript<<‘\t’ <<“数组空间 "<<maxSize<<endl; } voidordinsert(T&); //输入时以货号为关键字升序排序 friendostream & operator << (ostream &dist, Array<T>&ar); //双重重载 };
9.6 文件与对象—例9.13 P337说法有误! 注:open打开文件,若不存在则创建,但若子目录(路径)不存在,则创建失败。当前目录始终存在,不存在的文件可以创建! template <typename T> Array<T>::Array ( intmaxs ){ maxSize=maxs; Subscript=-1; //私有数据初始化 T temp; elements = new T[maxSize]; datafile.open(“mydatafile.txt”,ios::in); //在当前目录下打开文件 if( !datafile==0 ) {//打开文件成功,等价于datafile!=0,datafile while ( !datafile.eof() ){ datafile>>temp; //读文件,初始化对象 if (datafile.eof()==0) { //读到文件结束,读入不成功,eofbit为1 ordinsert(temp); //将temp对象升序插入动态数组中 } } datafile.close(); //关闭打开的文件,eofbit会清零 } else datafile.clear(0); //若文件打开失败,流无法恢复 } P338说法不准确! 注:这个语句只针对文件打开失败的情况,若文件读到结束,则随着文件的关闭,eofbit会清零,即文件指针返回文件头!
9.6 文件与对象—例9.13 template <typename T> Array<T>::~Array(){ int i; datafile.open("mydatafile.txt",ios::out); for(i=0;i<=Subscript;i++) datafile<<elements[i];//保存数据 datafile.close(); delete []elements; } template <typename T> void Array<T>::renews(){ int i; T *temp=elements; maxSize*=2; elements=new T[maxSize];//指向新的动态数组 for(i=0;i<=Subscript;i++) elements[i]=temp[i]; delete[]temp;//释放原先的动态数组,小心内存泄露 }
9.6 文件与对象—例9.13 template<typename T> void Array<T>::ordinsert(T & elem){ int i, j; //输入时以货号为关键字排序 if(IsFull()) renews();//数组扩容 for(i=0;i<=Subscript;i++) if(elem<=elements[i]) break;//定位i if(!(elem==elements[i])){//elem与i元素不重复 Subscript++; for(j=Subscript;j>=i;j--) elements[j+1]=elements[j]; }//依次后移 elements[i]=elem;//插入 show(); } template<typenameT>ostream & operator<< (ostream&dist, Array<T>&ar){ //双重重载的插入运算符 int i; for(i=0;i<=ar.Subscript;i++) cout<<ar.elements[i]; //重载的inventory对象插入运算符 returndist; } 函数模板中以类模板作为参数,VC6.0、BCC5、等都不支持,编译出错。 解决办法:在VC 2005及以上版本下运行;或将该插入符改为成员函数void Array<T>::Output(),由它调用inventory的重载插入符;
intmain(){ Array<inventory> mylist; inventory temp; charch; cout<<"是否输入新商品?Y or N"<<endl; cin>>ch; while(ch=='Y'||ch=='y'){ cin.get(); //吸收回车 cin>>temp; mylist.ordinsert(temp); cout<<"是否继续输入?Y or N"<<endl; cin>>ch;} cout<<mylist; //mylist.Output(); return 0;//mylist析构时,其数组元素保存到文件中 } 9.6 文件与对象—例9.13
Unit 9 —I/O流类库和异常处理 第十章 异常处理 10.1 异常的概念 10.2 异常处理的机制 10.5 异常和继承 10.3 栈展开与 异常捕获 10.6 异常规范(选读) 10.7 C++标准库异常类 层次结构 (选读) 10.4 异常的重新抛出 和catch_all子句(选读)
10.1 异常的概念 异常概念: 异常(exception)是程序中可能检测到的,运行时的不正常情况,如存储空间耗尽、数组越界、被0除等等。 三点说明: • 1. 异常是可以预见的,即可以预测在什么地方,发什么类型的异常,尽管无法确知怎样发生和何时发生。 • 2. 在可能发生错误的地方,添加相关代码,一旦错误发生,则停止发生错误的操作,回到调用操作的起点,对它进行处理,程序不用整个停止。 • 3. 大型软件开发必须进行异常处理。
10.1 异常的概念 如何处理异常? 首先,C++提供了一些内置的语言来产生(raise)或抛出(throw)异常,用以通知“异常已经发生”; 然后,由预先安排的程序段来捕获(catch)异常,并对它进行处理。
10.2 异常处理的机制 回忆【例7.6 】栈类,其压栈和出栈成员函数: template <typename T>void Stack<T>::Push(constT&data){ assert(!IsFull()) ;//假定未满,若栈满则退出程序 elements[++top]=data; } template<typename T>T Stack<T>::Pop(){ • assert(!IsEmpty());//出栈时栈空则退出程序 return elements[top--]; • } • assert()语句测到栈满或栈空就退出程序
异常与异常抛出: C++中异常以类来实现,例如: template <typename T>classpopOnEmpty{...}; //栈空异常 template <typename T>classpushOnFull{...}; //栈满异常 10.2 异常处理的机制 • 测到栈满或栈空就抛出异常,而不是退出程序。 template <typename T>void Stack<T>::Push(constT&data){ if(IsFull()) throwpushOnFull<T>(data); //注意加了括号,构造一无名对象 elements[++top]=data; } template<typename T>T Stack<T>::Pop(){ if(IsEmpty()) throwpopOnEmpty<T>(); return elements[top--]; }
10.2 异常处理的机制 说明: • 1. C++要求抛出的必须是对象,是由调用异常类pushOnFull类和popOnEmpty类的构造函数所建立的无名对象。 2. throw表达式抛出异常为异常处理的第一步。接着先捕获(catch)异常,并处理。在堆栈的压栈和出栈操作中发生错误而抛出的异常,理所当然地应由调用了压栈出栈操作的程序来处理。 3. 异常并非总是类对象,throw表达式也可以抛出任何类型的对象,如枚举、整数等等。但最常用的是类对象。