第 11 章 输入/输出流 11.1 输入/输出流概述 11.2 标准输入/输出流 11.3 输入/输出格式控制 11.4 文件输入/输出 第 11 章 输入/输出流 11.1 输入/输出流概述 11.2 标准输入/输出流 11.3 输入/输出格式控制 11.4 文件输入/输出 11.5 用户自定义类型的输入/输出 11.6 综合实例
输入输出(I/O)是所有高级语言都必须具备的基本功能。C++语言中也没有输入/输出语句。C++语言是通过I/O流来实现输入/输出的。I/O流不是C++语言的一部分,而是标准C++库的一部分,是C++类的一个集合,本章主 要介绍I/O流的使用,包括格式化输入输出、用户自定义类型的输入输出。 【 11.1输入/输出流概述】 流的概念始终和设备无关性密切相关,所谓设备无关性是指编程者不必关心所访问的特定设备的各种细节变化,他的程序可以根本不做改动或者只做少量改动就可以访问不同设备。设备无关性是程序可移植和可重用性的重要标志。 注 通过流的概念,可以将要输入或者输出的数据看作一个字节流,而不必考虑具体设备的特定细节。
【 11.1输入/输出流概述】 C++语言完全支持C语言的I/O系统,此外还定义了一套面向对象的I/O系统。通过面向对象的I/O系统,C++语言可以支持用户自定义的各种类的 对象的输入输出。支持面向对象的特征。 将流看成是一个对象,这个对象要与某种设备相联系,如与输入设备相联系的流是输入流,与输出设备相联系的流是输出流,与输入输出设备相联系的流则是输入输出流。 标准输入流cin 标准输出流cout 与标准输入设备键盘相关联 与标准输出流显示器相关联 非缓冲型的标准出错流cerr 缓冲型的标准出错流clog 预定义的流对象
ios istream ostream istrstream ifstream istream_withassign ostream_withassign ofstream ostrstream iostream stdiostream fstream strstream streambuf stdiobuf filebuf strstreambuf 图 各类之间的关系
【 11.2 标准输入/输出流】 标准输出流cout,是一个系统预先定义好的ostream类的派生类对象,它与标准输出设备(显示器)相联系,可以实现数据的显示。正如前面看到的,cout可以通过“<<”共同完成对数据的输出,其中“<<”被称为流插入运算符,或者简称插入符。 注 为什么cout对象可以把不同类型的数据按类似的方法输出呢? 原因就在于ostream类对“<<”插入符进行了运算符重载,用来处理各种基本数据类型的输出。 例 11-3 指针值的显示 //EXAMPLE11_3.CPP //源程序开始 #include <iostream.h> int main() { int value=100; int * iptr=&value;
程序运行结果: //显示value值 cout<<”value =”<<value<<endl; //显示iptr值(保存了value的地址) cout<<”iptr =”<<iptr<<endl; //显示iptr自身地址 cout<<”&iptr =”<<&iptr<<endl; return 0; } 程序运行结果: value =100 &value =0012FF7C iptr =0012FF7C &iptr =0012FF78
标准输入流cin,与标准输出流对象cout类似,cin也是一个系统预先定义好的istream类的派生类对象,它与标准输入设备(通常是键盘)相联系,可以实现数据的输入。cin可以通过“>>”完成数据的输入,其中“>>”被称为流提取运算符,或者简称提取符。 例 11-5 使用提取运算符进行数据的连续输入 //EXAMPLE11_5.CPP //源程序开始 #include <iostream.h> int main() { int i; double f; char str[30]; cout<<"请输入i、f和str : "; cin>>i>>f>>str; cout<<"你输入的数据分别是:"<<i<<" , "<<f<<" , "<<str<<endl; return 0; } 程序运行结果: 请输入i、f和str : 1 2.22 TestStream 你输入的数据分别是:1 , 2.22 , TestStream
【 11.3 输入/输出格式控制】 一般在进行输入输出格式控制时需要使用ios类中的相关成员函数或者是操纵子函数。 【 11.3 输入/输出格式控制】 一般在进行输入输出格式控制时需要使用ios类中的相关成员函数或者是操纵子函数。 在ios类中控制输入输出的成员函数有: int ios::width(); //返回当前的宽度设置 int ios::width(int w); //设置宽度并返回前一次的设置 int ios::precision(int p); //设置精度并返回前一次设置值 char ios::fill(); //返回当前空位填充的字符 char ios::fill(char ch); //设置空位填充字符 long ios::setf(long flags); //设置状态标志 long ios::unsetf(long flags); //清除状态标志 long ios::flags(); //测试状态标志 long ios::flags(long flags); //设置状态标志并返回前一次的状态标志 注 状态标志是各种状态值之间通过或运算组合而成的,在ios类中是一个公共的枚举类型。
表 iso类中各个标志代表的含义 标 志 标 志 含 义 跳过输入中的空白 输出数据左对齐 输出数据右对齐 在指定符号位或基指示符之后加入填充字符 基为十进制 基为八进制 基为十六进制 生成一个前缀标明生成的整数输出的基 skipws left right oct hex showbase showpoint 显示浮点数的小数点和后面的0 internal dec 输出时以大写字母代替相应的小写字母 正数前面的正号被显示 按科学记数法表示浮点数 以定点格式显示浮点数 showpos scientific fixed unitbuf 插入操作后立即刷新缓冲区 uppercase boolalpha 在字母格式中插入和提取布尔类型 stdio 插入操作后清空每个流
例 11-6 使用ios类成员函数控制输出格式 //EXAMPLE11_6.CPP //源程序开始 #include <iostream.h> int main() { const int n=3; int test[]={1024,2048,3072}; for(int i=0;i<n;i++) cout.setf(ios::oct|ios::showbase|ios::showpos); //设置标志八进制,显示基及正号 cout<<test[i]<<" "; cout.unsetf(ios::oct);//解除八进制设置 cout.setf(ios::hex|ios::showbase|ios::showpos); //设置标志十六进制,显示基及正号 cout.unsetf(ios::hex); //解除十六进制设置 cout<<test[i]<<endl; } cout<<endl; cout.fill('#'); //设置填充符号为# 例 11-6 (续)
程序输出: for(i=0;i<n;i++) { cout.width(8); //设置输出域宽度为8 cout<<test[i]<<" "; } cout<<endl; cout.fill(‘*’); //将填充字符设置为* cout.width(8); return 0; 程序输出: 02000 0x400 +1024 04000 0x800 +2048 06000 0xc00 +3072 ###+1024 ###+2048 ###+3072 ***+1024 ***+2048 ***+3072
注 设置I/O格式还有另外一种途径,就是使用格式操纵子。格式操纵子可以直接嵌入到输入输出语句中。格式操纵子分为带参数和不带参数两种。 表 C++中预定义的操纵子表 操 纵 子 说 明 以十进制格式 以十六进制格式 以八进制为格式 输入跳过开始的空格 换行并刷新输出流 插入空字符结束字符串 刷新输出流 转换基数设置 dec hex oct ends flush setbase setiosflags(long f) 设置格式标志 ws endl 解除格式标志设置 填充字符 设置数据精度 设置输出宽度 setfill(int c) setprecision(int p) setw(int w) resetiosflags(long f)
使用格式操纵子控制输出格式 例 11-8 //EXAMPLE11_8.CPP //源程序开始#include <iostream.h> #include <iostream.h> #include <iomanip.h> #include <math.h> int main() { const double pi=3.141592653589793; int i; cout<<setiosflags(ios::right); //设置标志为右对齐输出 cout<<setiosflags(ios::scientific|ios::uppercase); //设置标志为科学记数法,显示大写E cout<<setprecision(8); //设置输出精度为4 for(i=0;i<360;i+=60) cout<<"sin("; cout<<setw(3)<<i<<")="; cout<<setw(17)<<sin(i/180.0*pi)<<" ; "; cout<<"cos("; cout<<setw(17)<<cos(i/180.0*pi)<<endl; } 例 11-8 (续)
程序输出: sin( 0)= 0.00000000E+000 ; cos( 0)= 1.00000000E+000 return 0; } 程序输出: sin( 0)= 0.00000000E+000 ; cos( 0)= 1.00000000E+000 sin( 60)= 8.66025404E-001 ; cos( 60)= 5.00000000E-001 sin(120)= 8.66025404E-001 ; cos(120)= -5.00000000E-001 sin(180)= 1.22460635E-016 ; cos(180)= -1.00000000E+000 sin(240)= -8.66025404E-001 ; cos(240)= -5.00000000E-001 sin(300)= -8.66025404E-001 ; cos(300)= 5.00000000E-001 注 由于误差,180度的正弦值不为0。
【 11.4 文件输入/输出】 文件输入输出也分为二进制和文本方式,也要首先创建流,将该流与某个具体文件相关联(打开文件),然后进行读写操作,最后关闭该文件。 使用3种流类进行文件的输入输出:ifstream、ofstream、fstream它们的基类分别是istream、ostream、iostream。 可以使用成员函数open()和close()进行文件的打开和关闭操作 open()函数的原型为: void open(const char *filename, int filemode, int access=filebuf::openprot); filename包含路径的文件名 filemode为文件的打开方式 access为文件的存取属性 注 文件的打开方式必须取下表所示的一个或者其组合
表 文体的打开方式 打 开 方 式 含 义 使输出追加到文件尾 查找文件尾 从文件中读 打开不存在的文件时失败 文件存在时打开失败 写 同名文件被删除 文件以二进制方式打开 Ios::app Ios::ate Ios::in Ios::out Ios::trunc Ios::binary setiosflags(long f) 设置格式标志 Ios::nocreate Ios::noreplace 0——普通文件 1——只读文件 2——隐含文件 4——系统文件 8——备份文件 文件的访问方式
输入输出流的主要成员函数除open和close外还有: get()函数可以从与流对象相关联的文件中读取数据,每次读一个字节。 istream& get(unsigned char ch); istream& getline(char * str, streamsize n); ostream& put(char ch); getline()则容许从输入流中读取多个字符,并容许指定输入终止字符。 put()函数可以向与流相关联的文件中写入数据,每次写一个字符。 read()函数可以从流中读取num个字节,并放入buffer所指的缓冲区中。 istream& read(unsigned char * buffer, int num); ostream& write(const unsigned char * buffer, int num); write()函数把buffer所指向的缓冲区中num个字节写到相应的流中。 注 由于缓冲区首地址的数据类型为unsigned char *, 所以输入输出其他类型数据时必须进行强制类型转换。
seekg()函数用于输入,将相应的文件读指针从origin的位置偏置offset个字节。 istream& seekg(streamoff offset, seekdir origin); ostream& seekp(streamoff offset, seekdir origin); streampos tellg(); streampos tellp(); seekp()函数用于输出,将相应的文件写指针从origin的位置偏置offset个字节。 函数tellg()和函数tellp()则用于输入输出文件时确定文件指针的当前位置。 origin是每举变量,可以有下面三个值: ios::beg //从文件头开始 ios::cur //从文件当前位置开始 ios::end //从文件尾开始
例 11-9 文件I/O //EXAMPLE11_9.CPP //源程序开始 #include <iostream.h> #include <fstream.h> #include <string.h> int main() { int testi[]={1111,2222, 3333,4444}; //要写入文件的数据 char *tests[]={"This", "is", "a", "file"}; char fwi[30] ,fri[30], fws[30],frs[30]; //文件名 int tmpi; char tmps[80]; ofstream ofi,ofs;//输出流对象 ifstream ifi, ifs;//输入流对象 例 11-9
cout<<"Please input the file name which you want to output integer:"<<endl; cin>>fwi; //获得整数输出文件名 ofi.open(fwi);//打开输出文件 for(int i=0;i<4;i++) { ofi<<testi[i]<<" ";//向输出文件中写数据 } ofi<<endl; cout<<"Please input the file name whick you want to output string:"<<endl; cin>>fws;//获得字符输出文件名 ofs.open(fws);//打开输出文件 for(i=0;i<4;i++) ofs<<tests[i]<<" ";//向输出文件写数据 ofs<<endl; ofi.close();//关闭输出文件 ofs.close();//关闭输出文件 (续)
(续) strcpy(fri,fwi);//获得输入文件名,与前面输出文件同名 strcpy(frs,fws); ifi.open(fri);//打开输入文件 ifs.open(frs);//打开输入文件 for(i=0;i<4;i++) { ifi>>tmpi;//从输入文件中读数据 cout<<tmpi<<" "; } cout<<endl; ifs>>tmps;//从输入文件中读数据 cout<<tmps<<" "; return 0; 说明:按提示输入要写入的文件名(包含路径)后,将建立以该文件名命名的新文件,并分别将1111 2222 3333 4444和字符串″This is a file″写到这两个文件中,并将写入的内容读出来显示在输出窗口。
例 11-10 通过文件指针的位置对文件进行读写操作 #include <fstream.h> int main() { double dbl; fstream myio; //声明输入输出流 streampos pos[20]; //声明保存文件指针位置的数组 myio.open(″myfile″,ios::in|ios::out|ios::binary); //以读写方式打开文件 for(int i=0;i<10;i++) dbl=i/10.0; myio.write((char *)&dbl,sizeof(double)); //向文件中写数据 pos[i]=myio.tellp(); //保存文件指针当前的位置 } for(i=10;i<20;i++) } ` 通过文件指针的位置对文件进行读写操作
(续) for(i=0;i<20;i=i+2) { myio.seekg(pos[i]); //移动文件指针位置至指定位置 myio.read((char *)&dbl,sizeof(double)); //读数据 cout<<″The data is :″<<dbl<<endl; } myio.seekp(0,ios::beg); //将文件指针置于文件开始 for(i=100;i<120;i++) dbl=i/10.0; myio.write((char *)&dbl,sizeof(double)); //向文件中写数据 for(i=1;i<20;i=i+2) myio.seekg(pos[i]); //移动文件指针至指定位置 myio.read((char *)&dbl,sizeof(double)); //从文件中读数据 return 0; //源程序结束
(续) The data is :0.1 The data is :0.3 The data is :0.5 The data is :0.7 The data is :0.9 The data is :1.1 The data is :1.3 The data is :1.5 The data is :1.7 The data is :1.9 The data is :10.2 The data is :10.4 The data is :10.6 The data is :10.8 The data is :11 The data is :11.2 The data is :11.4 The data is :11.6 The data is :11.8
【 11.5 用户自定义类型的输入/输出】 C++语言的I/O流库的一个很重要的特性是能够支持新的数据类型——用 【 11.5 用户自定义类型的输入/输出】 C++语言的I/O流库的一个很重要的特性是能够支持新的数据类型——用 户自定义的类型的输入和输出。 实现这种功能的方式是通过重载插入符<<和提取符>>,重载插入符和提取符的方式与重载其他运算符类似:重载函数具有两个参数,一个是ostream(插入符)/istream(提取符)类的引用,另一个是用户自定义的类的引用,该函数的返回也是一个ostream(插入符)/istream(提取符)类的引用,这样可以保证插入符或者提取符的连续使用。 重载插入符<<和提取符>> 例 11-11 //源程序开始 #include <iostream.h> class Point //声明类Point { private: int x; int y;
重载插入符<<和提取符>> 例 11-11 public: Point(int xx=0,int yy=0) { x=xx; y=yy; } friend istream& operator>>(istream& in, Point& point); //重载>>为友员 friend ostream& operator<<(ostream& out,Point& point); //重载<<为友员 }; istream& operator>>(istream& in,Point& point) //返回istream&可以保证>>的连续使用 cout<<″Please input the coordinate of x:″; in>>point.x; cout<<endl; cout<<″Please input the coordinate of y:″; in>>point.y; return in; 重载插入符<<和提取符>>
(续) ostream& operator<<(ostream& out,Point& point) //返回ostream&可以保证<<的连续使用 { out<<″(″<<point.x<<″,″; out<<point.y<<″)″<<endl; return out; } int main() Point myPoint,myPoint1(10,20); //声明类Point的对象 cout<<″myPoint(x,y)=″<<myPoint<<endl; //自定义数据类型输出 cout<<″myPoint1(x,y)=″<<myPoint1<<endl; cin>>myPoint; //自定义数据类型输入 cin>>myPoint1; cout<<″the new myPoint(x,y)=″<<myPoint<<endl; //自定义数据类型输出 cout<<″the new myPoint1(x,y)=″<<myPoint1<<endl; return 0; //源程序结束
【 11.6综合实例】 编写程序使之具有学生管理的功能, 并将其保存到students.dat文件中 myPoint(x,y)=(0,0) Please input the coordinate of x:1 Please input the coordinate of y:2 Please input the coordinate of x:3 Please input the coordinate of y:4 the new myPoint(x,y)=(1,2) the new myPoint1(x,y)=(3,4) 【 11.6综合实例】 编写程序使之具有学生管理的功能, 并将其保存到students.dat文件中
小结 C++中使用流的概念实现了输入/输出的设备无关性。 标准输入流cin、标准输出流cout分别是ios类派生类 的内置对象,编程者不需要显式定义。 可以使用各种输入/输出成员函数或者操纵子控制数据 输入/输出格式。 对文件的输入/输出需要编程者自己定义对应于该文件的 流对象。