540 likes | 683 Views
第七章 C++ 的 I/O 流类库. 1. 7.1 C++ 为何建立自己的输入输出系统. C++ 除了完全支持 C 语言的输入输出系统外,还定义了一套面向对象的输入输出系统。为什么 C ++ 还要建立自己的输入输出系统呢? C 语言的输入输出系统不支持用户自定义的对象,如: struct my_struct{ int i; float f; char *str; }s; 对此结构类型 , 在 C 语言中下面的语句是不能接受的 : printf(“%my_struct”,s);
E N D
1 7.1 C++为何建立自己的输入输出系统 C++除了完全支持C 语言的输入输出系统外,还定义了一套面向对象的输入输出系统。为什么C ++还要建立自己的输入输出系统呢?C 语言的输入输出系统不支持用户自定义的对象,如: struct my_struct{ int i; float f; char *str; }s; 对此结构类型,在C语言中下面的语句是不能接受的: printf(“%my_struct”,s); 因为printf( )函数只能识别系统预定义的类型,而没有办法对新的数据类型进行扩充.。用C++的输入输出系统,就可以通过重载“<<”和“>>”运算符来解以上问题。 C++的类机制允它建立一个可扩展的输入输出系统,它可以通过修改和扩展来加入用户自定义类型及相应操作。
2 7.2 C++的流及流类库 7.2.1 C++的流 输入输出是一种数据传递操作,它可以看作字符序列在主机与外部介质 之间的流动。流(stream)为从源(或生产者)到目的(或消费者)的数据流的引用。流具有方向性: 与输入设备(如键盘)相联系的流称为输入流;与输出设备(如屏幕)相联系的流称为输出流;与输入输出设备(如磁盘)相联系的流称为输入输出流。 C++中包 含几个预定义的流 : 标准输入流 cin 与标准输入设备相关联 标准输出流 cout 与标准输出设备相关联 非缓冲型标准出错流 cerr 与标准错误输出设备相关联(非缓冲方式) 缓冲型的标准出错流 clog 与标准错误输出设备相关联(缓冲方式) 在缺省情况下,指定的标准输出设备是显示终端,标准输入设备是键盘。 在任何情况下(有时用户 把标准输出设备定向为其它设备),指定的标准错误输出设备总是显示终端。 cerr<<“The average cannot be computed.\n”;
3 7.2.2 流类库 C++流类库是用继承方法建立起来的一个输入输出类库。它具有两个平行的基类:streambuf 类,ios 类。所有其它的流类都是从它们直接或间接地派生出来的。使用C++的流类库,程序中可能应包含的头文件: iostream.h strstream.h fstream.h iomanip.h 1. streambuf 类 streambuf 派生 filebuf strstreambuf conbuf
4 ios 2. ios 类 istream fstreambase strstreambase ostream ifstream istrstream ofstream ostrstream constream ostream_withassign istream_withassign fstream strstream iostream iostream_withassign
5 7.3输入输出的格式控制 7.3.1 用ios 类的成员函数进行格式控制 ios类中有几个成员函数可以用来对输入输出进行格式控制。 主要控制 状态标志字、域 宽、填充字符及输出精度。 1. 状态标志字 状态标志存放在数据成员 long x_flags中。ios 类 public 中定义了一个枚举,它的每个成员可以分别定义状标志字的一个位,每一位都称为一个状标志位。
6 . 这个枚举 定义 如下: enum{ 0000000000000001 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,可用于输出 };
7 2 x_flags取以上枚举值的并存,即x_flags上的每一位(长整数为16位)中的0 1相当于一个枚举值。例如 x_flags 中放 0x0011 或 17即为 0000000000010001相当于 0x0001 与 0x0010 之并 skipws(0x0001) 0000 0000 0000 0001 dec (0x0010) + 0000 0000 0001 0000 x_flags (0x0011) 0000 0000 0001 0001
8 2. ios类中用于控制输入输出格式的成员函数 函数原型 功能 long ios::setf(long flags); 设置状态标志flags long ios::unsetf(long flags); 清除状态标志,并返回前标志 long ios::flags(); 测试状态标志 long ios::flags(long flags); 设置标志flags,并返回前标志 int ios::width(); 返回当前的宽度设置值 int ios::width(int w); 设置域宽w,返回以前的设置 int ios::precision(int p); 设置小数位数p返回以前的小数位数 char::iosfill(); 返回当前的填充字符 char::iosfill()char ch; 设置填充字符ch返回当前的填充字符
9 (1)设置状态标志 设置状态标志,即是将某一状态标志位置“1”,可使用setf()函数 其一般的调用格式为:流对象.setf(ios::状态标志); 例7.1 #include<iostream.h> main() { istream cin; //对象cin可不定义 ostream cout; //对象cout可不定义 cin.setf(ios::shipws); //键盘输入时跳过输入空白 cout.setf(ios::left); //设置输出左对齐 cout.setf(ios::showpos | ios::sciengific);//中间用运算符 “|”分隔 cout<<567<<“ ” <<567.89<<endl; } 设置showpos使得每个正数前添加“+”号, 设置sciengific使 浮点数按科学表示法 (指数形式)进行显示,输出结果: +567 +567.89e02 注意:要设置多项标志时,中间用或运算符 “|” 分隔。
10 (2)清除状态标志 清除某一状态标志,即是将某一状态标志位置”0”,可使用unseft()函数,使用时的调用格式与setf()相同。 (3)取状态标志 取一个状态标志,可使用 flags()函数。flags()函数有不带参数与带 参数两 种形式:ling ios ::flags(); 用于返回当前的状态 标志字; ling ios ::flags (liog flag); 返回当前的状态 标志字后,再者将状态标志字设置成flag(参数)。 flags(liog flag)函数与setf(liog flags)函数的差别在于:setf()函数是在原有的基础上追加设定的,而flags()函数是用 新设定覆盖以前的状态标志字。 例 7.2几个成员函数的使用 方法 #include <iostream.h> void showflags(long f) //输出状态标志字函数 { long i; for(i=0x8000;i;i=i>>1)//用右移方法使i中的值为”1”的位不断右移 if (i&f) cout<<”1”; //判断f中的某一位是否为”1” else cout <<”0”; cout<<endl; }
11 接1 例 7.2 void main() { long f; cout.setf(ios::unitbuf|ios::skipws); f=cout.flags(); //取当前状态标志字 showflags(f); //显示状态标志字 cout.setf(ios::showpos|ios::scientific); //追加状态 标志位 f=cout.flags(); showflags(f); cout.unsetf(ios::scientific); //从状态标志字中去掉sciengific f=cut.flags(); showflags(f); f=cout.flags(ios::oct); //重新设置状态标志字 showflags(f); //显示设置前的状态标志字 f=cout.flags(); //取设置 后的状态标志字 showflags(f); //显示设置后的状态标志字 } 程序运行结果为;0010000000000001 (1)0010110000000001 (2) 0010010000000001 (3)0000000000100000 (4)0000000000100000 (5)
12 (4)设置域宽 域宽主要用来控制输出,在ios 类中域 宽存放在数据成员int x_width中,设置域宽的成员函数有两个,其一般格式为:int ios ::width(); 返回当的域宽值 int ios ::width(int w); 设置域宽,并返回原来的域 宽 (5)设置输出的精度 精度用来控制浮点数的输出显示精度,在ios类中用数据成员int x_precision来存放精度,设置精度的成员函数的一般格式为: int ios::precision(int p); 重新设置浮点数所需小数的位数,并返回设置前的位数 (6)填充字符 填充字符的作用是:当输出值不满域宽时用填充字符来填充,缺省情况下填充字符为空格,所以在使用填充字符函数时,必须与width()函数相配合,否则就没有意义,在ios类中用数据成员x_fill来存放填充字符。填充字符的成员函数有两个,其一般形式为: char ios ::fill(); 返回当的填充字符 char ios ::fill(char ch); 用ch重新设置填充字符,并返回设置前的填充字符。
13 例 7.3 #include <iostream.h> void main() { cout<<”x_width=”<<cout.width()<<endl;cout<<”x_fill=”<<cout.fill()<<endl;cout<<”x_precision=”<<cout.precision()<<endl;cout<<123<<” “<<123.45678<<endl;cout<<”---------------------------------------\n”;cout<<”****x_width=10,x_fill= ,x_precision=4****\n”;cout.width(10);cout.precision(4);cout<<123 <<” “<<123.45678<<” “<<234.567<<endl;cout <<”x_width=”<<cout.width()<<endl;cout<<”x_fill=”<<cout.fill()<<endl;cout<<”x_precision=”<<cout.precision()<<endl;cout<<”---------------------------------------\n”;cout<<”****x_width=10,x_fill=&,x_precidion=4****\n”;
14 程序运行结果如下:x_width=0x_fill=x_precision=0123 123.45678------------------------------------- ****x_width=10,x_fill= ,x_preciosion=4**** 123 123.4568 234.567x_width=0x_fill=x_precision=4------------------------------------- ****x_width=10,x_fill=&,x_precision=4**** &&&&&&&123 123.4568123&&&&&&& 123.4568x_width=0x_fill=&x_precision=4 接1 例 7.3 cout.fill(‘&’);cout.width(10);cout<<123<<” “<<123.45678<<endl;cout.setf(ios::left);cout.width(10); cout<<123<<” “<<123.45678<<endl;cout<<”x_width=”<<cout.width()<<endl;cout<<”x_fill=”<<cout.fill()<<endl;cout<<”x_precision=“<<cout.precision()<<endl; }
这程序建立了如下的表x sqrt(x) x^2 1 1 1 2 1.4142 4 3 1.7321 9 4 2 16 5 2.2361 25 6 2.4495 36 7 2.6458 49 8 2.8284 64 9 3 8110 3.1623 100 11 3.3166 12112 3.4641 14413 3.6056 16914 3.7417 19615 3.873 22516 4 25617 4.1231 28918 4.2426 32419 4.3598 36120 4.4721 400 15 例7.4 利用控制输入输出格式的成员函数建立对齐的数字表 #include<iostream..h> #include<math.h> void main() { double x; cout,precision(4); cout<<” x sqrt(x) x^2\n\n”; for(x=1.0;x<=20.0;x++) { cout.width(8); cout<<x<<’ ‘; cout.width(8); cout<<sqrt(x)<<’ ‘; cout.width(8); cout<<x*x<<’\n’; } }
16 7.3.2使用操作符进行输入输出格式控制 C++提供了另一种进行I/O格式控制的方法,这一方法使用了一种称为操纵符(也称为操纵函数)的特殊函数。在很多情况下,使用操纵符进行格式化控制比用ios格式标志和成员函数要方便。 1. C++预定义的操纵符 C++预定义的操纵符是以一个流引用作为其参数,并返回同一流的引用,因此它可以嵌入到输入输出操作的链中。操纵符可用来改变域宽,使整型数以八进制或十六进制形式输入或输出,并设置填充字符等。许多操纵符的功能类似于上面介绍的ios类成员函数的功能。C++提供的预定义操纵符如下: ( 1 ) dec 以十进制形式输入或输出整型数,可用于输入或输出 。 ( 2 ) hex 以十六进制形式输入或输出整型数,可用于输入或输出。 ( 3 ) oct 以八进制形式输入或输出整型数,可用于输入或输出。 ( 4 ) ws 用于在输入时跳过开头的空白符,仅用于输入。 ( 5 ) endl 插入一个换行符并刷新输出流,仅用于输出。 ( 6 ) ends 插入一个空字符,通常用来结束一个字符串,仅用于输出。 ( 7 ) flush 刷新一个输出流,仅用于输出。 ( 8 ) setbase( int n )把转换基数设置为n(n的值为0,8,10,或16),n的缺省值为”0”,即以十进制形式输出。
17 ( 9 ) resetiosflags (long f ) 关闭由参数 f 指定的格式标志,可用于输入或输出。 ( 10 ) setiosflags (long f ) 设置由参数 f 指定的格式标志,可用于输入或输出。 ( 11 ) setfill (int c ) c 为填充字符,缺省时为空格,可用于输入或输出。 ( 12 ) setprecision (int n )设置数据小数部分的位数,缺省时小数的位数为6,可用输入输出。 ( 13 ) setw (int n) 设置域宽为n,可用于输入或输出。
18 操纵符setiosflags( )和resetiosflags ( )中所用的格式标志 格式标志名 含 义 ios :: left 输出数据按域宽左对齐输出 ios :: right 输出数据按域宽右对齐输出 ios ::scientific 使用科学计数法表示浮点数 ios ::fixed 使用定点形式表示浮点数 ios ::dec 转换基数为十进制形式 ios ::hex 转换基数为十六进制形式 ios ::oct 转换基数为八进制形式 ios ::uppercase 十六进制形式和科学计数法输出时,表示数值的字 符 一律为大写 ios ::showbase 输出带有一个表示制式的字符(如”X”表示十六进 制,”O”表示八进制) ios ::showpos 在正数前添加一个”+”号 ios ::showpoint 浮点输出时必须带有一个小数点 2.操纵符的使用 操纵符分为带参数的操纵符和不带参数的操纵符.通常,不带参数的操纵符在iostream.h 文件中定义,带参数的操纵符在 iomanip.h 文件中定义。
19 例7. 5 使用操纵符的输入输出格式控制 #include <iostream.h> #include <iomanip.h> void main ( ) { cout <<setw (10)<<12<<567<<endl; //① cout<<123<<setiosflags(ios::scientific)<<setw(20)<<123.456789<<endl; //② cout <<123<<setw(10)<<hex<<123<<endl; //③ cout <<123<<setw(10)<<oct(8)<<123<<endl; //④ cout <<123<<setw(10)<<setbase(0)<<123<<endl; //⑤ cout<<resetiosflags(ios::scientific)<<setprecision(4)<<123.456789<<endl;⑥ cout<<setiosflags(ios::scientific)<<setfill(‘#’)<<setw(8)<<123<<endl; // ⑦ cout<<resetiosflags(ios::scientific)<<setfill(‘$’)<<setw(8)<<456<<endl; // ⑧ } • 程序运行结果为: • 1234567 ① • 123 1.234567e+02 ② • 123 7b ③ • 7b 173 ④ • 173 123 ⑤ • 123.4568 ⑥ • 123##### ⑦ • $$$$$456 ⑧
20 例7.6 二次方表与二次方根表的程序的程序的另一个版本 #include<iostream.h> #include<iomanip.h> #include<math.h> void main ( ) { double x; cout<<setprecision (4); cout << “ x sqrt (x) x^2\n\n”; for (x=1.;x<=20.0;x++); { cout <<setw(8)<<x<<’’; cout <<setw(8)<<sqrt(x)<<’’; cout <<setw(8)<<x*x<<’n’; } }
21 3. 用户自定义的操纵符 C++除了提供此同预定义的操纵符外,也允许用户自定义操纵符,把程序中频繁使用的输入输出操作合并成一个操纵符。 若为输出流定义操纵符函数,则定义形式如下: ostream &manip_name(ostream &stream) { //自定义代码 return stream; } 若为输出流定义操纵符函数,则定义形式如下: istream &manip_name(istream &stream) { //自定义代码 return stream; }
22 例 7.7 自定义的操纵符 #include <iostream.h> #include<iomanip.h> ostream &output 1(ostream &stream) { stream.setf(ios::left); stream<<setw(10)<<hex<<setfill(‘&’); return stream; } void main( ) { cout<<123<<endl; cout<<output 1<<123<<endl; } 程序运行结果如下: 123 7b&&&&&&&&
23 例 7.8 #include<iostream.h> #include<iomanip.h> istream &inputl (istream &in) { cin>>hex; cout<< “Enter number using hex format:”; return in; } void main ( ) { int i; cin>>inputl>>i; cout <<endl; } 以上程序中定义了一个操纵符函数input1,该函数要求输入一个十六进制数。 程序运行后,屏幕上显示: Enter number using hex format: 提示用户输入一个十六进制数。
24 7.4用户自定义类型的输入输出 用户自定义的类型数据的输入或输出,可以通过重载运算符 “>>”和 “<<”来实现,使“>>”和 “<<”可以直接用来输入或输出类的对象。 7.4.1 重载输出运算符 “<<” 定义输出运算符”<<”重载函数的一般格式如下: ostream &operator <<(ostream &stream, class_name obj ) { //操作代码 return stream; } 函数中的一个参数是对ostream 对象的引用,它可以是其它任何合法的标示符,但必须与return后面的标示符相同。第二个参数接受将被输出的对象,其中class_name是类名,obj为该类的对象名。
25 例 7.9输出运算符 “<<”重载的例子 #include <iostream.h> class coord{ public: int x,y; coord ( ) {x=0; y=0;} coord ( int I,int j) {x=I; y=j;} }; ostream &operator <<(ostream &stream, coord ob) { stream <<ob.x<< “,”<<ob.y<<endl; return stream; } void main ( ) { coord a ( 5,6),b (10,34); cout <<a<<b; } 程序运行结果如下: 5,6 10,34
26 例 7.10 把重载输出运算符定义为类的友元函数,这样就可以访问类的私有成员 #include <iostream.h> class coord{ int x,y; public: coord ( ) {x=0; y=0;} coord ( int i,int j) {x=i; y=j;} friend ostream &operator<<(ostream &stream, coord ob); }; ostream &operator<<(ostream &stream, coord ob) { stream<<ob.x <<”,”<<ob.y<<endl; return stream; } void main ( ) { coord a ( 5,6),b (10,34); cout <<a<<b; } 程序运行结果如下: 5,6 10,34
27 7.4.2 重载输入运算符 “>>” 只是要把ostream换成istream ,把”<<”用”>>”代替,第二个参数是一个引用。 定义输入运算符”>>”重载函数的一般格式如下: istream &operator >>(istream &stream, class_name &obj ) { //操作代码 return stream; } 例 7.11重载输入运算符 “>>” #include<iostream.h> class three_d{ int x,y,z; public: three_d(int a, int b, int c) {x=a; y=b; z=c;} friend ostream &operator<<(ostream &output, three_d ob); friend istream &operator>>(istream &input, three_d &ob); };
28 接1 例 7.11 ostream &operator<<(ostream &output, three_d ob); { output<<ob.x<< “,”; output<<ob.y<< “,”; output<<ob.z<<endl; return output ; } istream &operator>>(istream &input, three_d &ob); { cout<<”Enter x,y,z value:”; input>>ob.x; input>>ob.y; input>>ob.z; return input; }
29 接2 例 7.11 程序运行结果如下: 10,20,30 Enter x,y,z value :40 50 60 40,50,60 void main ( ) { three_d obj (10,20,30); //定义类three_d的对象obj cout<<obj; //输出对象obj的成员值 cin>>obj; //输出对象obj的各成员值,将原值覆盖 cout<<obj; //输出对象obj的成员值(新值) }
30 7.5文件的输入输出 C++把文件看作字符序列,即文件是由一个字符数据顺序组成的。根据数据的组织形式,文件可分为文本文件和二进制文件。文本文件又称ASCII文件,它的每个字节存放一个ASCII代码,代表一个字符。二进制文件则是把内存中的数据,按其在内存中的存储形式原样写到磁盘上存放。文件是一个字符流或二进制流,它把数据看作是一连串的字符,而不考虑记录的界限,它对文件的 存取以字符为单位进行。我们把这种文件称为流式文件。在C++中,要进行文件的输入输出,必须首先创建一个流,然后将这个流与文件相关联,即打开文件,此时才能进行读写操作,完成后再关闭这个文件。这是C++中进行文件输入输出的基本过程。 7.5.1 文件的打开与关闭 1. 文件的打开 为了执行文件的输入输出,C++提供了三个流类。 名 称 基 类 功 能 ofstream 由ostream 派生 用于文件的输出 ifstream 由istream 派生 用于文件的输入 fstream 由iostream 派生 用于文件的输入或输出
31 建立流的过程就是定义流类的对象,例如: ifstream in; ofstream out; fstream both; 它们分别定义了输入流对象in,输出流对象out,输入输出流对象both。 Open( )函数是上述三个流类的成员函数,其原型为: void open(const unsigned char *,int mode , int access=filebuf::openprot); 打开语句: 流类的对象. open(const unsigned char *,int mode ,int access=filebuf::openprot); 其中第一个参数是用来传递文件名的;第二个参数mode的值决定文件将如何被打开,它必须取下面的值中的一个;access的值决定文件的访问方式。
32 ios::app 使输出追加到文件尾部 ios::ate 查找文件尾 ios::in 打开一个文件进行读操作 ios::nocreate 文件不存在时,导致open( )失败 ios::noreplace 若文件存在,则open( )失败 ios::out 打开一个文件进行写操作 ios::trunc 使同名文件被删除 ios::binary 文件以二进制方式打开,缺省时为文本文件 访问方式 0 普通文件 1 只读文件 2 隐含文件 4 系统文件 8 备份文件
33 打开文件的步骤: (1) 定义一个类的对象,例如: ofstream out; (2) 使用open( )函数打开文件。例如:out.open(”test”,ios::out ,0); 文件只有在打开后,才能对文件进行读写操作。由于某些原因,可能打开失败,因此使用文件之前必须进行执行检测,以确认打开一个文件是否成功。可以使用类似下面的方法进行检测: if(! mystream) { cout<<”cannot open file! \n”; //错误处理代码 } 在实际编程时,打开一个文件的最常见的形式为: ofstream out (”test”) ; 用 if (!out) 检测是否打开成功 它相当于语句: ofstream out; out.open(”test”); 2. 文件的关闭 关闭文件可使用close( )函数完成,close函数也是流类中的成员函数,它不带参数,没有返回值,例如: out.close ( );
34 7.5.2 文件的读写 在含有文件操作的程序中,必须有如下的预处理命令: # include <fstream.h> 1. 文本文件的读写 文件打开后,文本文件的读写使用运算符“<<”与“>>”,只是必须用与文件相连接的流代替cin和cout。 例 7.12 把一个整数、一个浮点数和一个字符串写到磁盘文件test中。 # include <fstream.h> int main () { ofstream fout(”test”); if (!fout) { cout<<”cannot open output file \n”; return 1; } fout<<10<<” ”<<123.456<<” \”This is a test file.\”\n”; fout.close(); return 0; }用“写字板”可以看到test文件的内容:10 123.456 “this is a test file”
35 例 7.13 先建立一个输出文件,向它写入数据,然后关闭文件,再按输入模式打开它,并读取信息。 # include <fstream.h> void main () { ofstream fout(”test2”); if(!fout) { cout<<”cannot open output file .\n”; return 1; } fout<<” Hello!\n”; fout<<100<<’ ’<<hex<<100<<endl; fout.close (); ifstream fin(”test2”); if(!fin) { cout<<”cannot open input file .\n”; return 1; } char str[80]; int I; fin>>str>>i; cout<<str<<” ”<<i<<endl; fin.close(); } 程序建立一个输出文件test2,并向它写入数据,关闭输出文件test2。再将文件test2按输入模式打开,并将字符串“hello!\n”读给字符数组str,将整数100读给整型变量i。最后在屏幕上显示出str和I的值,如下所示: Hello! 100
36 例 7.14 从键盘读入字符串,并将它们写进磁盘文件。当用户输入空白字符时,程序停止。 # include <fstream.h> # include <stdio.h> void main(int argc,char *argv[]) { if(argc!=2) { cout<<”Usage: write <filename>\n”; return 1; } ofstream outf (argv[1]); if (! outf) { cout<<”cannot open output file .\n”; return 1; } char str[60]; cout<<”Write strings to disk, RETURN to stop\n”; do { cout<<”:”; gets(str); outf<<str<<endl; }while (*str); outf.close (); } 这个例子采用了命令行参数的形式,输入以下命令行: <可执行文件名> <磁盘文件名> ↙ 程序运行情况如下: Write strings to disk,RETURN to stop :abcdefg (从键盘输入一个字符串) :↙ (输入空行,程序停止) 程序运行后,在磁盘上生成一个名为<磁盘文件名>的文件,其内容: abcdefg
37 2. 二进制文件的读写 任何文件,无论它包含格式化的文本还是包含原始数据,都能以文本方式或二进制方式打开。文本文件是字符流,而二进制文件是字节流。 (1) 用get( )函数和put( )函数读写二进制文件 get( )是输入流类istream中定义的成员函数,它可以从与流对象连接的文件中读出数据,每次读出一个字节(字符)。put( )是输出流类ostream中的成员函数,它可以向与流对象连接的文件中写入数据,每次写入一个字节(字符)。 get( )函数有许多格式,其最一般的使用版本的原型如下: istream &get(unsigned char &ch); get( )函数从相关流中只读一个字符,并把该值放如引用ch&中。 put( )函数的原型如下: ostream &put(char ch); put( )函数将字符ch写入流中。
38 例 7.15 实现任意类型的文件拷贝。 # include <iostream.h> # include <fstream.h> void main (int argc, char *argv[ ]) { char ch; if (argc!=3) { cout<<”Bad commond !\n”; return 0; } ifstream inf (argv[1]); if (! inf) { cout<<”Cannot open souce file.”; return 1; } ofstream outf (argv[2]); if (! outf) { cout<<”Cannot open target file.”; return 1; } while (inf) { inf.get (ch); outf.put (ch); } inf.close ( ); outf.close ( ); } 假定本程序的文件名为mvc7_15.cpp,经编译、连接后生成的可执行文件名为mvc7_15.exe,则在DOS下输入以下命令行: D> mvc7_15 file1.exe file2.exe 该程序的执行结果,是把文件file1.exe中的信息拷贝到文件file2.exe中
39 eof( )函数的使用 二进制文件的处理过程与文本文件的处理过程基本相同,但在判断文件是否结束时有所区别。在文本文件中,遇到文件结束符,get函数返回一个文件结束标志EOF,该标志的值为-1。但在处理二进制文件时,读入某一字节中的二进制数的值可能是-1,这与EOF的值相同。这样就有可能出现读入的有用数据被处理成“文件结束”的情况。为了解决这个问题,c++提供了一个成员函数eof,用来判断文件是否真的结束,其原型为: int eof(); 当到达文件末尾时,它返回一个非零值,否则返回零。当输入文件是键盘时,其结束符是ctrl_z, eof( ) 函数返回的值为真。
40 例 7.16在屏幕上显示任何给定文件的内容。 # include <iostream.h> # include <fstream.h> main (int argc, char *argv[ ]) { char ch; if (argc!=2) { cout<<”Usage: PR<filename>\n”; return 1; } ifstream in (argv[1]); if (! in) { cout<<”Cannot open file”; return 1; } while (! in.eof ( )) { in.get (ch); cout<<ch; } return 0; } 假定本程序的文件名为mvc7_16.cpp,经编译、连接后生成的可执行文件名为mvc7_16.exe,则在DOS下输入以下命令行: D> mvc7_16 file1 该程序的执行结果,是把文件 file1 中的信息在屏幕上显示。
41 (2) 用read( )函数和write( )函数读写二进制文件 有时需要读写一组数据 (如一个结构变量的值),为此C++提供了两个函数read( )和write( ),用来读写一个数据块。 ① read( )函数: 函数原型: istream &read (unsigned char *buf,int num); read( )是类istream中的成员函数,其功能为:从相应的流中读取num个字节(字符),并把它们放入指针buf所指的缓冲区中。 调用格式: read(缓冲区首址,读入的字节数); 注意:“缓冲区首址”的数据类型为unsigned char*,当输入其它类型数据时,必须进行类型转换,例如: int array[ ]={50,60,70};定义了一个整型数组array, read((unsigned char *)&array,sizeof(array); 对整型数组array,为了把数据从文件读入到它中,必须在read( )函数中把它转换为unsigned char *类型。由sizeof( )函数确定要读入的字节数。
42 ② write( )函数: 函数原型: ostream &write(const unsigned char *buf, int num); write( )是流类ostream的成员函数,利用该函数,可以把buf所指的缓冲区num个字节写到相应的流上。参数的含义及调用注意事项与read()函数类似。 注意:如果在num个字节被读出之前就达到了文件尾,则read( )只是停止执行,此时缓冲区包含这些字符。我们可以用另一个成员函数gcount( )统计出有多少字符被读出。gcount( )的原型如下: int gcount( );它返回所读取的字节数。
43 例 7.17 用write( )函数向文件test中写入双精度数与字符串。 # include <iostream.h> # include <fstream.h> # include <string.h> main( ) { ofstream outf(”test”); if (! out) { cout<<”cannot open output file .\n”; return 1; } double num=100.45; char str [ ]=”This is a test”; out.write ((char * )&num,sizeof (double )); out.write (str, strlen (str)); out.close ( ); return 0; } 程序执行后,屏幕上不显示任何信息,但程序已将双精度100.45和字符串“this is a test” 以二进制形式写入文件test中。用下面的程序 可以读取文件test中的数据,并在屏幕上显示出来,以验证前面的程序操作。
44 例 7.18 用read( ) 函数读取例7.17中程序所建立的文件。 # include <iostream.h> # include <fstream.h> # include <string.h> main() { ifstream in(”test”); if (! in) { cout<<”cannot open input file .\n”; return 1; } double num; char str [80]; in.read ((char * )&num, sizeof (double)); in.read (str, 14); cout<<num<<’ ’<<str; in.close ( ); return 0; } 程序运行结果为: 100.45 This is a test
45 3. 文件的随机读写 前面介绍的文件操作都是按一定顺序进行读写的,因此称为顺序文件。 C++在类istream及类ostream中定义了几个与在输出流中随机移动文件指针的成员函数,则可以在流内随机移动文件指针,从而可以对文件的任意数据进行随机读写。用函数seekg( )和seekp( )进行随机访问。函数seekg( )用于输入文件,函数seekp( )用于输出文件。
46 它们的函数原型如下: istream &seekg (streamoff offset, seek_dir origin); ostream &seekp (streamoff offset, seek_dir origin); 其中,参数origin表示文件指针的起始位置,offset表示相对于这个起始位置的位移量。seek_dir是一个系统定义的枚举名,origin是枚举变量。origin的取值有以下三种情况: ios::beg从文件头开始,把文件指针移动由offset指定的距离。offset的值为正数 ios::cur从当前位置开始,把文件指针向前(后)移动由offset指定的距离。 offset 的值为正或负数,正数时向前移,负数时向后移。 ios::end从文件尾开始,把文件指针移动由offset指定的距离,offset的值为负数进行文件的随机读写时,可以用以下函数测试文件指针的当前位置: streampos tellg( );用于输入文件。 streampos tellp( );用于输出文件。 streampos是在iostream.h中定义的类型,实际上是long型的。
例 7.19 演示seekp( )函数。它修改文件中的指定字符。执行时在命令行指定文件名,其后跟想修改的字符的位置,然后再跟新的字符。 47 # include <iostream.h> # include <fstream.h> # include <stdlib.h> int main (int argc, char *argv[ ]) { if (argc!=4) { cout<<”Usage: CHANGE<filename> <byte> <char>\n”; return 1; } fstream out (argv[1], ios::in|ios::out); if (! out) { cout<<”Cannot open file”<<argv[1]<<”\n”; return 1; } out.seekp (atoi (argv[2]), ios::beg); out.put ( *argv[3]); out.close ( ); return 0; } 假定上述程序的文件名为mvc7_19.cpp,经编译、连接生成的可执行文件名为mvc7_19.exe。需要修改的文件名为file,file的内容为: AAAAAAAAAA1234567890 执行下述命令: D> mvc7_19 file 5 # 程序运行结果为: AAAAA#AAAA1234567890
42 48 例 7.20 使用seekg( )函数,它从指定的位置开始显示文件内容。 # include <iostream.h> #include <fstream.h> # include <stdlib.h> void main (int argc, char * argv[ ]) { char ch; if (argc!=3 ) { cout<<”Usage: LOCATE<filename><loc>\n”; return 1; } ifstream inf (argv[1] ); if (! inf) { cout<<”Cannot open input file. \n”; return 1; } inf.seekg (atoi (argv[2]), ios::beg); while (! inf.eof ( )) { inf.get (ch); cout<<ch; } inf.close ( ); } 假定程序的执行文件文件名为mvc7_20.exe,要显示文件名为file,希望从第5个位置开始显示,则执行的命令行: D>mvc7_20 file 5 <cr> 程序运行结果为: #AAAA1234567890
49 7.6 应 用 举 例 例7.21 重载运算符“<<”和“>>”,使用户能直接输入和输出复数。 #include<iostream.h> class complex{ double real,imag; public: complex(double r, double i) { real=r; imag=i;} complex( ) { real=0; imag=0;} friend complex operator+(complex, complex); friend ostream &operator<<(ostream&, complex&); friend istream &operator>>(istream&, complex&); }; complex operator+(complex a, complex b)//定义重载运算符“+” { complex temp; temp.real=a.real+b.real; temp.imag=a.imag+b.imag; return temp; }