Unit 9 — I/O 流 类库 和 异 常处理

33
Unit 9 — I/O 流流流流流流流流 C++ 流流流流流流流流 I/O 流流流 “流”流流流流流 流流流流流流 流 一一 流流流 流流流流流流流流流流流流 / 流流流流流流 / 流流流流流流流9.1 C++ 流流流流流 9.4 流流流流流 / 流流 9.3 流流流流流流流 / 流流 *9.5 流流流流 *9.2 流流 / 流流流流流流流 流流流 流流流流流流 / 流流 9.6 流流流流流

description

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. - PowerPoint PPT Presentation

Transcript of Unit 9 — I/O 流 类库 和 异 常处理

Page 1: Unit  9  — I/O 流 类库 和 异 常处理

Unit 9 — I/O 流类库和异常处理

  C++ 的标准库中包含了一个 I/O 流类库,“流”是对数据从一个对象到另一个对象的传送过程的抽象,数据的输入 / 输出通过输入 / 输出流来实现的。

9.1 C++ 的基本流类体系 9.4 文件的输入 / 输出

9.3 标准设备的输出 / 输出

*9.5 字符串流 *9.2 输入 / 输出的控制格式

第九章 流类库与输入 / 输出

9.6 文件与对象

Page 2: Unit  9  — I/O 流 类库 和 异 常处理

流类体系:

basic_ios basic_streambuf

basic_istream

basic_ostream

basic_ifstream

basic_iostream

basic_ofstream

basic_fstream

指针

9.1 C++ 的基本流类体系

流类模板的层次并非指的模板之间的继承关系,而旨在表明类模板实例的继承关系。

提供对流进行格式化输入输出和错误处理的操作。 提供完成提取(输入)操作

的成员函数。

提供完成插入(输出)操作的成员函数。

仅为前两者的聚合,未增加成员。

提供输入文件相关操作 提供输出文件相关操作

独立的类模板, basic_ios 包含一个保护型 basic_streambuf 指针成员(聚合), 用于管理一个流的缓冲区。

所有标准 I/O 流类模板在头文件 <iostream> 中说明,包含头文件<ios> 、 <streambuf> 、 <istream> 和<ostream> 。而输入输出文件流则在头文件<fstream> 中说明。

Page 3: Unit  9  — I/O 流 类库 和 异 常处理

输出 /输出标准流对象: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> ,两者是独立的。

Page 4: Unit  9  — I/O 流 类库 和 异 常处理

对象提取运算符和插入运算符:9.1 C++ 的基本流类体系

1. 重载的提取运算符“ >>” 和插入运算符“ <<” ,执行字符序列的输入 / 输出操作。提取:指输入操作,从流中提取一个字符序列,如“ cin>>a;” 。插入:指输出操作,向流中插入一个字符序列,如“ cout<<a; ” 。2. cin 使用提取运算符; cout 、 cerr 和 clog 使用插入运算符。

文件的输入与输出:文件输入输出完成磁盘文件的读取和永久保存的功能。 Windows 下不同的 C++ 平台,都为文件功能作了扩充,在 VC++ 的 MFC 编程中采用了序列化( Serialization )。

Page 5: Unit  9  — I/O 流 类库 和 异 常处理

9.3 标准设备的输入 / 输出

*9.3.2 标准输入 / 输出成员函数

*9.3.1 提高标准输入 / 输出的健壮性

9.3.3 重载插入和提取运算符

Page 6: Unit  9  — I/O 流 类库 和 异 常处理

9.3.1 提高标准输入 / 输出的健壮性 标准设备输入使用要点:1. cin 为缓冲流。键盘输入的数据首先保存在缓冲区中,

当要提取时,系统将是从缓冲区中拿。如果键盘一次输入数据多于实际要提取的,则多余的数据会留在缓冲区,等着慢慢用;如果输入错了,必须在回车之前修改,一旦回车则数据传到缓冲区中。只有把输入缓冲区中的数据取完后,才要求输入新的数据。不可能用刷新来清除缓冲区,所以不能输错,也不能多输!

2. 输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字 state (枚举类型io_state )中对应位置位(置 1 ),程序继续。所以要提高健壮性,就必须在编程中加入对状态字 state 的判断。

