流类库与输入/输出 输入/输出标准流类 文件流类 串流类 输入/输出成员函数 用户自定义类型的输入/输出
输入/输出标准流类 一 、 输入/输出流的概念 一 、 输入/输出流的概念 所谓流,是指数据从一个对象流向另一个对象。在C++程序中,数据可以从键盘流入到程序中,也可以从程序中流向屏幕或磁盘文件,把数据的流动抽象为“流”。流在使用前要被建立,使用后要被删除,还要使用一些特定的操作从流中获取数据或向流中添加数据。从流中获取数据的操作称为提取操作,向流中添加数据的操作称为插入操作。
在C++语言中,针对流的特点,提供了如图9-1所示的层次结构来描述流的行为,并给出了I/O流类库的操作。 图9-1 输入/输出流类层次图
表9-1 I/O流类列表
二、 输入/输出标准流类 1.标准流的设备名 由表9-1可见,I/O流的标准头文件是iostream.h。其中,ostream类通过其派生类ostream_withassign支持以下预先定义的流对象: cout:标准输出。默认设备为屏幕。 cerr:标准错误输出。没有缓冲,发送给它的内容立即被输出,默认设备为屏幕。 clog:标准错误输出。有缓冲,当缓冲区满时被输出,默认设备为打印机。 而istream类通过其派生类istream_withassign支持预先定义的对象。 cin:标准输入。默认设备为键盘。
2.原理 cout是ostream类的全局对象,它在头文件iostream.h中的定义如下: ostream cout(stdout); //这里,stdout作为该对象构造时的参数对应每种基本数据类型,ostream类都存在友元,它们都在iostream.h中声明。例如: ostream& operator<<(ostream &dest,int i); ostream& operator<<(ostream &dest, float f); ostream& operator<<(ostream &dest,const char*psz); //...
如语句: cout<<"How old are you? "; cout是类ostream的对象,<<是插入运算符,右面是char*类型,所以,应该匹配上面第三个操作符。它将整个字符串输出,并返回ostream流对象的引用。 同理,cin是istream的全局对象,istream类也存在友元。例如:istream& operator>>(istream &dest, int &n); istream& operator>>(istream &dest, float &f); istream&operator>>(istream&dest, char*pSource); //...
3. 格式化输入输出 C++提供了两种进行格式控制的方法: 一种是使用ios类中有关格式控制的成员函数进行格式控制; 另一种是使用称为操纵符的特殊类型的函数进行格式控制。 (1) 用ios类的成员函数进行格式控制格式控制主要是通过对状态标志字的操作来完成的。
表9-2 控制输入输出格式的成员函数 函 数 原 型 功 能 long ios∷setf(long flags); 设置状态标志flags 功 能 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,返回当前的设置
表9-2 控制输入输出格式的成员函数 函 数 原 型 功 能 int ios∷precision(int p); 功 能 int ios∷precision(int p); 设置小数位数p,返回当前的小数位数 char ios∷fill(); 返回当前的填充字符 char ios∷fill(char ch); 设置填充字符ch,返回当前的填充字符
【例1】 成员函数进行格式控制。 #include<iostream.h> 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<<"*** x_width=10,x_fill=&, x_precision=4 ***\n"; cout<<"_________________________________\n"; cout<<"*** x_width=10,x_fill=&, x_precision=4 ***\n"; cout.fill('&'); cout.width(10); cout<<123<<" "<<123.45678<<endl; cout.setf(ios::left); cout<<"x_width="<<cout.width()<<endl; cout<<"x_fill="<<cout.fill()<<endl; cout<<"x_precision="<<cout.precision()<<endl; return 0; }
程序运行结果如下: x_width=0 x_fill= x_precision=6 123 123.457 ----------------------------- *** x_width=10, x_fill= , x_precision=4 *** 123 123.5 234.6 x_precision=4
----------------------------- *** x_width=10, x_fill=&, x_precision=4 *** &&&&&&&123 123.5 123&&&&&&& 123.5 x_width=0 x_fill=& x_precision=4
(2)使用称为预定义操纵符进行格式控制 C++提供的预定义操纵符如下: (1) dec 以十进制形式输入或输出整型数,可用于输入或输出。 (2) hex 以十六进制形式输入或输出整型数,可用于输入或输出。 (3) oct 以八进制形式输入或输出整型数,可用于输入或输出。 (4) ws 用于在输入时跳过前导的空白符,可用于输入。 (5) endl 插入一个换行符并刷新输出流,仅用于输出。 (6) ends 插入一个空字符’\0’,通常用来结束一个字符串,仅用于输出 (7) flush 刷新一个输出流,仅用于输出
(8) setbase(int n) 设置转换基格式为为n(n的 取值为0、8、10 或16), n的缺省值为0,即表示采用十进制,仅用于输出。 (9) resetiosflags(long f) 关闭由参数f指定的格式标志,可用于输入或输出 (10) setiosflags(long f) 设置由参数f指定的格式标志,可用于输入或输出 (11) setfill(int ch) 设置ch为填充字符,缺省时为空格,可用于输入或输出 (12) setprecision(int n) 设置小数部分的位数,可用于输入或输出 (13) setw(int n) 设置域宽为n,可用于输入或输出
【例2】操纵符的使用。 #include<iostream.h> #include<iomanip.h> main() {cout<<setw(10)<<123<<567<<endl; //① cout<<123<<setiosflags(ios::scientific)<<setw(20) <<123.456789<<endl; //② cout<<123<<setw(10)<<hex<<123<<endl; //③ cout<<123<<setw(10)<<oct<<123<<endl; //④
cout<<123<<setw(10)<<dec<<123<<endl; //⑤ cout<<resetiosflags(ios::scientific)<<setprecision(4) <<123.456789<<endl; //⑥ cout<<setiosflags(ios::left)<<setfill('#')<<setw(8) <<123<<endl; //⑦ cout<<resetiosflags(ios::left)<<setfill('$')<<setw(8) <<456<<endl; //⑧ return 0; }
程序运行结果为: 123567 ① 123 1.234568e+002 ② 123 7b ③ 7b 173 ④ 173 123 ⑤ 123.5 ⑥ 123##### ⑦ $$$$$456 ⑧
ostream& manip_name(ostream& stream) { … //自定义代码 return stream; } (3) 用户自定义的操纵符 若为输出流定义操纵符函数,则定义形式如下: ostream& manip_name(ostream& stream) { … //自定义代码 return stream; }
用户自定义的操纵符 若为输入流定义操纵符函数,则定义形式如下: istream& manip_name(istream& stream) { … //自定义代码 return stream; }
【例3】为输出流定义操纵符函数。 #include<iostream.h> #include<iomanip.h> ostream& output1(ostream& stream) { stream.setf(ios::left); stream<<setw(10)<<hex<<setfill(’&’); return stream; } int main() { cout<<123<<endl; cout<<output1<<123<<endl; return 0;
【例4】为输入流定义操纵符函数。 #include<iostream.h> #include<iomanip.h> istream& input1(istream& in) { in>>hex; cout<<"Enter number using hex format:"; return in; }
int main() { int i; cin>>input1>>i; cout<<"***"<<hex<<i<<"***"<<dec<<i<<"***"<<endl; return 0; }
4. 重载输出运算符“<<” 和“>>” 定义输出运算符“<<”重载函数的一般格式如下: ostream& operator<<(ostream& out,class_name& obj) { out<<obj.item1; out<<obj.item2; .. . return out; }
重载输出运算符“<<” 和“>>” 定义输入运算符函数 “>>”重载函数的一般格式如下: istream& operator>>(istream& in,class_name& obj) { in>>obj.item1; in>>obj.item2; . . . return in; }
【例5】输入运算符“<<”重载。 #include<iostream.h> class three_d { 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& itput,three_d& ob); private: int x,y,z; };
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; }
main() { three_d obj(10,20,30); // 定义类three_d的对象obj cout<<obj; // 输出对象obj的成员值 cin>>obj; // 输入对象obj的各成员值,将原值覆盖 cout<<obj; // 输出对象obj的成员值(新值) return 0; }
文件流类 fstream、ifstream和ofstream是文件流类,在头文件fstream.h中定义 。其中,fstream是ofstream和ifstream多重继承的子类。文件流类不是标准设备,没有cout那样预先定义的全局对象。文件流类支持对磁盘文件的操作。要定义一个文件流类对象,须指定文件名和打开方式。
类ofstream用于执行文件输出,该类有以下几个构造函数: ofstream::ofstream( filedesc fd ); ofstream::ofstream( filedesc fd, char*pch, int nLength ); ofstream::ofstream( const char*szName, int nMode = ios::out, int nProt = filebuf::openprot );
类ifstream用于执行文件输入,该类有以下几个构造函数: ifstream::ifstream( filedesc fd ); ifstream::ifstream( filedesc fd, char*pch, int nLength ); ifstream::ifstream( const char*szName, int nMode = ios::in, int nProt = filebuf::openprot ); 其中最常用的都是最后一个构造函数。该函数有三个参数,第一个参数是指向要打开的文件名的字符串,后两个参数指定文件的打开模式。
也可以使用open()函数打开文件,也就是使某一文件与上面的某一流相联系。open()函数是上述三个流类的成员函数,其原型是在fstream 也可以使用open()函数打开文件,也就是使某一文件与上面的某一流相联系。open()函数是上述三个流类的成员函数,其原型是在fstream.h中定义的, 原型为: void open(const unsigned char*,int mode,int access=filebuf::openprot); 第一个参数是指向要打开的文件名的字符串,后两个参数指定文件的打开模式。文件打开模式的具体标志见表9-3。可以用按位OR(|)运算符组合这些标志,它们作为枚举器定义在ios类中。
表9-3 文件打开模式
ios::app 打开一个输出文件,用于在文件尾添加数据。 ios::ate 打开一个现存文件(用于输入或输出)并查找到结尾。 ios:: in 打开一个输入文件。对于一个ofstream文件,使用ios::作为一个openmode,可避免删除一个现存文件中现有的内容。 ios::out打开一个文件,用于输出。对于所有ofstream对象,此模式是隐含指定的。
ios::nocreate如果一个文件存在,则打开它;否则该操作失败。 ios::replace 如果一个文件不存在,则作为新文件打开它;如果文件已存在,则该操作失败。 ios::trunc 打开一个文件。如果它已经存在,则删除其中原有的内容。如果指定了ios::out,但没有指定ios::ate、ios::app和ios::in,则隐含为此模式 ios::binary 以二进制模式打开一个文件(默认是文本模式) Nprot是文件保护方式,它的标志如表9-4。
表9-4 文件保护方式 1、文本文件的读写 一旦文件打开了,从文件中读取文本数据与向文件中写入文本数据都十分容易,只需使用运算符“<<”与“>>”就可以了,只是你必须用与文件相联接的流代替cin和cout。(test_file1.cpp)
【例6】把一个整数、一个浮点数和一个字符串写到磁盘文件test中。(前提:文件test所在的路径必须有效) #include<fstream.h> int main() { ofstream fout(“d:\\test_c\\test”) ; if (!fout) //文件打开失败,则流对象的值为0。 //打开前进 行检验 { cout<<"Cannot open output file\n,"; return 1; }
fout<<10<<" "<<123 fout<<10<<" "<<123.456<<"\" This is a text file.\"\n"; fout.close(); return 0; }
注意: ① 这里的文件名要说明其路径,要使用双斜杠,因为C++编译器理解单斜杠为字符转换符。 ② 在文件打开时,匹配了构造函数ofstream::ofstream(char*),只需要一个文件名,其它为默认。打开方式默认为ios::out | ios::trunc,即该文件用于接受程序的输出。如果该文件已存在,则其内容必须先清除,否则就新建。 如果要打开一个同时用于输入和输出的文件,则有 fstream fc("myfile",ios::in | ios::out);
2、 二进制文件的读写 (字节流) 文本文件在输入时,回车和换行两个字符要转换成二进制‘\n’,在输出时,字符要转换成回车和换行,而这些转换在二进制方式下是不转换的。 (1) 用get()函数和put()函数读写二进制文件 get()函数有许多格式,其中最常用的版本原型如下: istream& get(char& ch); get()函数从相关流中只读一个字节,并把该值放入ch中并返回该流,当到达文件尾时,使该流的值为0。
put()函数的原型如下: ostream& put(char ch); put()函数将ch写入流中并返回该流。 ((test_file2.cpp)) 【例7】将‘a’ 至‘z’ 的26个英文字母写入文件,而后从该文件中读出并显示出来。 #include<iostream.h> #include<fstream.h> void test_write() { ofstream fs("d:\\test.txt"); int i; char c='a'; for (i=0;i<26;i++){ fs.put(c); c++; } }
void test_read() { ifstream fs("d:\\test.txt"); char c; while(fs.get(c)) cout<<c; } void main() { test_write(); test_read(); }
(2) 用read()函数和write()函数读写二进制文件 istream &read(unsigned char* buf,int num); ostream &write(const unsigned char* buf,int num); read其功能:从相应的流中读取num个字节(字符),并放入指针buf的缓冲区中。Read的两个参数,buf指向要读入数据的存放地址(输入其它类型时必须要转换);第二个num是要读入的字节数。 write其功能:从buf指的缓冲区把num个字节写到相应的流中。 (test_file2.cpp)
【例8】用write()函数向文件test中写入整数与双精度数。 #include<iostream.h> #include<fstream.h> #include<string.h> main() { ofstream out("test.txt"); if (!out) { cout<<"Cannot open output file.\n"; return 1; }
int i=12340; double num=100.45; out.write((char *) &i,sizeof(int)); out.write((char *) &num,sizeof(double)); out.close(); }
(3) 检测文件结束 在文件结束的地方有一个标志位,记为EOF(End OF)。采用文件流方式读取文件时,使用成员函数eof(),可以检测到这个结束符。如果该函数的返回值非零,表示到达文件尾。为零表示未到达文件尾。该函数的原型是: int eof(); 函数eof()的用法示例如下: ifstream ifs; … if (!ifs.eof()) //尚未到达文件尾 或 if(!ifs)
(4) 文件的随机读写 (fstream) 在C++的I/O系统中有个文件指针,它有两个名字。 其中一个名字叫get指针,用于指出下一次输入操作的位置; 另一个名字叫做put指针,用于指出下一次输出操作的位置。
ostream & seekg(streampos pos); ostream & seekg(streamoff off ,ios::seek_dir dir); ostream & seekp(streampos pos); ostream & seekp(streamoff off ,ios::seek_dir dir); 函数参数中streampos和streamoff相当于long类型,它们分别限定了文件位置以及读写操作的相对偏移的范围。 参数dir表示文件指针的起始位置,off表示相对于这个起始位置的位移量。
由三种情况: 1)ios::beg 从文件头开始,文件指针移动off的距离 2)ios::cur 从当前位置开始,文件指针移动off的距离 3)ios::end 从文件尾开始,文件指针移动off的距离
【例9】将文件test中第五个字符修改成X 。 (test_file3.cpp) #include<iostream.h> #include<fstream.h> void main(void) { fstream fs; fs.open(“d:\\test.txt",ios::in| ios::out); // 以读写方式打开文件 if (fs.fail()) {cout<<"open file errer!"; }
else { fs.seekp(4,ios::beg); // 设置写指针 fs.put('X'); char contents[20]; fs.seekg(0,ios::beg); // 设置读指针 fs.get(contents,20); cout<<contents; }
串流类 strstream、istrstream和ostrstream是串流类,在头文件strstrea.h中定义。同样,串流类也不是标准设备,它没有cout那样预先定义的全局对象。串流类允许将fstream类定义的文件操作应用于存储区中的字符串,即将字符串看作为设备。
要定义一个串流类对象,须提供字符数组和数组大小。类ostrstream用于执行串流输出,该类有以下几个构造函数: ostrstream( char*pch, int nLength, int nMode = ios::out ); 其中比较常用的是第二个构造函数,它有三个参数。第一个参数指出字符数组,第二个参数说明数组的大小,第三个参数指出打开方式。
类istrstream用于执行串流输入,该类有以下几个构造函数: istrstream( char*pch ); istrstream( char*pch, int nLength ); 这两个构造函数都比较常用。Char*pch参数指出了字符数组,int nLength参数说明数组的大小。当nLength为0时,表示把istrstream类对象连接到由pch指向的以空字符结束的字符串。
例如: ostrstream str(ch1,20); //建立一个输出字符串流对象str,并使str与字符数组ch1相关联,即读取str中的字符串并存入字符数组ch1中,流缓冲区大小为20。 istrstream str(ch1); //建立一个输入字符串流对象,将字符数组ch1中的全部数据作为输入字符串流的内容。 istrstream str(ch2,20); //输入字符串流的缓冲区大小为20,因此只将数组ch2中的前20个字符作为输入字符串流的内容。
【例10】 使用串流输入对字符串中的数据进行解读。 #include <iostream.h> #include <strstrea.h> char*ioString(char*pString) { istrstream inS(pString,0); //以ios::in方式 int iNumber; float fNumber;
inS>>iNumber>>fNumber; //从串流中读入一个整数和浮点数 char*Buf1=new char[28]; ostrstream outS(Buf1,28); outS<<"iNumber="<<iNumber<<",fNumber="<<fNumber<<endl; return Buf1; }
#include<iostream.h> #include<strstrea.h> char*ioString(char*); void main( ) { char*str="100 123.456"; char*Buf0=ioString(str); cout<<Buf0<<endl; }
程序运行结果为 iNumber=100,fNumber=123.456 分析:在函数ioString( )中,以pString为输入设备,先定义一个输入串流对象inS,从中输入一个整数和一个浮点数。再开辟一个字符串空间作为输出设备,定义一个输出串流对象outS,将从输入设备输入的两个变量的值输出。
输入/输出成员函数 一、 使用成员函数输入 1.getline( )函数 一、 使用成员函数输入 1.getline( )函数 在程序使用cin输入时,cin用空白符和行结束符将各个值分开。有时候输入可能需要读取一整行文本并且分开不同的域,为此,我们可以使用getline成员函数。其函数原型如下: istream&getline( char*pch, int nCount, char delim = '\n' );
其中,第一个参数是字符数组,用于放置读取的文本;第二个参数是本次读取的最大字符个数;第三个参数是分隔字符,作为读取一行结束的标志。 getline成员函数的功能是允许从输入流中读取多个字符 (包括空白字符和行结束符),并且允许指定输入终止字符 (默认值是换行字符)。在读取完成后,从读取的内容中删除该终止字符。
【例11】 为输入流指定一个终止字符。 本程序连续读入一串字符,直到遇到字符t时停止,字符个数最多不超过99个。程序中的t是大小写敏感的。 #include<iostream.h> void main( ) { char line[100]; cout<<"Type a line terminated by 't'"<<endl; cin.getline(line,100,'t'); cout<<line<<endl; }
在输入时,有些时候需要执行每次只输入单个字符的操作,我们可以使用get( )成员函数来完成。get( )函数的格式如下: char istream::get( ); 【例12】 循环读入字符,直到键入一个y字符,或遇到文件尾。 #include<iostream.h> void main( ) { char letter;
while(!cin.eof( )) { letter=cin.get( ); if(letter=='y') { cout<<"'y'be met!"; break; } cout<<letter; } }
get( )函数还有一种形式可以输入一系列字符,直到输入流中出现结束符或所读字符个数已达到要求读的字符个数。这时,get( )函数的函数原型如下: istream&istream::get(char*,int n,char delim='\n'); 例如,下面程序输入一系列字符,将前24个字符输出。
#include <iostream.h> void main( ) { char line[25]; cout << " Type a line terminated by carriage return\n>"; cin.get( line, 25 ); cout << ' ' << line; }
二、使用成员函数输出 【例13】 使用put( )成员函数,在屏幕上显示字母表中的字母。 #include<iostream.h> void main( ) { char letter; for(letter='A';letter<='Z';letter++) cout.put(letter); }
程序运行结果为 ABCDEFGHIJKMNOPQRSTUVWXYZ 也可以像下面那样在一条语句中连续调用put( )函数: cout.put('A').put('\n'); 该语句在输出字符A后输出一个新换行符。 还可以用ASCII码值表达式调用put( )函数: cout.put(65); 该语句也输出字符A。
I/O流类综合练习 1、编写一个程序,统计文件abc.txt中的英文字母个数。 2、编写一个程序: (1)在二进制文件data.dat中写入3个记录,显示其内容。 (2)删除第二个记录,并显示删除记录后的文件内容。
用户自定义类型的输入/输出 【例14】 用户自定义的插入运算符和提取运算符。(test_type.cpp) #include<iostream.h> class PhoneNumber { private: char nationCode[4]; char areaCode[4]; char phoneCode[8];
用户自定义类型的输入/输出 public: friend ostream& operator<<(ostream&,PhoneNumber&); friend istream& operator>>(istream&,PhoneNumber&); };
ostream& operator<<(ostream& output,PhoneNumber& num) { output<<"("<<num.nationCode<<")" <<num.areaCode<<"-"<<num.phoneCode; return output; } istream& operator>>(istream& input,PhoneNumber& num) { input.ignore( ); //跳过 (
input.getline(num.nationCode,4); input.ignore( ); //跳过 ) input.getline(num.areaCode,4); input.ignore( ); //跳过 - input.getline(num.phoneCode,8); return input;} void main( ) { PhoneNumber phone; cout<<"Enter a telephone number in the" <<" form(086)029-5261111\n";
cin>>phone; cout<<"The phone number entered was:\n" <<phone<<endl; } 程序运行结果为 输入:(086)029-1234567 The phone number entered was: (086)029-1234567 该程序为处理用户自定义的电话号码类PhoneNumber的数据重载了这两个运算符。另外,该程序假定电话号码的输入是正确的。
提取运算符的参数是对istream对象的引用和对自定义类型对象的引用,返回对istream对象的引用。在该程序中,重载的提取运算符用于把形如(086)029-5261111的电话号码输入到PhoneNumber类型的对象中。运算符函数分别将电话号码的三个部分分别读到被引用的PhoneNumber对象的成员nationCode、areaCode和phoneCode中 (在运算符函数中,被引用对象是num;在main函数中,被引用对象是phone)。
调用成员函数ignore( )去掉了括号和破折号。运算符函数返回istream&类型的引用input。通过返回对流引用,我们可以在一条语句中执行完对类PhoneNumber的对象的输入操作后,继续执行对类PhoneNumber的其它对象或其它数据类型的输入操作。如两个PhoneNumber对象可以按下列方式输入: cin>>phone1>>phone2;
插入运算符的两个参数是对ostream对象的引用和对自定义类型(本例中为PhoneNumber)的对象的引用,返回对ostream对象的引用。在该程序中,重载的插入运算符按输入格式显示类PhoneNumber的对象。该运算符函数将电话号码各部分显示为字符串,因为它们是以字符串格式存储的(类istream中的成员函数getline在结束输入后存储一个空字符)。 重载的运算符函数在类PhoneNumber中被声明为友元函数。为了能够访问类中非公有成员,重载的输入和输出运算符必须被声明为类的友元。
编写程序,利用操纵符函数在屏幕上显示一个由字母B组成的三角形。(假设最后一行B显示在屏幕最左边位置) BBB BBBBB BBBBBBB BBBBBBBBB