第8章 数据流与文件的输入输出
第8章 C++的I/O系统
8.1 知识要点
1.C++语言的“流”是指信息从外部输入设备(如键盘、磁盘等)向计算机内部(内存)输入和从内存向外部输出设备(显示器、磁盘)输出的过程。这种输入输出过程被形象地比喻为“流”。
2.C++系统中的所有I/O类均包含在iostream.h 、fstream.h、strstream.h这三个系统头文件中。
3.C++系统编译预处理时,要对# include命令进行“文件包含”处理,把该命令中指定的文件中的全部内容嵌入到该命令的位置,再编译整个C++文件,生成相应的目标代码程序。“文件包含”命令可以节省程序设计人员的重复劳动,也可以共享一些相同的程序段。
C++语言不仅提供了现成的I/O类库供用户使用,而且还为用户进行标准I/O操作定义了4个类对象,它们分别是cin、cout、cerr、clong
4.用格式控制符进行格式化输入、输出
5.所谓“文件”一般是指:存储在外部介质上的数据的集合。文件可用于存入程序代码,也可用于存放数据。
6.C++语言的文件名也是由文件的主名和扩展名两部分组成,它们之间用“.”号分隔。文件的主名是由用户命名的一个有效的C++标识符,为了同其他软件系统兼容,一般不超过8个有效字符。
7.C++语言把文件看作是一个字符(字节)序列,即由一个一个字符的数据顺序组成。根据数据的组织形式可分为ASCII文件和二进制文件两种。ASCII文件又称为文本(text)文件或字符文件,它的每一个字节放一个ASCII代码,代表一个字符。二进制文件又称为字节文件,是把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。
8.对文件的操作
要在程序中使用文件,就要在程序的开始包含预处理命令:
#include
对一般文件(主要是磁盘文件)的操作过程是:打开,读/写,关闭。
用户对标准I/O文件不需要进行打开/关闭,标准I/O文件函数分为以下三种:
(1)字符I/O函数字符输入函数getchar ( )和字符输出函数putchar ( )。
(2)字符串I/O函数字符串输入函数gets ( )和字符串输出函数puts ( )。
(3)格式化I/O函数格式化输入函数scanf ( )和格式化输出函数printf ( )。
9.对字符文件的操作
(1)字符文件
在字符文件中,以换行符‘\n’结束的一串字符称为记录;终端屏幕上输出的一行字符就相当于一条记录;交互输入时,用户通过键盘输入,以回车键结束的一串字符也是一条记录。
(2)向字符文件写入数据向字符文件写入数据有2种方法。
1)调用从ostream流类中继承来的插入操作重载函数。使用方法同交互输出过程,但这时插入操作符的第一个操作数应是某个定义过的文件对象而不是cout。
(3)从字符文件读取数据到内存变量从打开的字符文件中输入数据到内存变量有3种方法。
10.对字节文件(二进制文件)的操作
(1)字节文件
与字符文件不同的是,字节文件中存放的是可供机器直接读取的二进制代码,在打开方式中带有ios::binary选项
(2)建立二进制文件和write()成员函数一个文件被用户定义的一个文件流对象按字节方式打开后,通过文件流对象调用在ostream流类中定义的write()成员函数就能够向文件流对象所对应的文件中写入数据。
(3)访问二进制文件和read()成员函数
从字节文件读数据,就是把具有一定字节数的内容原原本本地复制到内存中由指定字符
指针所指向的内存缓冲区中,所读内容是从当前文件指针所指位置开始读取,然后文件指针自动向后移动所读取内容的字节数。
11.随机访问文件
(1)随机访问文件是可以使用位移量对其中的数据进行访问,“位移量”指以当前位置(开始点或当前位置或文件末尾)为基点,移动的字节数。
(2)C++语言既支持顺序访问文件也支持随机访问文件。为实现对文件的随机访问,fstream 文件类中定义了以下4个成员函数:seekg ( )、tellg ( )、seekp ( )和ellp ( )。seekg ( )和tellg ( )带后缀g表示get,这两个函数用于从文件中读取数据的情况;函数seekp ( )和tellp ( )带后缀p表示put,这两个函数用于向数据文件中写数据的情况。
8.2 例题分析
例题1:说明下面程序的格式控制符的作用
#include
#include
void main()
{ int number1=15;
double number2=6.54321;
cout<<"Decimal:"< cout<<"Hexadecimal:"< cout<<"Hexadecimal:"< cout<<"Octal:"< cout< cout< cout<< number2< cout< cout< cout<< number2< cout< cout<< number2< cout< cout<< number2< cout< cout< cout<< number2< } 运行结果: Decimal:15 Hexadecimal:f Hexadecimal:F Octal:17 6.54321 6.54 6.54 6.54 6.543E+000 6.5432 分析:程序中使用输入输出流格式控制符,必须包含头文件iomanip.h。 整型数number=15是个十进制数,dec控制符输出十进制数;hex控制符输出十六进制数f;setiosflags(ios::uppercase) 控制符输出大写十六进制数F;oct控制符输出八进制数17。 结果第5行没有设置有效位数,按流的默认值6位输出3.14159。 setprecision(3)控制符设置了3位有效位数,输出3.14。 setw(15)控制符设置了域宽为15,只影响下一个值的输出。 setiosflags(ios::left)设置了左对齐方式; setiosflags(ios::right)设置了右对齐方式。 setiosflags(ios::scientific) 设置了指数表示方式。 setprecision(3)和setiosflags(ios::fixed) 共同设置了浮点数表示方式。 例题2:已知int a, *pa=&a;输出指针pa十进制的地址值的方法是()。 A.cout< C.cout<<&pa; D.cout< 答案:D 分析:插入符输出指针类型对象的地址值时,默认为十六进制形式。如果要输出十进制形式的地址值,必须用类型long进行强制。答案A,输出了指针pa本身的值,也即变量a的地址值,不能选A。答案B中输出了指针pa所指向的对象,即变量a的值,不能选B。答案C,以十六进制形式输出指针pa的地址值,也不能选。 例题3:关于read()函数的下列描述中,()是对的。 A. A.函数只能从键盘输入中获取字符串 B. B.函数所获取的字符多少是不受限制的 C. C.该函数只能用于文本文件的操作中 D. D.该函数只能按规定读取所指定的字符数 答案:D 分析:read成员函数的使用格式是:read(char *buf,int size); 其中,buf用来存放读取到的字符指针或字符数组,size用来指定从输出流中读取字符的个数。read函数不仅可以从键盘输入中读取字符,也可以从任意输入流中获取信息,因此答案A错误。read函数读取的字符数受到size参数的限制,答案B错误。read函数不仅可以用于文本文件,也可以用于二进制文件,答案C错误。 例题4:某个类的输入、输出重载操作符>>和<<应定义为类的成员函数,描述正确吗? 答案:错误。 分析:由于重载插入符和提取符时,其左操作数是流,右操作数是类的对象,因此,插入符和提取符只能重载为友元函数。 例题5:下面程序建立了类triangle,用来存储直角三角形的宽与高。这个类的重载输出运算符函数在屏幕上显示三角形。 程序: #include class triangle { int height,base; public: triangle(int h,int b) {height=h;base=b; } friend ostream& operator <<(ostream &stream,triangle ob); }; ostream& operator <<(ostream &stream,triangle ob) { int i,j,h,k; i=j=ob.base-1; for(h=ob.height-1;h;h--) { for(k=i;k;k--) stream<<' '; stream<<'*'; if(j!=i) { for(k=j-i;k;k--)stream<<' '; stream<<'*'; } i--; stream< } for(k=0;k stream< return stream; } void main() { triangle t1(5,5),t2(10,10),t3(12,12); cout< cout< cout< } (1)插入符和提取符应重载为类的友元。 (2)插入符和提取符重载格式如下: ostream& operator<<(ostream& s,const type& p) { //操作代码 return s; } istream& operator>>(istream& s, type& p) { //操作代码 return s; } 其中左操作数是对ostream或istream对象的引用,右操作数接收将被输出或输入的对象。 (3)对重载的插入符或提取符的调用形式如下: ostream< istream>>obj; 分析: (1)不清楚插入符和提取符应重载为类的友元。 (2)重载插入符和提取符格式错误。 (3)不清楚如何用重载后的插入符与提取符进行输入/输出。 例题6:从输入流中分析出数字串。例如:设输入串为 A012BCD378 274D EF55G^Z 则输出为: Digit string 1 is: 012 Digit string 2 is: 378 Digit string 3 is: 274 Digit stirng 4 is: 55 答案: #include #include bool getnum(char *); void main() { int i=1; char buf[100]; while(getnum(buf)) { cout<<"Digit string "< cout< i++; } } bool getnum(char *s) { bool found(false); char ch; while(cin.get(ch)&&!isdigit(ch)) ; if(!cin) return false; do{ *s++=ch; }while(cin.get(ch)&&isdigit(ch)); *s='\0'; found=true; if(cin) cin.putback(ch); return found; } 例题7:定义一个Dog类,包含体重和年龄两个数据成员及相应的成员函数。声明一个实例dog1,体重为5,年龄为10,使用I/O流把dog1的状态写入磁盘文件;再声明另一个实例dog2,通过读文件把dog1的状态赋给dog2。 答案: #include class Dog {public: Dog(int w,int a):weight(w),age(a) {} ~Dog() {} int GetWeight() { return weight; } int GetAge() { return age; } void SetWeight(int w) { weight=w; } void SetAge(int a) { age=a; } private: int age,weight; }; int main() { char fileName[80]; cout<<"Please input the file name: "; cin>>fileName; ofstream fout(fileName); if(!fout) { cout<<"Can't open the file "< return(1); } Dog dog1(5,10); fout.write((char*)&dog1,sizeof(dog1)); fout.close(); ifstream fin(fileName); if(!fin) { cout<<"Can't open the file "< return(1); } Dog dog2(0,0); fin.read((char*)&dog2,sizeof(dog2)); fin.close(); cout<<"Dog2's weight: "< cout<<"Dog2's age: "< return 0; } 8.3 教材习题解答 1.当使用ifstream流类定义一个对象并打开一个磁盘文件时,文件的隐含打开方式是什么?答:Ifstream myfile; Myf ile.open(“filename”); Open函数由两个参数组成,第一个参数是指定的文件路径名,第二个参数是打开模式,系统默认按ios::in方式打开,即为隐含打开方式。 2.当使用ofstream流类定义一个对象并打开一个磁盘文件时,文件的隐含打开方式是什么? 答:当用输出文件流对象调用open()成员函数打开一个文件时,打开方式参数也可以省略,即使用户指定了其他的打开方式,系统仍按默认的ios::out方式打开 3.当使用fstream流类定义一个对象并打开一个磁盘文件时,文件的隐含打开方式是什么?答:文件流类ifstream定义的对象实际上是一个输入文件;输出文件流类ofstream,文件流类ofstream定义的对象实际上是一个输出文件;输入输出(双向)文件流类fstream,文件流类fstream定义的对象实际上是一个既可读又可写的文件。所以当使用fstream流类定义一个对象并打开一个磁盘文件时,文件的隐含打开方式是ios::in | ios::out 4.若在程序文件中进行标准输入输出操作,则必须在文件开始的#include命令中使用哪一个头文件? 答:#include 5.若在程序文件中进行文件的输入输出操作,则必须在文件开始的#include命令中使用哪一个头文件? 答:#include 6.什么是格式状态标志? 答:格式状态标志是C++编译器提供的一种格式标记,有6种:left、right、showpoint、showpos、fixed和scientific。它们作为setiosflags和resetiosflags的参数使用,其类型为枚举型。格式化输入、输出使用到的格式控制符和格式状态标志均在根基类ios类中有定义 7.程序中有说明语句: int a1; char a2 ; double a3 ; 根据下面的输出要求写出相应的cout语句。 (1)输出a1、a2、a3的值,每两个值之间用5个空格间隔。; (2)以固定长度10按左对齐方式输出变量a1的值。 (3)以固定长度10按左对齐方式输出变量a1的值,如果a1值为正,要求输出前面的正号。(4)以固定长度15按右对齐方式输出变量a3的值,用填充字符*填充不足部分(用浮点数的定点数表示法表示)。 (5)以固定长度15、小数点后保留3位、按左对齐方式输出变量a3的值。 #include Void main() { int a1; char a2 ; double a3 ; Cin>>a1>>a2>>a3; Cout< Cout< Cout< Cout< < Cout< < } 8.现在需要打开empfile文件,进行文件更新操作,应使用什么语句来实现? 答:Fstream myfile; Myfile.open(“c:\\empfile”, ios :: out); 9.在C++语言中,二进制文件可以用来存放哪些数据类型的数据? 答:字节文件中存放的是可供机器直接读取的二进制代码,在打开方式中带有ios::binary 选项。通常二进制文件的内容不能直接打印,也不允许用户直接阅读。但与字符文件相比它也有许多优点,如节省内存空间;可以存放结构类型的数据、记录等 10.执行完函数调用indata.seekg(0 ,ios : : end);后。函数indata.tellg()将返回什么值? 答:indata.seekg(0 ,ios : : end);表示从文件末尾开始移动0个字节,所以,indata.tellg()函数将返回该文件的末尾字节的索引值。 11.简述几种打开文件方式ios::in 、ios::out、ios::app及ios::in|ios::out之间的区别。 答:1)ios :: in:打开的文件是输入文件,既从该文件中读取数据,用于数据输入。 2)ios :: out:打开的文件是输出文件,既该文件用于写入数据,用于数据输出。 3)ios :: in | ios :: out:打开的文件既可以用于输入也可以用于输出。 12.如何判断打开文件操作是否成功? 答: fin . open ( file_ name , ios::in ) ; if ( fin .fail ( ) ) //判断open语句执行是否成功,若不成功 //用cout语句输出文件打开失败提示信息 { cout << “\n file open error on “ << file_name ; exit (–1) ; } 13.函数exit()的功能是什么?在程序中的作用是什么? 答:函数exit( )将强制程序结束执行,其格式为: exit(-1 ) ; 它仅有一个整型参数,执行这条语句时,结束程序的执行,将控制权交还给操作系统,同时给出程序的结束状态-1。通常负的参数值代表程序的非正常结束。 14.函数close()实现什么操作?为什么完成对文件的操作后应及时关闭文件? 答:每个文件流类中都提供有一个关闭文件的成员函数close( ),当打开文件并在文件上执行完相应操作后,应将文件关闭。关闭任何一个流对象所对应的文件,就是利用这个流对象调用close( )成员函数,调用格式如下: internalfilename.close ( ) ; internalfilename是相应I/O类对象的名,close()成员函数没有参数。如果文件是以输出或添加方式打开的,操作系统将在close( )成员函数的作用下在文件的末尾加上文件结束符,断开程序与文件间的连接;如果文件是以输入方式打开的,close( )成员函数直接断开程序与文件间的连接。并能保证最后输出到文件缓冲区中的内容,无论是否已满都将立即写入到对应的文件中。当文件关闭后,文件对象不再存在,此时不能再对该文件进行任何操作。可再次使用open( )函数打开文件。 如果使用完文件后,没有使用close( )成员函数将程序与文件断开,则当程序结束执行时,操作系统将自动关闭文件。但应养成在使用完文件后立即将其关闭的好习惯。 15.顺序访问文件与随机访问文件之间的区别是什么? 答:顺序访问文件是指只能按数据的存放顺序依次进行读取;随机访问文件是可以使用位移量对其中的数据进行访问,“位移量”指以当前位置(开始点或当前位置或文件末尾)为基点,移动的字节数。 16.编写一个完整的C++程序,功能是读取一个文本文件的内容,并将文件内容以10行为单位输出到屏幕上,每输出10行就询问用户是否结束程序,不是则继续输出文件后面的内容。 程序: # include < iostream . h > # include < stdlib . h > # include < fstream . h > void main() { char ch ; // 使用ch读入字符 char y; int j=0 // 使用j统计行数 ifsteam file4 ( “D: \\ write2 .dat “ ,ios::in | ios::nocreate ) ; //定义输入文件流file4并打开D盘上的write2.dat文件 if ( ! file4 ) //判断文件打开操作是否成功,当file4打开失败提示有关信息{ cerr << “\n D:\\write2.dat not open “ << endl ; exit (–1) ; } while (f4.get (ch ) ) //依次从文件中输入字符到ch,当读到的是文件结束符时 // 条件表达式的值为0 { cout << ch ; if (ch = = …\n? ) j++ ; //如果是换行符,j值增加1 if( j%10==0) { cout<<”是否继续显示?Y/N”; cin>y; if( y==?y? || y==?Y?) continue; else break; } } cout << endl << ” lines : “ << j << endl ; file4.close ( ) ; } 17.按下面每个题目的要求编写出相应的函数 (1)利用一个字符文件保存100以内的所有素数。 (2)利用一个字节文件保存10个100以内的随机整数,要求保存的所有值各不相同。程序: (1) # include < iostream . h > # include < stdlib . h > # include < fstream . h > void main ( ) { ofstream file1 ; // 定义输出文件流file1。 file1.open ( “A: \\ write1 .dat “ ) ; // 调用open ( )成员函数打开A盘上的write1.dat //文件。也可直接写为ofsteam file1(“A: \\ write1 .dat “ ) i f ( ! file1 ) //判断open语句执行是否成功,当file1打开失败则提示有关信息{ cerr << “\n A:\\write1.dat not open “ << endl ; exit (–1) ; } int flag=0; file1<<2; for (int k=3 ; k<=100 ; k++ ) { Flag=1; For( int j=2; j<=k-1;j++) { if( k%j==0) { flag=0; Break;; } } If ( flag==1) file1 << k << “ “ ; // 向file1文件流写入k值 } file1.close ( ) ; // 关闭file1所对应的文件 } (2) # include < stdlib . h > # include < fstream . h > #include #include #include void main ( ) { int x[10]; srand( (unsigned)time( NULL ) ); for ( int i=0;i<10;i++) { x[i]=rand(); } ofstream file5 ; // 定义输入文件流file5 file5.open ( “d:\\bb1.dat “,ios :: out | ios :: binary) ; // 调用open ( )成员函数打开d盘上的bb1.dat二进制文件 // 也可直接写为ofsteam file5(“d: \\ bb1 .dat “ , ios :: out | ios :: binary )if ( ! file5) //判断open语句执行是否成功,当file5打开失败提示有关信息。 { cerr << “\n d:\\ bb1.dat not open “ << endl ; exit (–1) ; } for (int k=0 ; k<10 ; k++ ) //向f5所对应的二进制文件中写入每个元素的值file5 .write( (char * ) & x[k] , sizeof (int )) ; //也可写为file5 .write( (char * ) & x[k] , 2 ) file5.close ( ) ; // 关闭file5所对应的文件 } 8.4 补充习题 1.已知int a, *pa=&a;,输出指针pa十进制的地址值的方法是()。