Page 7: Unit  9  — I/O 流 类库 和 异 常处理

9.3.1 提高标准输入 / 输出的健壮性 标准设备输入使用要点:3. 空格和回车都可以作为数据之间的分格符,所以多个数

据可以在一行输入,也可以分行输入。但如果是字符型和字符串,则空格( ASCII 码为 32 )无法用 cin 输入,字符串中也不能有空格。回车符也无法读入。

4. 输入数据以后再输入字符或字符串:如果数后直接加回车,应该用 cin.get() 提取回车。如果还有空格,则要清空缓冲区。

Page 8: Unit  9  — I/O 流 类库 和 异 常处理

9.3.3 重载插入和提取运算符 重载插入和提取运算符:在用户定义类中,将重载的运算符说明为其友元函数:friend istream& operator>>(istream&,className&);friend ostream& operator<<(ostream&,className&);

说明:1. 函数的返回值是对输入或输出流的引用,保证在 cin

和 cout 中可以连续使用“ >>” 或“ <<” 运算符。2. 第 1 个参数是输入或输出流的引用,作为“ >>” 或

“ <<” 的左操作数;第 2 个参数为用户定义类的引用,作为右操作数。

3. 流用作函数参数,必须是引用调用,不能是传值调用。因为要处理的是流本身,而不是其副本。【作业 H8_2】重载插入运算符“ <<”。

【例 9.6】用户定义的复数类 Complex的输入与输出。

Page 9: Unit  9  — I/O 流 类库 和 异 常处理

9.3.3 重载插入和提取运算符 重载【作业 H8_2】的插入运算符“ <<” :重载插入运算符“ <<”声明为 Scholar 类的友元函数:friend ostream & operator<<(ostream & ,const mystring &);

// 流类作为形式参数必须是引用重载插入运算符“ <<” 定义:ostream & operator<<(ostream & o,const Scholar & s){

return o<<s.Name<<'\t'; }

Orderedlist 类的打印函数简化为:template <typename T,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;}

输出格式统一,无论对于基本数据类型,还是用户定义类型均通用。

Page 10: Unit  9  — I/O 流 类库 和 异 常处理

9.3.3 重载插入和提取运算符 【例 9.6】用户定义复数类 Complex 的输入 / 输出:#include<iostream>using namespace std;class Complex{ double Real, Image;public: Complex ( double r=0.0, double i=0.0):Real(r),Image(i) {} // 这里省略若干成员函数 , 详见【例 4.7】 friend ostream&operator<<(ostream&s, const Complex&z); friend istream &operator>>(istream&s, Complex&a); // 流类作为形式参数必须是引用};

ostream& operator<<(ostream&s, const Complex &z){

return s<<'('<<z.Real<<','<<z.Image<<')';

}

Page 11: Unit  9  — I/O 流 类库 和 异 常处理

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);它将最后一次从输入流中取得的字符放回到输入流中。

Page 12: Unit  9  — I/O 流 类库 和 异 常处理

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;

}

9.3.3 重载插入和提取运算符

Page 13: Unit  9  — I/O 流 类库 和 异 常处理

9.4 文件的输入与输出

文件指的是磁盘文件,分为两类:二进制文件和文本文 件 。 文 本 文 件 也称 ASCII 码 文 件 , 以 字 符( character )为文件存取的最小信息单位;而二进制文件中存取的最小信息单位为字节( Byte )。

C++ 把每一个文件都看成一个有序的字节流,每一个文件以文件结束符( end of file marker )结束。

0 1 2 43 65 7 8 … n-1

… 文件结束符

图 9.2 C++ 把文件看作有序字节的流

文件的基本概念:

Page 14: Unit  9  — I/O 流 类库 和 异 常处理

9.4 文件的输入与输出 当打开一个文件时,该文件就和某个流关联起来了。对文件进行读写实际上受到一个文件定位指针( file position pointer )的控制。 输入流指针也称读指针,每一次提取操作将从读指针当前所指位置开始,每次提取操作自动将读指针向文件尾移动。 输出流指针也称写指针,每一次插入操作将从写指针当前位置开始,每次插入操作自动将写指针向文件尾移动。9.4.1 文件的打开与关闭

9.4.2 文本文件的读写

9.4.3 二进制文件的读写

*9.4.4 文件的随机访问

