C++文件流类与文件流对象
文件流是以外存文件为输入输出对象的数据流。输出文件流是从内存流向外存文件的数据,输入文件流是从外存文件流向内存的数据。每一个文件流都有一个内存缓冲区与之对应。
请区分文件流与文件的概念,不用误以为文件流是由若干个文件组成的流。文件流本身不是文件,而只是以文件为输入输出对象的流。若要对磁盘文件输入输出,就必须通过文件流来实现。
在C++的I/O类库中定义了几种文件类,专门用于对磁盘文件的输入输出操作。
除了标准输入输出流类istream、ostream和iostream类外,还有3个用于文件操作的文件类:
- ifstream类,它是从istream类派生的,用来支持从磁盘文件的输入。
- ofstream类,它是从ostream类派生的,用来支持向磁盘文件的输出。
- fstream类,它是从iostream类派生的,用来支持对磁盘文件的输入输出。
要以磁盘文件为对象进行输入输出,必须定义一个文件流类的对象,通过文件流对象将数据从内存输出到磁盘文件,或者通过文件流对象从磁盘文件将数据输入到内存。
其实在用标准设备为对象的输入输出中,也是要定义流对象的,如cin、cout就是流对象,C++是通过流对象进行输入输出的。由于cin、cout已在iostream.h中事先定义,所以用户不需自己定义。在用磁盘文件时,由于情况各异,无法事先统一定义,必须由用户自己定义。此外,对磁盘文件的操作是通过文件流对象(而不是cin和cout)实现的。文件流对象是用文件流类定义的,而不是用istream和ostream类来定义的。可以用下面的方法建立一个输出文件流对象:
1
|
ofstream outfile; |
如同在头文件iostream中定义了流对象cout —样,现在在程序中定义了outfile为 ofstream类(输出文件流类)的对象。但是有一个问埋还未解决:在定义 cout 时已将它和标准输出设备(显示器)建立关联,而现在虽然建立了一个输出文件流对象,但是还未指定它向哪一个磁盘文件输出,需要在使用时加以指定。
C++对字符串流的读写
文件流是以外存文件为输入输出对象的数据流,字符串流不是以外存文件为输入输出的对象,而以内存中用户定义的字符数组(字符串)为输入输出的对象,即将数据输出到内存中的字符数组,或者从字符数组(字符串)将数据读入。字符串流也称为内存流。
字符串流也有相应的缓冲区,开始时流缓冲区是空的。如果向字符数组存入数据,随着向流插入数据,流缓冲区中的数据不断增加,待缓冲区满了(或遇换行符),一起存入字符数组。如果是从字符数组读数据,先将字符数组中的数据送到流缓冲区,然后从缓冲区中提取数据赋给有关变量。
在字符数组中可以存放字符,也可以存放整数、浮点数以及其他类型的数据。在向字符数组存入数据之前,要先将数据从二进制形式转换为ASCII代码,然后存放在缓冲区, 再从缓冲区送到字符数组。从字符数组读数据时,先将字符数组中的数据送到缓冲区,在赋给变量前要先将ASCII代码转换为二进制形式。总之,流缓冲区中的数据格式与字符数组相同。这种情况与以标准设备(键盘和显示器)为对象的输入输出是类似的,键盘和显示器都是按字符形式输入输出的设备,内存中的数据在输出到显示器之前,先要转换为 ASCII码形式,并送到输出缓冲区中。从键盘输入的数据以ASCII码形式输入到输入缓冲区,在赋给变量前转换为相应变量类型的二进制形式,然后赋给变量。对于字符串流的输入输出的情况,如不清楚,可以从对标准设备的输入输出中得到启发。
文件流类有ifstream,ofstream和fstream,而字符串流类有istrstream,ostrstream和strstream。文件流类和字符串流类都是ostream,istream和iostream类的派生类,因此对它们的操作方法是基本相同的。向内存中的一个字符数组写数据就如同向文件写数据一样,但有3点不同:
输出时数据不是流向外存文件,而是流向内存中的一个存储空间。输入时从内存中的存储空间读取数据。在严格的意义上说,这不属于输入输出,称为读写比较合适。 因为输入输出一般指的是在计算机内存与计算机外的文件(外部设备也视为文件)之间 的数据传送。但由于C++的字符串流采用了 C++的流输入输出机制,因此往往也用输入和输出来表述读写操作。
字符串流对象关联的不是文件,而是内存中的一个字符数组,因此不需要打开和关闭文件。
每个文件的最后都有一个文件结束符,表示文件的结束。而字符串流所关联的字符数组中没有相应的结束标志,用户要指定一个特殊字符作为结束符,在向字符数组写入全部数据后要写入此字符。
字符串流类没有open成员函数,因此要在建立字符串流对象时通过给定参数来确立字符串流与字符数组的关联。即通过调用构造函数来解决此问题。建立字符串流对象的方法与含义如下。
建立输出字符串流对象
ostrstream类提供的构造函数的原型为:
1
|
ostrstream::ostrstream( char *buffer, int n, int mode=ios::out); |
buffer是指向字符数组首元素的指针,n为指定的流缓冲区的大小(一般选与字符数组的大小相同,也可以不同),第3个参数是可选的,默认为ios::out方式。可以用以下语句建立输出字符串流对象并与字符数组建立关联:
1
|
ostrstream strout(ch1,20); |
作用是建立输出字符串流对象strout,并使strout与字符数组ch1关联(通过字符串流将数据输出到字符数组ch1),流缓冲区大小为20。
建立输入字符串流对象
istrstream类提供了两个带参的构造函数,原型为:
1
2
|
istrstream::istrstream( char *buffer); istrstream::istrstream( char *buffer, int n); |
buffer是指向字符数组首元素的指针,用它来初始化流对象(使流对象与字符数组建立关联)。可以用以下语句建立输入字符串流对象:
1
|
istrstream strin(ch2); |
作用是建立输入字符串流对象strin,将字符数组ch2中的全部数据作为输入字符串流的内容。
1
|
istrstream strin(ch2,20); |
流缓冲区大小为20,因此只将字符数组ch2中的,20个字符作为输入字符串流的内容。
建立输入输出字符串流对象
strstream类提供的构造函数的原型为:
1
|
strstream::strstream( char *buffer, int n, int mode); |
可以用以下语句建立输入输出字符串流对象:
1
|
strstream strio(ch3, sizeof (ch3),ios::in|ios::out); |
作用是建立输入输出字符串流对象,以字符数组ch3为输入输出对象,流缓冲区大小与数组ch3相同。
以上个字符串流类是在头文件strstream中定义的,因此程序中在用到istrstream、ostrstream和strstream类时应包含头文件strstream(在GCC中,用头文件strstream)。
[例] 将一组数据保存在字符数组中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#include <strstream> using namespace std; struct student { int num; char name[20]; float score; }; int main( ) { student stud[3]={1001, "Li" ,78,1002, "Wang" ,89.5,1004, "Fun" ,90}; char c[50]; //用户定义的字符数组 ostrstream strout(c,30); //建立输出字符串流,与数组c建立关联,缓冲区长 for ( int i=0;i<3;i++) //向字符数组c写个学生的数据 strout<<stud[i].num<<stud[i].name<<stud[i].score; strout<<ends; //ends是C++的I/O操作符,插入一个'\\0' cout<< "array c:" <<c<<endl; //显示字符数组c中的字符 } |
运行时在显示器上的输出如下:
1
2
|
array c: 1001Li781002Wang89.51004Fun90 |
以上就是字符数组c中的字符。可以看到:
1) 字符数组c中的数据全部是以ASCII代码形式存放的字符,而不是以二进制形式表示的数据。
2) 在建立字符串流strout时指定流缓冲区大小为30字节,与字符数组c的大小不同,这是允许的,这时字符串流最多可以传送30个字符给字符数组c。请思考:如果将流 缓冲区大小改为10字节,即:
1
|
ostrstream.strout( c ,10); |
运行情况会怎样?流缓冲区只能存放10个字符,将这10个字符写到字符数组c中。运行时显示的结果是:
1
|
1001Li7810 |
字符数组c中只有10个有效字符。一般都把流缓冲区的大小指定与字符数组的大小 相同。
3) 字符数组c中的数据之间没有空格,连成一片,这是由输出的方式决定的。如果以后想将这些数据读回赋给程序中相应的变量,就会出现问题,因为无法分隔两个相邻的数据。为解决此问题,可在输出时人为地加入空格。如
1
2
|
for ( int i=0;i<3;i++) strout<< " " <<stud[i].num<< " " <<stud[i].name<< " " <<stud[i].score; |
同时应修改流缓冲区的大小,以便能容纳全部内容,今改为字节。这样,运行时将输出:
1
|
1001 Li 78 1002 Wang 89.5 1004 Fun 90 |
再读入时就能清楚地将数据分隔开。
[例] 在一个字符数组c中存放了个整数,以空格相间隔,要求将它们放到整型数组中,再按大小排序,然后再存放回字符数组c中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include <strstream> using namespace std; int main( ) { char c[50]= "12 34 65 -23 -32 33 61 99 321 32" ; int a[10],i,j,t; cout<< "array c:" <<c<<endl; //显示字符数组中的字符串 istrstream strin(c, sizeof (c)); //建立输入串流对象strin并与字符数组c关联 for (i=0;i<10;i++) strin>>a[i]; //从字符数组c读入个整数赋给整型数组a cout<< "array a:" ; for (i=0;i<10;i++) cout<<a[i]<< " " ; //显示整型数组a各元素 cout<<endl; for (i=0;i<9;i++) //用起泡法对数组a排序 for (j=0;j<9-i;j++) if (a[j]>a[j+1]) {t=a[j];a[j]=a[j+1];a[j+1]=t;} ostrstream strout(c, sizeof (c)); //建立输出串流对象strout并与字符数组c关联 for (i=0;i<10;i++) strout<<a[i]<< " " ; //将个整数存放在字符数组c strout<<ends; //加入'\\0' cout<< "array c:" <<c<<endl; //显示字符数组c return 0; } |
运行结果如下:
1
2
3
|
array c: 12 34 65 -23 -32 33 61 99 321 32(字符数组c原来的内容) array a: 12 34 65 -23 -32 33 61 99 321 32 (整型数组a的内容) array c: -32 –12 32 33 34 61 65 99 321 (字符数组c最后的内容) |
对字符串流的几点说明:
1) 用字符串流时不需要打开和关闭文件。
2) 通过字符串流从字符数组读数据就如同从键盘读数据一样,可以从字符数组读入字符数据,也可以读入整数、浮点数或其他类型数据。如果不用字符串流,只能从字符数组逐个访问字符,而不能按其他类型的数据形式读取数据。这是用字符串流访问字符数组的优点,使用方便灵活。
3) 程序中先后建立了两个字符串流strin和strout,与字符数组c关联。strin从字符数组c中获取数据,strout将数据传送给字符数组。分别对同一字符数组进行操作。甚至可以对字符数组交叉进行读写,输入字符串流和输出字符串流分别有流指针指示当前位 置,互不干扰。
4) 用输出字符串流向字符数组c写数据时,是从数组的首地址开始的,因此更新了 数组的内容。
5) 字符串流关联的字符数组并不一定是专为字符串流而定义的数组,它与一般的字符数组无异,可以对该数组进行其他各种操作。
通过以上对字符串流的介绍,大家可以看到:与字符串流关联的字符数组相当于内存中的临时仓库,可以用来存放各种类型的数据(以ASCII形式存放),在需要时再从中读回来。它的用法相当于标准设备(显示器与键盘),但标准设备不能保存数据,而字符数组中的内容可以随时用ASCII字符输出。它比外存文件使用方便,不必建立文件(不 需打开与关闭),存取速度快。但它的生命周期与其所在的模块(如主函数)相同,该模块的生命周期结束后,字符数组也不存在了。因此只能作为临时的存储空间。