360 likes | 582 Views
为配合这些错误状态的使用, ios 中还说明了几个成员函数: int ios :: rdstate(); // 读取当前错误状态 int ios :: good(); // state = 0 为真 int ios :: eof(); // state 中 eofbit 置位时为真 int ios :: fail(); // state 中 failbit , badbit , hardfail // 任一个置位时为真 int ios :: bad(); // state 中 badbit , hardfail 任一个
E N D
为配合这些错误状态的使用,ios 中还说明了几个成员函数: int ios :: rdstate(); // 读取当前错误状态 int ios :: good(); // state = 0 为真 int ios :: eof(); // state 中 eofbit 置位时为真 int ios :: fail(); // state 中 failbit,badbit,hardfail // 任一个置位时为真 int ios :: bad(); // state 中 badbit,hardfail 任一个 // 置位时为真 int ios :: clear(int = 0); // 清除指定错误位 例: cin >> i; if(ios :: good()) // 使用变量 i 的值 else ios :: clear(); 注意:clear() 不能清除 state 中的 hardfail 位。
#include <iostream.h> void main() { int i, s; char str[81]; cin >> i; s = cin.rdstate(); while(s) { cin.clear(); cin >> str; cout << "输入错误,重新输入:"; cin >> i; s = cin.rdstate(); } cout << i << endl; }
利用 ios 中重载的运算符 !,可以将上述程序写的更简单: int ios :: operator ! () { return fail(); } void main() { int i; char str[81]; while(!(cin >> i)) { cin.clear(); cin >> str; cout << "输入错误,重新输入:"; } cout << i << endl; } 习题: 20, 30
13.3输入和输出 ios 的派生类 istream 提供了创建输入流以及从流中提取数据的手段; ios 的派生类 ostream 提供了创建输出流以及向流中插入数据的手段; ios 的派生类 iostream 则同时提供了上述两种手段。由于 iostream 的主要作用是用来派生其它类的,在格式化 I/O 中通常不涉及该类。 对于格式化 I/O,类 istream 和 ostream 通过对运算符 “>>”和 “<<” 的多次重载,提供了对 3 大类 13 种数据类型的 I/O操作。 字符型:signed char, unsigned char 字符串型:signed char*, unsigned char* 数值型:short, unsigned short, int, unsigned, long, unsign-ed long, float, double, long double
13.3.1格式化输入 格式化输入是指通过 istream 的对象,利用重载了的 “>>” 运算符来实现的。 最常用的格式化输入是对标准流 cin 的提取操作。在应用程序中也可以定义用户自己的输入流类。但由于定义用户流时将涉及到对流缓冲区 streambuf 的操作,且实用价值不大,因此本课程对此不作介绍。 格式化输入的缺省格式为:十进制(ios :: dec)、跳过前导空白字符(ios :: ws)
13.3.2输入操作函数 int istream :: get(); istream& istream :: get(signed char&); istream& istream :: get(unsigned char&); istream& istream :: get(signed char*, int, char = '\n'); istream& istream :: get(unsigned char*, int, char = '\n'); 后 4个成员函数所以返回对流 istream 的引用,是为了便于进行 “串联” 输入。这实际上是流类成员函数重载的 “标准” 形式。第一个函数的值为一整型数据,其目的是为了能够读取文件结束标志 EOF。该标志是 C++ 语言预定义的一个宏: #define EOF -1
#include <iostream.h> void main() { char ch, str[81]; cin.get(str, 81); cout << str << endl; while(cin.get() != '\n'); // 重要! cin >> ch; cout << ch << endl; }
为了方便字符串的输入,istream 中还说明了几个主要用于输入字符串的非格式化成员函数: istream& istream :: getline(signed char*, int, char = '\n'); istream& istream :: getline(unsigned char*, int, char='\n'); istream& istream :: ignore(int = 1, int = EOF); int istream :: gcount();
#include <iostream.h> void main() { char ch, str[81]; cin.getline(str, 81); cout << str << '\t' << cin.gcount() << endl; cin >> ch; cout << ch << endl; }
13.3.3格式化输出 格式化输出是通过 ostream 类的对象,利用重载了的 “<<” 运算符来实现的。 最常用的输出流对象为 cout,有时,出于某种特殊需要也会用到 cerr 和 clog。程序员也可以定义自己的输出流类,但实用价值不大。 格式化输出的缺省格式为:十进制、域宽为 0、左对齐、空格填充;精度为 6 位小数、浮点数。
13.3.4输出操作函数 ostream& ostream :: put(char); // 输出单个字符 ostream& ostream :: flush(); // 刷新输出流 例: #include <iostream.h> void main() { char ch = 'A'; while(ch <= 'Z') put(ch ++) put('\n'); }
13.3.5重载提取和插入运算符 friend istream& operator >> (istream&, cls&); friend ostream& operator << (ostream&, cls&); 其中:cls 为运算符重载所属类的类名。 从上述运算符重载的一般形式可以看出:提取和插入运算符必须重载为类的友元函数,且函数的返回值必须是对相应类的引用。至于参数,除了要求必须有一个流和一个类(说明运算符重载的类)外,对其类型无严格要求。也就是说,可以是引用、对象甚至指针。但习惯上常用引用,几乎从不使用指针。
#include <iostream.h> class X { int x; public: X(int a = 0) : x(a) {} int Get() { return x; } void Set(int a) { x = a; } friend ostream& operator << (ostream&, X&); friend istream& operator >> (istream&, X&); };
ostream& operator << (ostream& ros, X& rx) { ros << rx.x; return ros; } istream& operator >> (istream& ris, X& rx) { ris >> rx.x; return ris; }
void main() { X aX(10); cout << aX << endl; cin >> aX; cout << aX << endl; } 当编译器遇到语句 cout << aX 时,就将它解释为: operator << (cout, aX); 从而导致函数 operator(ostream&, X&) 的调用。
13.4 文件流 “文件” 这一术语广义地讲是指与计算机交换信息的具体设备,狭义的讲则是专指存放在外存中的、用一个名字标识的一批数据。用于标识这批数据的名字叫做文件名。 C++ 语言的 I/O 流类体系中定义了几个专门用于文件 I/O 的流类,它们均在系统头文件 fstream.h 中说明。
ios streambuf fstreambase filebuf istream ostream ifstream ofstream iostream fstream 13.4.1 C++ 语言的文件流类体系
13.4.2文件的打开与关闭 13.4.2.1 打开文件 文件流类中定义了 4 个用于打开文件的成员函数: void fstreambase :: open(char*, int, int = filebuf :: openprot); void ifstream :: open(char*, int = ios :: in, int); void ofstream :: open(char*, int = ios :: out, int); void fstream :: open(char*, int, int); 在类 ios 中,还定义了一个指定文件打开模式的公有枚举成员: enum open_mode { in = 0x01, out = 0x02, ate = 0x04, app = 0x08, trunc = 0x10, nocreate = 0x20, noreplace = 0x40, binary = 0x80 }
例: ifstream inf; ofstream outf; inf.open("c:\\mydir\\demo.cpp"); // 以文本流打开 demo.cpp。若该文件不存在,则创建它。 inf.open("demo.cpp", ios :: binary); // 以二进制流打开 outf.open("demo.cpp"); /* 以文本流打开 demo.cpp。若该文件不存在,则创建它;若文件已经存在,则将其内容清为 0。*/ outf.open("demo.cpp", ios :: binary | ios :: app); /* 以二进制流打开 deno.cpp。无论该文件是否存在,均将文件写指针移到文件尾部。*/
outf.open("demo.cpp", ios :: nocreate); /* 打开 demo.cpp,若文件不存在则打开操作失败。 */ outf.open("demo.cpp", ios :: noreplace | ios :: app); /* 打开 demo.cpp,并将文件写指针移到文件尾部。若删除参数中的 ios :: app,则表示创建 demo.cpp。 */ outf.open("demo.cpp", ios :: trunc); /* 相当于强行创建demo.cpp。事实上,以输出为目的而打开文件时,缺省包含 ios :: trunc。所以,本例删除第二个参数其作用不变。 */
文件流类中还说明书了 4 个带有缺省参数的构造函数: fstreambase :: fstreambase(char*, int, int = filebuf::openprop); ifstream :: ifstream(char*, int = ios :: in, int); ofstream :: ofstream(char*, int = ios :: out, int); fstream :: fstream(char*, int, int); 利用这些构造函数,就可以在创建对象的同时打开文件: ifstream inf("demo.cpp"); ofstream outf("demo.cpp");
13.4.2.2 关闭文件 void fstreambase :: close(); 该成员函数被 fstreambase 的所有子类所继承,因此所有文件流对象均可以利用该函数来关闭文件。例 inf.close(); outf.close(); 注意:对于以写为目的而打开的文件,建议最好在对文件访问结束后显式地调用该函数关闭文件,以防止数据的丢失。
13.4.3文件的访问 访问文件无非就是从文件中读取数据和向文件中写入数据。对于文本流,最为直接的手段就是利用提取或插入运算符对文件进行访问。然而,由于这两个运算符的某些缺省格式控制设置可能会导致文件中一些信息的丢失,在实用中常常还是使用其它一些流读写函数为好。
// FILEDEMO.CPP #include <iostream.h> #include <fstream.h> #include <stdlib.h> void main() { fstream inf, outf; char ch; inf.open("filedemo.cpp", ios :: in); outf.open("demofile.cpp", ios :: out); if(!inf || !outf) { cout << "打开文件失败!" << endl; exit(1); }
do { // A inf >> ch; if(!inf.good()) break; outf << ch; } while(1); // B inf.close(); outf.close(); } //FILEDEMO.CPP#include<iostream.h>#include<fstream.h># include<stdlib.h>voidmain(){fstreaminf,outf;charch;inf.open("t est1.cpp",ios::in);outf.open("test2.cpp",ios::out);if(!inf||!outf){co ut<<"打开文件失败!"<<endl;exit(1);}do{inf>>ch;if(!inf.go od()) break;outf<<ch;}while(1);inf.close();outf.close();}
将上述程序从行 A 到行 B 改为: while(inf.get(ch)) outf.put(ch); 则最后生成的文件副本 demofile.cpp 将与原件的内容完全相同。
13.5几个主要用于文件流的函数 13.5.1 文件读写函数 istream& istream :: read(signed char*, int); istream& istream :: read(unsigned char*, int); ostream& ostream :: write(signed char*, int); ostream& ostream :: write(unsigned char*, int); int ios :: eof(); 最后一个函数是类 ios 中说明的成员函数,该函数在进行文件读操作时用来判断文件是否已经结束。前面曾介绍过:当逐字节地读文件时,若当前读到的字符为 EOF,则说明文件已经结束。但对于二进制文件来说,该宏将则有二义性。因为 -1 在二进制文件中可能是一个有效数据,EOF 宏并不能标识出二进制文件的结束。对于二进制文件,eof() 函数可能是唯一判断文件结束的手段。该函数对文本文件同样有效。
// FILEDEMO.CPP #include <iostream.h> #include <fstream.h> #include <stdlib.h> void main() { fstream inf, outf; char ch[1024]; inf.open("test1.cpp", ios :: in | ios :: binary); outf.open("test2.cpp", ios :: out | ios :: binary); if(!inf || !outf) { cout << "打开文件失败!" << endl; exit(1); }
do { inf.read(ch, 1024); if(inf.eof()) { outf.write(ch, inf.gcount()); break; } outf.write(ch, 1024); } while(1); inf.close(); outf.close(); }
13.5.2文件随机访问函数 istream& istream :: seekg(streampos); istream& istream :: seekg(streamoff, seek_dir); streamops istream :: tellg(); ostream& ostream :: seekp(streampos); ostream& ostream :: seekp(streamoff, seek_dir); streamops ostream :: tellp(); 其中,streampos 和streamoff 为 ios 中定义的数据类型: typedef long streampos; typedef long streamoff; 而 seek_dir 则是ios 中说明的一个公有枚举: enum seek_dir{beg = 0, cur = 1, end = 2};
inf.seekg(100); // 将文件读指针移到第 100个字节处 inf.seekg(100, ios :: beg); // 同上 outf.seekp(sizeof(Person), ios :: cur); /* 从文件中当前位置将写指针向文件尾部移动一个 Person 大小的位置 */ inf.seekg(-20, ios :: cur); // 将读指针向文件头部移动 20 字节 outf.seekp(-100, ios :: end); /* 将写指针从文件尾部向文件头部方向移动 100 字节 */
13.6 文本文件和二进制文件 13.6.1 文本文件 将数据以文本格式存盘,其优点是文件具有很高的兼容性。文本文件可以利用任何一个文字处理器对其进行阅读、编辑、修改和打印输出。其缺点是,数值数据所需的存储空间比二进制文件来得大,且相互之间必须人为地添加分隔符,否则将无法正确地读出。一般讲,对于包含有数值数据的文件,最好选择二进制格式;对于纯字符文件,可以选择文本格式。
#include <fstream.h> #include <stdlib.h> void main() { ofstream of("test2.txt"); if(!of) { cout << "建立输出文件失败!" << endl; exit(1); } for(int i = 1; i <= 5; i ++) of << i; of.close(); }
void main() { ifstream inf("test2.txt"); int n; if(!inf) { cout << "打开输入文件失败!" << endl; exit(1); } while(1) { inf >> n; if(inf.eof()) break; cout << n << " "; } cout << endl; inf.close(); } 该程序的输出为: 12345
void main() { ofstream of("test2.txt"); if(!of) { cout << "建立输出文件失败!" << endl; exit(1); } for(int i = 1; i <= 5; i ++) of << i << ' '; of.close(); } 写数据程序经过这样的修改 后,上述读数据程序的输出 就变为: 1 2 3 4 5
13.6.2二进制文件 以二进制格式存盘的文件其缺点是无可读性(除非文件中的内容为纯字符),且兼容性较差;其优点是节省存储空间、读写速度高、便于随机访问。对于数值数据,特别是结构变量和对象等数据,二进制文件将是最佳选择。