第十一章 流类库和输入 /输出
本章的目的就是就是介绍在 C++语言环境下, 数据的输入和
输出机制 。
在 C++语言中,数据的输入和输出(简写为 I/O)包括对
标准输入设备(键盘)和标准输出设备(显示器)(标准
I/O )、对在外存磁盘上的文件(文件 I/O)进行输入输出这两
个方面。
C++中把数据之间的传输操作称作 流, 流是一种抽象的概
念,负责在数据的产生者和数据的使用者之间建立联系,并管理
数据的流动,流既可以表示数据从内存传送到某个载体或设备中,
即 输出流 ;也可以表示数据从某个载体或设备传送到内存缓冲区
变量中,即 输入流 。
C++语言中并没有输入 /输出语句,而是在标准库里包含
了一个 I/O流类库,它与标准模板库同为 C++标准库中最重要
的组成部分。
第十一章 流类库和输入 /输出
11.1 C++的基本流类体系
11.3 流的格式化控制
11.2 标准流对象
11.4 文件的输入与输出
11.5 检测流错误操作
11.6 定向打印输出
11.1 C++的基本流类体系
C++为实现数据的输入和输出定义了一个庞大的类库,
它包括的类主要有:
streambuf类:用以管理流的缓冲区 。
ios类:它为根基类, 提供格式, 错误检测和状态信息 。
ostream类:用来创建输出流 。
istream类:用来创建输入流 。
iostream类:用来创建输入输出流 。
ifstream,ofstream,fstream类:用于文件 。
istream_withassign 类:标准输入流类 。
ostream_withassign 类:标准输出流类 。
其中 ios为根基类, 是 istream和 ostream的虚基类, 而
iostream是 istream和 ostream的公共派生类 。
使用标准的输入输出流类必须包含头文件 iostream.h
使用文件的输入输出流类必须包含头文件 fstream.h
11.1 C++的基本流类体系
整个流类体系是一个派生类层次体系。它的结构如下:
输入 /输出流类派生体系
ios streambuf
istream ostream
istream_withassign iostream ostream_withassign
iostream_withassign
指针
11.2 标准流对象
C++不仅定义有现成的 I/O类库供用户使用,而且还为
用户进行标准 I/O操作预定义了四个类对象,它们分别是 cin,
cout,cerr和 clog,定义如下:
extern istream_withassign cin;
extern ostream_withassign cout;
extern ostream_withassign cerr;
extern ostream_withassign clog;
cin为 istream_withassign流或标准输入流的对象,后三个
为 ostream_withassign流类的对象,cout代表标准输出设
备显示器,为标准输出流,cerr和 clog含义相同,均代表错
误信息输出设备显示器。因此当进行键盘输入时使用 cin流,
当进行显示器输出时使用 cout流,当进行错误信息输出时使
用 cerr(无缓冲)或 clog(有缓冲)。
11.2 标准流对象 cin
类 istream的定义:
class istream:virtual public ios {
public:
istream(streambuf *);
istream & get(signed char *,int,char=?\n?);
istream & get(signed char *,int,char=?\n?);
istream & get(signed char &);
istream & get(unsigned char &);
int get();
istream & getline(signed char *,int,char=?\n?);
istream & getline(unsigned char *,int,char=?\n?);
istream & read(signed char *,int,);
……
int peek();
istream & operator >> (signed char *);
istream & operator >> (signed char &);
istream & operator >> (int &);
……
};
11.2 标准流对象 cin
C++的流通过重载运算符, <<”和, >>”执行输入和输出
操作 。 输出操作是向流中插入一个字符序列, 因此, 在流操作
中, 将运算符, <<”称为插入运算符 。 输出操作是从流中提取
一个字符序列, 因此, 将运算符, >>”称为提取运算符 。
在 istream输入流类中定义有对右移操作符 >>重载的一组
公用成员函数, 函数的具体声明格式为:
istream & operator >>( 简单类型标识符 &) ;
简单类型标识符可以为 char,unsigned char,short,
unsigned short,int,long,unsigned long,float,
double,long double,char *,unsigned char *之中的
任何一种,对于每一种类型都对应着一个右移操作符重载函数。
由于右移操作符重载用于给变量输入数据的操作,所以又称为
提取操作符,即从流中提取出数据赋给变量。
cin >> 变量 相当于 cin.operator >> (变量)
cin >> k; cin.operator >> ( k) ;
11.2 标准流对象 cin
char c;
int k;
float f;
cin >> k >> c >> f;
cin.operator>>(k),operator>>(k).operator>>(k);
另外, 我们也可以直接利用 istream中的公用成员函数来进
行数据的输入, 如:
void main() // 11-2-1.cpp
{ int i = 0; char buf[20];
while(i<20 && (buf[i]=cin.get()) !=?\n?) i ++;
buf[i] = ?\0?; cout << buf << endl;
} // while(i<20 && cin.get(buf[i]) && buf[i] != ?\n?) i ++;
对象引用
11.2 标准流对象 cin
标准设备输入,注意以下几点,可以避免错误。
1,cin为 缓冲流 。键盘输入的数据保存在缓冲区中,当要提取
时,是从缓冲区中拿。如果一次输入过多,会留在那儿慢慢
用,如果输入错了,必须在 回车之前修改,如果回车键按下
就无法挽回了。只有把输入缓冲区中的数据取完后,才要求
输入新的数据。不可能用刷新来清除缓冲区,所以 不能输错,
也不能多输 !
2,输入的数据类型必须与要提取的数据类型一致,否则出错。
3,空格和回车都可以作为数据之间的分格符,所以多个数据可
以在一行输入,也可以分行输入。但如果是 字符型和字符串,
则 空格 ( ASCII码为 32) 无法用 cin输入,字符串中也不能
有空格。回车符也无法读入。
读空格符,cin.getline(str,SIZE);
11.2 标准流对象 cin
#include <iostream.h> // 11-2-2.cpp
const int N=40;
void main()
{ char s1[N],s2[N];
cout <<,Enter a String:”;
cin >> s1; cout <<,S1=,<< s1 << endl;
cin.getline(s2,N); // cin.get(s2,N);
cout <<,S2=,<< s2 << endl;
}
输入,get put read write
结果是,S1=get
S2= put read write
编程计算从键盘上输入的单词个数,并从中找出最长的单词,
输出单词个数和最长单词和它的长度 CTRL+z 结束输入
#include <iostream.h> // 11-2-21.cpp
void main()
{ char buf[80],maxWord[20];
int curLen,maxLen = 0,cnt = 0;
cout <<,请输入一句话:” ;
while(cin >> buf) {
curLen = strlen(buf); cnt ++;
if(curLen > maxLen) {
maxLen = curLen;
strcpy(maxWord,buf);
}
}
cout << endl <<,单词个数:” << cnt << endl;
cout <<,最长单词:” << maxWord
<<,,长度:” << maxLen << endl;
}
11.2 标准流对象 cout
类 ostream的定义:
class ostream:virtual public ios {
public:
ostream(streambuf *);
ostream & flush();
ostream & put(char);
ostream & write(const signed char *,int,);
ostream & write(const unsigned char *,int,);
……
streampos tellg();
ostream & operator << (short);
ostream & operator << (int);
ostream & operator << (float);
……
};
11.2 标准流对象 cout
在 ostream输出流类中定义有对左移操作符 <<重载的一组公用
成员函数, 函数的具体声明格式为:
ostream & operator <<(简单类型标识符);
cout <<,Sum=,<< 100;
简单类型标识符除了与在 istream流类中声明右移操作符重载函
数给出的所有简单类型标识符相同以外,还增加一个 void * 类
型,用于输出任何指针(但不能是字符指针,因为它将被作为字
符串处理,即输出所指向存储空间中保存的一个字符串)的值。
由于左移操作符重载用于向流中输出表达式的值,所以又称为插
入操作符。另外,要注意输出表达式中运算符的优先级如果低于
<<,那么要括号把表达式括起来。 如:
cout << i>j?i:j << endl; cout << (i>j?i:j) << endl;
对象引用
11.2 标准流对象 cout
直接利用 ostream类中的函数进行输出:
#include <iostream.h> // 11-2-3.cpp
void main()
{ char *s =,This is a string.”;
int x = 10,*p = &x;
cout.put(*s).put(?A?);
cout << endl;
cout.write(s+10,3); cout << endl;
cout << x <<, Address:” << p << endl;
}
结果是,TA
str
10 Address,0x0012FF78
11.2 标准流对象 cerr和 clog
cerr:
cerr类似标准错误文件 。 cerr与 cout的差别在于:
( 1) cerr是不能重定向的;
( 2) cerr不能被缓冲, 它的输出总是直接传达到标准输
出设备上 。
cerr <<,Error”<<,\n”;
clog:
clog是不能重定向的, 但是可以被缓冲 。
clog <<,Error”<<,\n”;
11.3 流的格式化控制
在数据的输入输出中, 常常需要准确地控制数据的输入输
出格式 。 在 C++中, 完成格式化输入输出有两种方式, 一是
使用 ios类中的成员函数, 二是使用输入输出操作符 。
ios类中的成员函数:
每一个输入或输出流都有一个当前数据格式状态字
x_flags( 长整数 ), 在根基类 ios中定义, 该字称为 格式化标
志字 ( formatting flag), 在根基类 ios的派生类 istream和
ostream中可以用 ios::x_flags加以引用 。 x_flags的每一位
都分别表示一种格式状态, 1表示采用, 0表示不采用 。 而
C++把所有的输入输出格式状态编码为一个枚举类型的数据
定义, 该枚举是用于设置控制输入输出格式的标志使用的 。 枚
举类型定义如下:
11.3 流的格式化控制
格式控制符定义为公有的无名的枚举类型:
enum{
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
};
I
O
O
O
I/O
I/O
I/O
O
O
O
O
O
O
O
O
11.3 流的格式化控制
各枚举常量的详细含义如下:
( 1) skipws
利用它设置对应标志后, 从流中输入数据时跳过当前位置
及后面的所有连续的空白字符, 从第一个非空白字符起读数,
否则不跳过空白字符 。 空格, 制表符 ‘ \t?,回车符 ‘ \r?和换
行符 ‘ \n?统称为空白符 。 默认为设置 。
( 2) left,right,internal
left在指定的域宽内按左对齐输出,right按右对齐输出,
而 internal使数值的符号按左对齐、数值本身按右对齐输出。
域宽内剩余的字符位置用填充符填充。默认为 right设置。在
任一时刻只有一种有效。
( 3) dec,oct,hex
设置 dec对应标志后, 使以后的数值按十进制输出, 设
置 oct后按八进制输出, 而设置 hex后则按十六进制输出 。 默
认为 dec设置 。
11.3 流的格式化控制
( 4) showbase
设置对应标志后使数值输出的前面加上, 基指示符,,
八进制数的基指示符为数字 0,十六进制数的基指示符为 0x,
十进制数没有基指示符 。 默认为不设置, 即在数值输出的前
面不加基指示符 。
( 5) showpoint
强制输出的浮点数中带有小数点和小数尾部的无效数字
0。默认为不设置。
( 6) uppercase
使输出的十六进制数和浮点数中使用的字母为大写 。 默
认为不设置 。 即输出的十六进制数和浮点数中使用的字母为
小写 。
( 7) showpos
使输出的正数前带有正号, +”。 默认为不设置 。 即输出
的正数前不带任何符号 。
11.3 流的格式化控制
( 8) scientific,fixed
进行 scientific设置后使浮点数按科学表示法输出,进行
fixed设置后使浮点数按定点表示法输出。只能任设其一。缺
省时由系统根据输出的数值选用合适的表示输出。
( 9) unitbuf,stdio
这两个常量很少使用, 所以不予介绍 。
该枚举量说明中每一个枚举量实际对应 x_flags中的每一
个位,所以可以同时采用几个格式控制,只要把对应位置 1即
可,这样既方便又节约内存。取多种控制时,用或,|”运算
符来合成,合成为一个长整型数,在 ios中为:
protected:
long x_flags;
访问 x_flags的重载函数定义如下:
11.3 流的格式化控制
long ios::flags() //返回当前标志字
long ios::flags(long flags) //设置新标志,并返回原标志字
long ios::setf(long setbits) //设置对应的控制位(多项)
long ios::setf(long setbits,long delbits)
//第一个参数为需设置的控制位,第二个参数表示清除的控制位
long ios::unsetf(long delbits) //清除指定位上的控制
int ios::width() //返回当前域宽
int ios::width(int w) //把参数作为新的域宽,返回原域宽
char ios::fill() //返回当前填充字符
char ios::fill(char ch) //参数作为新填充字符,返回原填充字符
int ios::precision() //返回当前精度
int ios::precision(int p) //参数作为新精度,返回原精度
默认情况下,数据输出宽度为该数据所需的最小字符数
默认情况下,填充字符为空格符
单精度浮点 7位有效数字,双精度浮点 15位有效数字
11.3 流的格式化控制
#include <iostream.h> // 11-3-1.cpp
void showflags(long f)
{ for(long i= 0x4000; i; i = i>>1)
cout <<((i&f)?”1”:”0”);
cout << endl;
}
void main()
{ const int i = 156;
char c1 = ?a?,c2 = ?b?;
showflags(cout.flags());
cout<<“i=“<<i<<“,c1=“<<c1<<“,c2=“<<c2<<endl;
showflags(cin.flags(cin.flags()&~ios::skipws));
cin>>c1>>c2;
showflags(cin.flags());
cout<<“i=“<<i<<“,c1=“<<c1<<“,c2=“<<c2<<endl;
11.3 流的格式化控制
showflags(cout.flags(cout.flags()
|ios::uppercase|ios::hex));
showflags(cout.flags());
cout<<“i=“<<i<<“,c1=“<<c1<<“,c2=“<<c2<<endl;
}
用 ios::flags设置一个状态位 fmt,flags|fmt
用 ios::flags清除一个状态位 fmt,flags&~fmt
用 ios::setf和 ios::unsetf()来设置和清除一个状态位:
ios::setf(fmt);
ios::setf(0,fmt);
ios::unsetf(fmt)
fmt为 ios::skipws,ios::dec 等等
11.3 流的格式化控制
为了方便使用,在 ios类中定义了如下静态存储对象
static const long adjustfield left|right|internal
static const long basefield del|oct|hex
static const long floatfield scientific|fixed
void main(void) // 11-3-2.cpp
{ int inum=255;
double fnum=31.415926535;
cout<<“十进制方式” <<inum<<?\t?;
cout.flags(ios::oct|ios::showbase);
//八进制带数制基数输出是前面加 0,参数等效 0x00a0
cout<<“八进制方式” <<inum<<?\t?;
cout.setf(ios::hex);
//等效 0x0040,因是或关系,仍带基数输出,格式为 0x...
cout<<“十六进制方式” <<inum<<endl;
cout.flags(ios::dec|ios::showbase);
11.3 流的格式化控制
cout<<“缺省域宽为,”<<cout.width()<<“位” <<?\n?;
cout<<“缺省精度为,”<<cout.precision()<<“位” <<?\n?;
cout<<“缺省表达方式,”<<funm<<?\n?;
//按值大小,自动决定定点还是科学数
cout.setf(ios::scientific,ios::floatfield);
cout<<”科学数表达方式,”<<fnum<<?\n?;
cout.setf(ios::fixed,ios::floatfield); //设为定点,取消科学
数方式
cout<<”定点表达方式,”<<fnum<<?\n?;
cout.precision(9); //精度为 9位,小数点后 9位
cout.setf(ios::scientific,ios::floatfield);
cout<<”定点 9位科学数表达方式,”<<fnum<<endl
cout.width(10); cout <<,abcdef” << endl;
cout.width(10); cout.fill(?#?); cout<<“abcdef”<<endl;
}
11.3 流的格式化控制
格式控制操作符:
数据输入输出的格式控制还有更简便的形式,就是使用
系统头文件 iomanip.h中提供的操纵符也称操纵算子。使用
这些操纵符不需要调用成员函数,只要把它们作为插入操作
符 <<(个别作为提取操作符 >>)的输出对象即可。这些操
纵符及功能如下:
操作符 含义
showpos 在非负数值中显示 +
*noshowpos 在非负数值中不显示 +
*skipws 输入操作符跳过空白字符
noskipws 输入操作符不跳过空白字符
uppercase 在十六进制下显示 0X,科学计数法中显示 E
*nouppercase 在十六进制下显示 0x,科学计数法中显示 e
*dec 以十进制显示
hex 以十六进制显示
oct 以八进制显示
left 将填充字符加到数值的右边
right 将填充字符加到数值的左边
Internal 将填充字符加到符号和数值的中间
*fixed 以小数形式显示浮点数
scientific 以科学计数法形式显示浮点数
flush 刷新 ostream缓冲区
操作符 含义
ends 插入字符串结束符,然后刷新 ostream缓冲区
endl 插入换行符,然后刷新 ostream缓冲区
ws,吃掉”空白字符
//以下这些参数化的流操作子要求 #include<iomanip.h>
setiosflags(f) 设置 f的标志位
resetiosflags(f) 清除 f的标志位
setfill(ch) 用 ch填充空白字符
setprecision(n) 将浮点精度设置为 n
setw(n) 按照 w个字符来读或者写
setbase(b) 以进制基数 b为输出整数值
注,*表示缺省的流状态
11.3 流的格式化控制
// 11-3-3.cpp
cout << hex << 100 << endl; // 以 16进制输出 100
cout << dec << 100 << endl;
cout << oct << 100 << endl;
cout << setw(10) <<,abcdef” << endl;
cout <<,abcdef” << endl;
cout << setw(10) << setfill(?$?)<<,12345” << endl;
显示如下图形:
*
* * *
* * * * *
* * * * * * *
* * * * * * * * *
* * * * * * * * * * *
* * * * * * * * * * * * *
* * * * * * * * * * *
* * * * * * * * *
* * * * * * *
* * * * *
* * *
*
#include <iostream.h> // 11-3-3.cpp
#include <iomanip.h>
void main()
{ int n;
cout << "请输入层数,";
cin >> n;
for(int i = n; i > 1; i --)
cout << setfill(' ') << setw(i) << " " <<
setfill('*') << setw(2*n-2*i+1) << "" << endl;
for(i = 1; i < n+1; i ++)
cout << setfill(' ') << setw(i) << " " <<
setfill('*') << setw(2*n-2*i+1) << "" << endl;
}
11.4 文件的输入与输出
文件的概念:
在磁盘上保存的信息是按文件的形式组织的,每个文件都
对应一个文件名,并且属于某个物理盘或逻辑盘的目录层次结
构中一个确定的目录之下。一个文件名由 文件主名和扩展名 两
部分组成,它们之间用圆点(即小数点)分开,扩展名可以省
略。文件主名是由用户命名的一个有效的 C++标识符,同时为
了便于记忆和使用,最好使文件主名的含义与所存的文件内容
相一致。
文件扩展名也是由用户命名的,1至 3个字符组成的、有效
的 C++标识符,通常用它来区分文件的类型。如在 C++系统中,
用扩展名 h表示头文件,用扩展名 cpp表示程序文件,用 obj表
示程序文件被编译后生成的目标文件,用 exe表示连接整个程
序中所有目标文件后生成的可执行文件。对于用户建立的用于
保存数据的文件,通常用 dat表示扩展名,若它是由字符构成
的文本文件则也用 txt作为扩展名等等。
11.4 文件的输入与输出
在 C++程序中的文件按存储格式分为两种类型,一种为字
符格式文件,简称 文本文件,又称 ASCII码文件 ;另一种为内
部格式文件,简称 二进制文件 。在 文本文件 中,每个字节单元
的内容为字符的 ASCII码,被读出后能够直接送到显示器或打
印机上显示或打印出对应的字符,供人们直接阅读。在 二进制
文件 中,文件内容是数据的内部表示,即是在内存中存放格式。
当然对于字符信息,两者表示是一样的,而对于数值信息,用
文本文件和二进制文件显然不同。
在 C++程序中使用文件是通过 文件流 的方式来进行,首先
要在程序开始包含 fstream.h 文件。由它提供输入文件流类
ifstream、输出文件流类 ofstream和输入输出文件流类
fstream来定义用户所需要的文件流对象,然后利用该对象调
用相应类中的 open成员函数,按照一定的打开方式打开一个命
名的文件。文件被打开后,就可以通过流对象访问它了,访问
结束后再通过流对象关闭它。
11.4.1 文件的打开与关闭
文件的使用步骤如下:
1,说明一个文件流对象:
ifstream ifile; //只输入用
ofstream ofile; //只输出用
fstream iofile; //既输入又输出用
2.使用文件流对象的成员函数打开一个磁盘文件。这样在文件
流对象和磁盘文件名之间建立联系。打开文件的成员函数原型:
void open(const char *filename,int filemode,
int=filebuf::openprot);
第一个参数为要打开的磁盘文件名,必要时候要求绝对路径。第
二个参数为打开方式,有输入( in)、输出( out)、二进制方式
( binary)等,打开方式在 ios基类中定义为枚举类型。第三个参
数为指定打开文件的保护方式,一般取缺省 。
例如,iofile.open(“myfile.txt”,ios::in|ios::out);
11.4.1 文件的打开与关闭
文件打开方式 是在 ios类中定义的, 公有枚举成员:
enum open_mode{
in=0x01,//打开文件用于读操作, 文件指针在文件头
out=0x02,//打开文件用于写操作 。 如文件不存在, 则建
立, 但指定目录必须存在, 否则建立文件失败 。
如文件存在, 未同时设 app,ate,in则文件清空
ate=0x04,//打开文件用于读 /写, 文件指针在文件尾
app=0x08,//打开文件用于写, 但从尾部添加,
新数据只能添加在尾部
trunce=0x10,//打开文件, 并清空它, 以建立新文件
nocreate=0x20,//如文件存在则打开, 不存在打开失败
noreplace=0x40,//如文件不存在则创建, 如文件存在,
除非设置 ate及 app方式, 否则打开失败 。
binary=0x80 //以二进制方式打开文件
}; 使用方法与格式控制符相同, 引用时候采用 ios::mode或者
它们的组合, 用 |连接, 如,ios::in,ios::out|ios::binary 等
11.4.1 文件的打开与关闭
对于 ifstream 流, 打 开 方式 默 认值 为 ios::in;对于
ofstream流, 打开方式默认值为 ios::out。
保护方式 openprot参数决定文件的存取属性, 取值为:
0 普通文件 1 只读文件
2 隐含文件 4 系统文件
8 档案文件
一般情况下,该访问方式使用默认值。
注意:打开文件操作并不能保证总是正确的,如文件不存
在、磁盘损坏等原因可能造成打开文件失败。如果打开文件失
败后,程序还继续执行文件的读 /写操作,将会产生严重错误。
在这种情况下,应使用异常处理以提高程序的可靠性。
如果使用构造函数或 open()打开文件失败,流状态标志
字中的 failbit,badbit或 hardbit将被置为 1,并且在 ios类中
重载的运算符“!”将返回非 0值。通常可以利用这一点检测
文件打开操作是否成功,如果不成功则作特殊处理。
11.4.1 文件的打开与关闭
三个文件流类都重载了一个带缺省参数的 构造函数, 功
能与 open函数一样:
ifstream::ifstream(const char *,int=ios::in,
int=filebuf::openprot);
ofstream::ofstream(const char *,int=ios::out,
int=filebuf::openprot);
fstream::fstream(const char*,int,
int=filebuf::operprot);
所以建立一个可读写的文件流对象可以:
fstream iofile(, myfile.txt”,ios::in|ios::out) ;
所以建立一个可读的文本文件流对象可以:
ofstream ifile(, msg.dat”) ;
所以建立一个可写的文本文件流对象可以:
ofstream ofile(, stud.dat”) ;
11.4.1 文件的打开与关闭
打开文件也应该判断是否成功,若成功,文件流对象值为非零值,
不成功为 0( NULL) 。
因此打开一个文件完整的程序为:
fstream iofile(, myfile.txt”,ios::in|ios::out) ;
if(!iofile) {
cout<<”不能打开文件,”<<”myfile,txt”<<endl;
exit(1); //失败退回操作系统
}
文件关闭:
每个文件流类中都提供有一个关闭文件的成员函数 close
( ), 当打开的文件操作结束后, 就需要关闭它, 使文件流与对
应的物理文件断开联系, 并能够保证最后输出到文件缓冲区中的
内容, 无论是否已满, 都将立即写入到对应的物理文件中 。 文件
流对应的文件被关闭后, 还可以利用该文件流调用 open成员函
数打开其他的文件 。 文件流对象,close();
11.4.2 文本文件的读写
文件的访问:
在 C++的文件流中实际上没有特定的文件读写函数来进行
数据的读写,对文件的访问一般是通过调用各个流的基类
( ios,istream,ostrem)所提供的方法来实现。
( 1)使用流运算符直接读写
文件的读操作就是从文件流中 提取( >>) 一个数据,而文
件的写操作就是向文件流中 插入( <<) 一个数据。
( 2) 使用流成员函数
常用的输出流成员函数为,put函数, write函数等
常用的输入流成员函数如下,get函数, getline函数,
read函数等
11.4.2 文本文件的读写
文本文件的读写:
例子:将一个整数文件中的数据乘以 10以后写到另外文件
#include <iostream.h> // 11-4-2-1.cpp
#include <fstream.h>
#include <stdlib.h>
void main()
{ char filename[10];
cout <<,输入文件名:” ;
cin >> filename;
ifstream ifile(filename,ios::in|ios::nocreate);
if(! ifile) {
cout <<,文件不存在” << endl;
exit(0);
}
11.4.2 文本文件的读写
ofstream ofile(“result.txt”);
if(! ofile) {
cout <<,Result.dat 文件不能建立!” << endl;
exit(0);
}
int n;
while(ifile >> n) ofile << 10 *n;
ifile.close();
ofile.close();
}
11.4.2 文本文件的读写
编写程序:用来将某校的本科生和硕士生的有关信息保存到指定
文件。本科生包括姓名、学号和某门科成绩,而硕士生信息除了
本科生资料之外,还增加导师姓名。
#include <iostream.h> // 11-4-2-2.cpp
#include <fstream.h>
#include <string.h.h>
#include <iomanip.h>
#include <stdlib.h>
class Student {
public:
Student(char *pN,long num,double g)
{ strcpy(Name,pN); uID = num; score = g }
virtual void Print(ostream &out);
friend ostream &operator << (ostream &out,
Student &st);
private:
char Name[80]; long uID; double score;
};
11.4.2 文本文件的读写
void Student::Print(ostream &out)
{ out.setf(ios::left,ios::adjustfield);
out.width(15); out << Name << uID;
out.setf(ios::right,ios::adjustfield);
out.width(8); out << score;
}
ostream &operator << (ostream &out,Student &st)
{ st.Print(out); out << endl; return out; }
class Master:public Student {
public:
Master(char *pN,long num,double g,char *dN):
Student(pN,num,g)
{ strcpy(dName,dN) }
void Print(ostream &out);
private:
char dName[8];
};
11.4.2 文本文件的读写
void Master::Print(ostream &out)
{ Student::Print(out);
out <<,, << dName;
}
void main()
{ ofstream out(“abc.txt”);
Student s1(“Wang Ping”,88001,96.5);
Master s2(“Liu Hong”,99055,86.2,“Hu”);
Master s3(“Li Jun”,99078,73.6,“Huang”);
out << s1;
out << s2;
out << s3;
}
11.4.2 文本文件的读写
例子:文件拷贝:
void main() //11-4-2-3.cpp
{ ifstream sfile(“abc.txt");
ofstream dfile(“result.txt");
char ch;
if(!sfile)
{ cout<<"不能打开源文件,"<<endl; exit(1); }
if(!dfile)
{ cout<<"不能打开目标文件,"<<endl;exit(1); }
sfile.unsetf(ios::skipws); //关键 ! 把跳过空白控制位置 0,
即不跳过空白,否则空白全部未拷贝
while(sfile>>ch)
dfile<<ch;
sfile.close(); //如没有这两个关闭函数,析构函数也可关闭
dfile.close();
}
while(sfile.getline(buf,100))
{ if(sfile.gcount()<100)
dfile<<buf<<?\n?;
//因回车符未送到
else dfile<<buf;
}
11.4.2 文本文件的读写
该程序中,首先 必须设置关闭跳过空白,因为提取(,>>”)
运算符在缺省情况下是跳过 空白 (包括空格,制表,
backspace和回车等)的,这样拷贝的文件会缺少一些字符。
第二,该程序能确定文件是否 拷贝结束 。流类成员函数和运算
符全是返回本类型的引用,这里就是流文件对象自身,当文件
结束时,返回 NULL,这时不再拷贝,退出循环。
第三,拷贝是 按字节进行 的,效率很低,按字节传递开销极大,
但该程序能正确拷贝任意类型的文件,不仅是文本文件(看作
按字符),二进制文件(看作按字节)也一样可正确完成。如
果是文本文件,我们可以按行进行拷贝。
第四, !sfile中的!是重载的运算符,在状态函数中重载,当该
操作出现不正常状态,返回 true。
11.4.3 二进制文件的读写
二进制文件的读写:
C++的默认文件存储方式是文本方式,而二进制文件与文
本文件对数据的解释及存储方式不同,往二进制文件中写字符
,可以用 put()函数,而对于数值性数据,则应把它在内存中
所占的字节写入到文件中,一般是用 write()函数来写。
float x = 123.456; char ch = ?A?; int num = 100;
file.write((char *)&x,4);
file.put(ch); file.write((char *)&num,2);
一般地,对二进制文件的读写可采用两种方法:一种是使用
get()和 put();另一种是使用 read()和 write()。
文件
内存
px=&x
11.4.3 二进制文件的读写
C++提供了对二进制文件进行块读写的成员函数:
istream &istream::read(char *,int); //从二进制流提取
istream &istream::read(unsigned char *,int);
istream &istream::read(signed char *,int);
//第一个参数指定存放有效输入的变量地址,第二个参数指定提取的字节数,
//函数从输入流提供指定数量的字节送到指定地址开始的单元
ostream &ostream::write(const char *,int);//向二进制流插入
ostream &ostream::write(const unsigned char *,int);
ostream &ostream::write(const signed char *,int);
//第一个参数指定输出对象的内存地址,第二个参数指定插入的字节数,
//函数从该地址开始将指定数量的字节插入输入输出流
读函数并不能知道文件是否结束,可用状态函数 int ios::eof()
来判断文件是否结束。必须指出系统是根据 当前操作 的实际情
况设置状态位,如需根据状态位来判断下一步的操作,必须在
一次操作后立即去调取状态位,以判断本次操作是否有效。
11.4.3 二进制文件的读写
#include <fstream.h> // 11-4-3-1.cpp
void main()
{ ifstream sfile(“abc.exe“,
ios::in|ios::binary|ios::nocreate);
ofstream dfile(“result.exe“,ios::out|ios::binary);
if(!sfile || !dfile)
{ cout<<“文件打开失败 !"<<endl; exit(1); }
char ch[1024]; int n;
while(! sfile.eof())
{ sfile.read(ch,1024);
n = sfile.gcount(); // 有可能读不到 1024个字符
dfile.write(ch,n);
}
sfile.close();
dfile.close();
}
11.4.3 二进制文件的读写
#include <fstream.h> // 11-4-3-2.cpp
struct person {
char name[20];
int age;
double score;
};
Struct person people[3] = {“Liu Hua”,23,78,
“Wang Yu”,24,85.7,
“Li San”,22,98};
const Plen = sizeof(struct person);
void main()
{ fstream infile,outfile;
outfile.open(“test.dat”,ios::out|ios::binary);
// 不成,退出
11.4.3 二进制文件的读写
for(int i = 0; i < 3; i ++)
outfile.write((char *)&people[i],Plen);
outfile.close();
infile.open(“test.dat”,ios::in|ios::binary);
// 不成,退出
struct person buf;
for(int i = 0; i < 3; i ++) {
infile.read((char *)&buf,Plen);
cout << buf.name << ?\t? << buf.age << ?\t? <<
buf.score << endl;
}
infile.close();
}
11.4.4 文件的随机访问
C++把每一个文件都看成一个有序的字节流,见下图,每
一个文件或者以文件结束符( end of file marker)结束,或
者在特定的字节号处结束。
0 1 2 43 65 7 8 … n-1
… 文件结束符
C++把文件看作有序的 n个字节的流
当打开一个文件时,该文件就和某个流关联起来了。对文
件进行读写实际上受到一个 文件定位指针( file position
pointer) 的控制,输入流的指针也称为读指针,每一次提取操
作将从读指针当前所指位置开始读若干个字符,同时其读定位
指针也自动向下移动若干个字节位置,为下次读做准备。输出
流指针也称写指针,每一次插入操作将从写指针当前位置开始,
每次插入操作自动将写指针向文件尾移动。
11.4.4 文件的随机访问
在 C++中可以由程序移动文件定位指针, 从而实现文件的
随机访问, 即可 读写流中任意位置开始的一段内容 。 一般文本
文件很难准确定位, 所以随机访问多用于二进制文件 。
随机读写的三类函数:
( 1) 文件位置指针的随机偏移
( 2) 测试文件位置指针的当前位置
( 3) 文件位置指针的绝对偏移
实现文件位置指针移动的三个参量:
( 1)文件流对象名
( 2)偏移量,typedef long streamoff
( 3)偏移参照位置:在 ios类中说明了一个公有枚举类型:
enum seek_dir{
beg=0,//文件开头
cur=1,//文件指针的当前位置
end=2 //文件结尾
};
11.4.4 文件的随机访问
istream类中提供了如下三个成员函数:
istream &istream::seekg(streampos); //指针直接定位
istream &istream::seekg(streamoff,ios::seek_dir); 相对
streampos istream::tellg(); //返回当前指针位置
ostream类中提供了如下三个成员函数:
ostream &ostream::seekp(streampos); //指针直接定位
ostream &ostream::seekp(streamoff,ios::seek_dir); 相对
streampos ostream::tellp(); //返回当前指针位置
流的指针位置类型 streampos和流的指针偏移类型
streamoff定义为长整型,也就是可访问文件的最大长度为 4G
为了便于记忆, 函数名中 g是 get的缩写, 而 p是 put的缩写 。
对输入输出文件定位指针只有一个但函数有两个, 这两个函数功
能完全一样 。
11.4.4 文件的随机访问
datafile.seekg(-20L,ios::cur);
表示将文件定位指针从当前位置向文件头部方向移 20个字节 。
datafile.seekg(20L,ios::beg);
表示将文件定位指针从文件头向文件尾方向移 20个字节 。
datafile.seekg(-20L,ios::end);
表示将文件定位指针从文件尾向文件头方向移 20个字节 。
datafile.seekg(1000L);
表示将文件定位指针绝对地移动到 1000L的字节地方
streampos pos = datafile.tellg(); // pos值为,1000
tellg()和 seekg()往往配合使用 。
11.4.4 文件的随机访问
void main() // 11-4-4-1.cpp
{ fstream f(“MYDATA”,ios::in|ios::out|ios::binary);
for(int i=0; i < 10; i ++) // 写 10个整数
f.write((char *)&i,sizeof(int));
streampos pos=f.tellp(); // 记下当前位置
for(i=10; i < 20; i ++) // 写 10个整数
f.write((char *)&i,sizeof(int));
f.seekg(pos); // 将读指针定位到 pos位置
f.read((char *)&i,sizeof(int));
cout <<,The Data is:” << i << endl;
f.seekp(0,ios::beg);
for(i=100; i < 120; i ++) // 开头位置重写 20个整数
f.write((char *)&i,sizeof(int));
f.seekg(pos); // 将读指针定位到 pos位置
f.read((char *)&i,sizeof(int));
cout <<,The Data is:” << i << endl;
}
11.4.5 检测流操作的错误
在 I/O流的操作过程中可能出现各种错误, 每一个流都有
一个状态标志字, 以指示是否发生了错误以及出现了哪种类
型的错误, 这种处理技术与格式控制标志字是相同的 。 ios类
定义了以下枚举类型:
enum io_state
{ goodbit =0x00,//不设置任何位, 一切正常
eofbit =0x01,//输入流已经结束, 无字符可读入
failbit =0x02,//上次读 /写操作失败, 但流仍可使用
badbit =0x04,//试图作无效的读 /写操作, 流不再可用
hardfail=0x80 //不可恢复的严重错误
};
11.4.5 检测流操作的错误
对应于这个标志字各状态位, ios类还提供了以下成员函
数来检测或设置流的状态:
int rdstate(); //返回流的当前状态标志字
int eof(); //返回非 0值表示到达文件尾
int fail(); //返回非 0值表示操作失败
int bad(); //返回非 0值表示出现错误
int good(); //返回非 0值表示流操作正常
int clear(int flag=0); //将流的状态设置为 flag
int operator!(); //流创建失败, 返回非 0
int operator void *(); //流正常, 返回非 0
为提高程序的可靠性,应在程序中检测 I/O流的操作是
否正常。当检测到流操作出现错误时,可以通过异常处理来
解决问题。
11.4.6 定向打印输出
打印机设备的数据输出:
在 C++系统中,打印机设备名为 PRN,打开打印机文件
PRN后所建立的输出流对象,向该对象输出数据,就是向打
印机输出。
ofstream prt(“PRN”);
prt <<,数据在打印机上输出 !” << endl;
cout = prt; // 将 cout重定向为打印机输出
cout <<,数据用 cout在打印机上输出 !” << endl;
例题:
1,编写一个程序把一个文件内容显示在屏幕上
2,编写一个程序统计一个文件中的行数和字符数
综合例子
编写一个学生成绩管理程序
// studentsys.cpp