Page 15: Unit  9  — I/O 流 类库 和 异 常处理

9.4.1  文件的打开与关闭 文件使用基本步骤:1. 建立一个文件流对象;2. 打开一个磁盘文件;3. 对文件进行读写操作;4. 关闭文件。

具体步骤,如下:1 .建立一个文件流对象,这对象又称为内部文件:ifstream ifile ; // 仅输入用ofstream ofile ; // 仅输出用fstream iofile ; //既输入又输出用

Page 16: Unit  9  — I/O 流 类库 和 异 常处理

9.4.1  文件的打开与关闭 2 .使用文件流对象的成员函数打开一个磁盘文件。这样文件流对象和磁盘文件之间就建立了联系。文件流中说明了 3个打开文件的成员函数。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=ios::in|ios::out, int=filebuf::openprot);

第 1 参数为要打开的磁盘文件名。第 2 参数为打开方式,有输入( in ),输出( out )等,打开方式在 ios 基类中定义为枚举类型。第 3 参数为指定打开文件的保护方式,一般取默认。

本步骤的实例:iofile.open(“myfile.txt”, ios::in|ios::out);

Page 17: Unit  9  — I/O 流 类库 和 异 常处理

9.4.1  文件的打开与关闭

ifstream 、 ofstream 、 fstream 这 3 个文件流类都重载了一个带默认参数的构造函数,具有 open 函数的功能:ifstream::ifstream(const char*,int=ios::in,int=filebuf::openprot);ofstream::ofstream(const char*,int=ios::out, int=filebuf::openprot);fstream::fstream(const char*,int=ios::in|ios::out,int=filebuf::openprot);

这样 1 , 2 两步也可以合成为一步:fstream iofile(“myfile.txt”,ios::in|ios::out);建议采用!

Page 18: Unit  9  — I/O 流 类库 和 异 常处理

9.4.1  文件的打开与关闭

 接着判断文件打开是否成功,若成功,则文件流对象值非零,不成功则为 0 ( NULL )。文件流对象值物理上就是指它的地址。打开一个文件的完整程序实例:fstream iofile(“myfile.txt”,ios::in|ios::out);if(!iofile) //“ !”为重载的运算符,见 9.3.1节 { cout<<“ 不 能 打 开 文件 :”<<“myfile,txt”<<endl; return -1; //失败,退回操作系统 }

Page 19: Unit  9  — I/O 流 类库 和 异 常处理

9.4.1  文件的打开与关闭

3.使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写,这在下一节中讨论。4.关闭文件。 3 个文件流类各有一个关闭文件的成员函数 :void ifstream::close();void ofstream::close();void fstream::close();

使用很方便,如:iofile.close();

Page 20: Unit  9  — I/O 流 类库 和 异 常处理

9.4.1  文件的打开与关闭   关闭文件的过程:1. 系统把该文件相关联的文件缓冲区中的数据写到文

件中,保证文件的完整,收回与该文件相关的内存空间,供再分配;

2. 然后把磁盘文件名与文件流对象之间的关联断开,以防止对磁盘文件的误操作。再次使用文件时必须重新打开。

注意事项:3. 关闭文件并没有取消文件流对象,该流对象仍可与

其他磁盘文件建立联系。4. 文件流对象在程序结束时,或它的生命期结束时,

由析构函数撤消,同时释放内部分配的预留缓冲区。

Page 21: Unit  9  — I/O 流 类库 和 异 常处理

9.4.2  文本文件的读写

文本文件的顺序读写: 顺序读写可用 C++ 的提取运算符( >> )和插入运算符( << )进行。

【例9.7】复制文件。【例9.8】按行复制文本文件。【例9.9】文本式数据文件的创建与读取数据。

Page 22: Unit  9  — I/O 流 类库 和 异 常处理

9.4.2  文本文件的读写 【例 9.7】复制文件,逐字符复制 int main() { char ch; ifstream sfile("d:\\Ex9_6\\Ex9_6.cpp"); ofstream dfile("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; }

Page 23: Unit  9  — I/O 流 类库 和 异 常处理

9.4.2  文本文件的读写 注意事项:1. 必须关闭“跳过空白”字 ios::skipws ,因为提取(“ >>” )符在默认情况下是跳过空白(包括空格,制表符,回车等)的,这样复制的文件会缺少一些字符。2. while( sfile>>ch ) 语句能确定文件是否复制结束。流类成员函数和运算符全是返回本类型的引用,这里就是流文件对象自身,当文件结束时,返回 NULL ,这时不再复制,退出循环。3. 复制是按字节进行的,效率很低,按字节传递开销极大,但该程序能正确复制任意类型的文件,不仅对文本文件(看作按字符),二进制文件(看作按字节)也一样。如果是文本文件,可以按行进行复制。4. !sfile 中的!是重载的运算符,在状态函数中重载,当该操作出现不正常状态,返回 true 。

Page 24: Unit  9  — I/O 流 类库 和 异 常处理

【例 9.8】按行复制文本文件int main(){ char filename[256],buf[100]; fstream sfile,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  文本文件的读写

Page 25: Unit  9  — I/O 流 类库 和 异 常处理

9.4.2  文本文件的读写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;}A 行中 sfile.getline(buf,100) 从源文件读一行字符,或读 99个字符,效率大大提高。 B 行中,因 getline() 从源文件读到行结束(回车换行)符而停止,但回车换行符(’ \n’ )并不放在 buf 中,因此要加一个回车换行符。此程序只能用于文本文件。

课本:while(sfile.getline(buf,100),sfile.eof()!=1) 有误,少执行 1次循环!课本: sfile.rdstate==0 ,有误,即便未读到回车换行符, rdstate也可能不为零,使得读取中断!

Page 26: Unit  9  — I/O 流 类库 和 异 常处理

【例 9.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); }; // 流类作为形式参数必须是引用

9.4.2  文本文件的读写

Page 27: Unit  9  — I/O 流 类库 和 异 常处理

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; }// 从文件读出是自动把数字串转为数读出

9.4.2  文本文件的读写

资源获取是由构造函数实现,而资源释放是由析构函数完成。同样可以把由文件重构对象放在构造函数中,把对象存入文件则放在析构函数中。将在 9.6 节介绍。

Page 28: Unit  9  — I/O 流 类库 和 异 常处理

9.4.2  文本文件的读写int main(){ inventory car1("夏利2000","805637928",156,80000,105000),car2; inventory motor1("金城25","93612575",302,10000,13000),motor2; ofstream distfile("d:\\Ex9_9.data"); distfile<<car1<<motor1; distfile.close(); cout<<car1; cout<<motor1; cout<<car2; cout<<motor2; ifstream sourfile("d:\\Ex9_9.data"); // 分两次打开 , 可避免读文件时 , 误改了源文件 sourfile>>car2>>motor2; sourfile.close(); cout<<car2; cout<<motor2; return 0; }

Page 29: Unit  9  — I/O 流 类库 和 异 常处理

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);// 第一个参数指定输出对象的内存地址 ,// 第二个参数指定插入的字节数 ,// 函数从该地址开始将指定数量的字节插入输出流

Page 30: Unit  9  — I/O 流 类库 和 异 常处理

9.4.3  二进制文件的读写 【例 9.10】创建二进制数据文件及文件的读取类 inventory 与【例 9.9 】的基本一样,只是用二进制文件的 Bdatatofile() 和 Bdatafromfile() 取代了文本文件的插入符 << 和提取符 >> 。void inventory::Bdatafromfile(ifstream&sour){ char k[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));} // 由此可见读和写是完全对称的过程 , 次序决不能错

Page 31: Unit  9  — I/O 流 类库 和 异 常处理

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; ofstream ddatafile("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*

Page 32: Unit  9  — I/O 流 类库 和 异 常处理

9.4.3  二进制文件的读写 ddatafile.close(); ifstream sdatafile("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;}

Page 33: Unit  9  — I/O 流 类库 和 异 常处理

9.4.3  二进制文件的读写

二进制文件特点:1. 可以控制字节长度,读写数据时不会出现二义性,可靠性高。同时不知数据格式无法解析数据,保密性好。2. 读函数并不知道文件是否结束,需用 ios::eof() 自行判断文件是否结束,若为 1 则系统不会再读。3. 如果写完数据后没有关闭文件,直接开始读,则必须把文件定位指针移到文件头。如关闭文件后重新打开,文件定位指针就在文件头。