C++中输入输出流ifstream/ofstream用法总结

1 文件的概念

迄今为止,我们讨论的输入输出是以系统指定的标准设备(输入设备为键盘,输出设备为显示器)为对象的。在实际应用中,常以磁盘文件作为对象。即从磁盘文件读取数据,将数据输出到磁盘文件。 

所谓“文件”,一般指存储在外部介质上数据的集合。一批数据是以文件的形式存放在外部介质上的。操作系统是以文件为单位对数据进行管理的。要向外部介质上存储数据也必须先建立一个文件(以文件名标识),才能向它输出数据。

外存文件包括磁盘文件、光盘文件和U盘文件。目前使用最广泛的是磁盘文件。

对用户来说,常用到的文件有两大类,一类是程序文件(program file)。一类是数据文件(data file)。程序中的输入和输出的对象就是数据文件。

根据文件中数据的组织形式,可分为ASCII文件和二进制文件。

对于字符信息,在内存中是以ASCII代码形式存放的,因此,无论用ASCII文件输出还是用二进制文件输出,其数据形式是一样的。但是对于数值数据,二者是不同的。例如有一个长整数100000,在内存中占4个字节,如果按内部格式直接输出,在磁盘文件中占4个字节,如果将它转换为ASCII码形式输出,则要占6个字节。

C++提供低级的I/O功能和高级的I/O功能。高级的I/O功能是把若干个字节组合为一个有意义的单位,然后以ASCII字符形式输入和输出。传输大容量的文件时由于数据格式转换,速度较慢,效率不高。

所谓低级的I/O功能是以字节为单位输入和输出的,在输入和输出时不进行数据格式的转换。这种输入输出速度快、效率高,一般大容量的文件传输用无格式转换的I/O。但使用时会感到不大方便。

 

2 文件流类与文件流对象

文件流是以外存文件为输入输出对象的数据流。输出文件流是从内存流向外存文件的数据,输入文件流是从外存文件流向内存的数据。每一个文件流都有一个内存缓冲区与之对应。

请区分文件流与文件的概念。文件流本身不是文件,而只是以文件为输入输出对象的流。若要对磁盘文件输入输出,就必须通过文件流来实现。

在C++的I/O类库中定义了几种文件类,专门用于对磁盘文件的输入输出操作除了标准输入输出流类istream,ostream和iostream类外,还有3个用于文件操作的文件类:

(1) ifstream类,它是从istream类派生的。 用来支持从磁盘文件的输入。

(2) ofstream类,它是从ostream类派生的。 用来支持向磁盘文件的输出。

(3) fstream类,它是从iostream类派生的。 用来支持对磁盘文件的输入输出。

要以磁盘文件为对象进行输入输出,必须定义一个文件流类的对象,通过文件流对象将数据从内存输出到磁盘文件,或者通过文件流对象从磁盘文件将数据输入到内存。

其实在用标准设备为对象的输入输出中,也是要定义流对象的,如cin,cout就是流对象,C++是通过流对象进行输入输出的。

由于cin,cout已在iostream.h中事先定义,所以用户不需自己定义。在用磁盘文件时,由于情况各异,无法事先统一定义,必须由用户自己定义。此外,对磁盘文件的操作是通过文件流对象(而不是cin和cout)实现的。文件流对象是用文件流类定义的,而不是用istream和ostream类来定义的。

可以用下面的方法建立一个输出文件流对象:

ofstream outfile;

现在在程序中定义了outfile为ofstream类(输出文件流类)的对象。但是有一个问题还未解决: 在定义cout时已将它和标准输出设备建立关联,而现在虽然建立了一个输出文件流对象,但是还未指定它向哪一个磁盘文件输出,需要在使用时加以指定。

 

3 文件的打开与关闭

A. 打开磁盘文件

打开文件是指在文件读写之前做必要的准备工作,包括:

(1) 为文件流对象和指定的磁盘文件建立关联,以便使文件流流向指定的磁盘文件。

(2) 指定文件的工作方式。

 

以上工作可以通过两种不同的方法实现。

(1) 调用文件流的成员函数open。如

ofstream outfile;//定义ofstream类(输出文件流类)对象outfile

outfile.open(″f1.dat″,ios::out);  //使文件流与f1.dat文件建立关联

调用成员函数open的一般形式为

文件流对象.open(磁盘文件名,输入输出方式);

磁盘文件名可以包括路径,如″c://new//f1.dat″,如缺省路径,则默认为当前目录下的文件。

(2) 在定义文件流对象时指定参数

在声明文件流类时定义了带参数的构造函数,其中包含了打开磁盘文件的功能。因此,可以在定义文件流对象时指定参数,调用文件流类的构造函数来实现打开文件的功能。如

ostream outfile(″f1.dat″,ios::out);

一般多用此形式,比较方便。作用与open函数相同。

输入输出方式是在ios类中定义的,它们是枚举常量,有多种选择。

说明:

①      新版本的I/O类库中不提供ios::nocreate和ios::noreplace。

②       每一个打开的文件都有一个文件指针。

③ 可以用“位或”运算符“|”对输入输出方式进行组合。

④ 如果打开操作失败,open函数的返回值为0(假),如果是用调用构造函数的方式打开文件的,则流对象的值为0。

 

B. 关闭磁盘文件

在对已打开的磁盘文件的读写操作完成后,应关闭该文件。关闭文件用成员函数close。如

outfile.close( );//将输出文件流所关联的磁盘文件关闭

所谓关闭,实际上是解除该磁盘文件与文件流的关联,原来设置的工作方式也失效,这样,就不能再通过文件流对该文件进行输入或输出。此时可以将文件流与其他磁盘文件建立关联,通过文件流对新的文件进行输入或输出。如

outfile.open(″f2.dat″,ios::app|ios::nocreate);

此时文件流outfile与f2.dat建立关联,并指定了f2.dat的工作方式。

 

4 对ASCII文件的操作

如果文件的每一个字节中均以ASCII代码形式存放数据,即一个字节存放一个字符,这个文件就是ASCII文件(或称字符文件)。程序可以从ASCII文件中读入若干个字符,也可以向它输出一些字符。

对ASCII文件的读写操作可以用以下两种方法:

(1) 用流插入运算符“<<”和流提取运算符“>>”输入输出标准类型的数据。

(2) 用文件流的put,get,geiline等成员函数进行字符的输入输出。

例11 有一个整型数组,含10个元素,从键盘输入10个整数给数组,将此数组送到磁盘文件中存放。

#include

using namespace std;

int main( )

{int a[10];

 ofstream outfile(″f1.dat″,ios::out);//定义文件流对象,打开磁盘文件″f1.dat″

 if(!outfile)                        //如果打开失败,outfile返回0值

  {cerr<<″open error!″<

   exit(1);

  }

 cout<<″enter 10 integer numbers:″<

 for(int i=0;i<10;i++)

  {cin>>a[i];

   outfile<

 outfile.close();                   //关闭磁盘文件″f1.dat″

 return 0;

}

运行情况如下:

enter 10 integer numbers:

1 3 5 2 4 6 10 8 7 9 ↙

请注意: 在向磁盘文件输出一个数据后,要输出一个(或几个)空格或换行符,以作为数据间的分隔,否则以后从磁盘文件读数据时,10个整数的数字连成一片无法区分。

 

例12 从例11建立的数据文件f1.dat中读入10个整数放在数组中,找出并输出10个数中的最大者和它在数组中的序号。

#include

int main( )

{int a[10],max,i,order;

 ifstream infile(″f1.dat″,ios::in|ios::nocreate);

//定义输入文件流对象,以输入方式打开磁盘文件f1.dat

 if(!infile)

  {cerr<<″open error!″<

   exit(1);

  }

 for(i=0;i<10;i++)

   {infile>>a[i];//从磁盘文件读入10个整数,顺序存放在a数组中

    cout<

 cout<

 max=a[0];

order=0;

 for(i=1;i<10;i++)

   if(a[i]>max)

{max=a[i];                //将当前最大值放在max中

order=i;                 //将当前最大值的元素序号放在order中

     }

 cout<<″max=″<

 infile.close();

 return 0;

}

运行情况如下:

1 3 5 2 4 6 10 8 7 9(在磁盘文件中存放的10个数)

max=10                           (最大值为10)

order=6                          (最大值是数组中序号为6的元素)

 

例13 从键盘读入一行字符,把其中的字母字符依次存放在磁盘文件f2.dat中。再把它从磁盘文件读入程序,将其中的小写字母改为大写字母,再存入磁盘文件f3.dat。

#include

using namespace std;

// save_to_file函数从键盘读入一行字符,并将其中的字母存入磁盘文件

void save_to_file( )

{ofstream outfile(″f2.dat″);  

//定义输出文件流对象outfile,以输出方式打开磁盘文件f2.dat

if(!outfile)

  {cerr<<″open f2.dat error!″<

exit(1);

  }

 char c[80];

 cin.getline(c,80);//从键盘读入一行字符

 for(int i=0;c[i]!=0;i++)               //对字符逐个处理,直到遇′/0′为止

if(c[i]>=65 && c[i]<=90||c[i]>=97 && c[i]<=122)//如果是字母字符

{outfile.put(c[i]);                      //将字母字符存入磁盘文件f2.dat

cout<

 cout<

 outfile.close();                         //关闭f2.dat

}

//从磁盘文件f2.dat读入字母字符,将其中的小写字母改为大写字母,再存入f3.dat

void get_from_file()

{char ch;

 ifstream infile(″f2.dat″,ios::in|ios::nocreate);

//定义输入文件流outfile,以输入方式打开磁盘文件f2.dat

 if(!infile)

  {cerr<<″open f2.dat error!″<

   exit(1);

  }

ofstream outfile(″f3.dat″);

 //定义输出文件流outfile,以输出方式打开磁盘文件f3.dat

if(!outfile)

  {cerr<<″open f3.dat error!″<

   exit(1);

  }

while(infile.get(ch))//当读取字符成功时执行下面的复合语句

  {if(ch>=97 && ch<=122)          //判断ch是否为小写字母

ch=ch-32;                    //将小写字母变为大写字母

   outfile.put(ch);               //将该大写字母存入磁盘文件f3.dat

   cout<

  }

cout<

infile.close( );                  //关闭磁盘文件f2.dat

outfile.close();                 //关闭磁盘文件f3.dat

}

int main( )

{save_to_file( );  

       //调用save_to_file( ),从键盘读入一行字符并将其中的字母存入磁盘文件f2.dat

get_from_file( );  

   //调用get_from_file(),从f2.dat读入字母字符,改为大写字母,再存入f3.dat

 return 0;

}

运行情况如下:

New Beijing, Great Olypic, 2008, China.↙

NewBeijingGreatOlypicChina(将字母写入磁盘文件f2.dat,同时在屏幕显示)

NEWBEIJINGGREATOLYPICCHINA           (改为大写字母)

磁盘文件f3.dat的内容虽然是ASCII字符,但人们是不能直接看到的,如果想从显示器上观看磁盘上ASCII文件的内容,可以采用以下两个方法:

(1) 在DOS环境下用TYPE命令,如

D://C++>TYPE f3.dat↙(假设当前目录是D://C++ )

在显示屏上会输出

NEWBEIJINGGREATOLYPICCHINA

如果用GCC编译环境,可选择File菜单中的DOS Shell菜单项,即可进入DOS环境。想从DOS返回GCC主窗口,从键盘输入exit即可。

(2) 编一程序将磁盘文件内容读入内存,然后输出到显示器。可以编一个专用函数。

#include

using namespace std;

void display_file(char *filename)

{ifstream infile(filename,ios::in|ios::nocreate);

 if(!infile)

  {cerr<<″open error!″<

   exit(1);}

 char ch;

 while(infile.get(ch))

cout.put(ch);

 cout<

 infile.close();

}

然后在调用时给出文件名即可:

int main( )

{display_file(″f3.dat″);//将f3.dat的入口地址传给形参filename

 return 0;

}

运行时输出f3.dat中的字符:

NEWBEIJINGGREATOLYPICCHINA

 

 

5 对二进制文件的操作

 

二进制文件不是以ASCII代码存放数据的,它将内存中数据存储形式不加转换地传送到磁盘文件,因此它又称为内存数据的映像文件。因为文件中的信息不是字符数据,而是字节中的二进制形式的信息,因此它又称为字节文件。

对二进制文件的操作也需要先打开文件,用完后要关闭文件。在打开时要用ios::binary指定为以二进制形式传送和存储。二进制文件除了可以作为输入文件或输出文件外,还可以是既能输入又能输出的文件。这是和ASCII文件不同的地方。

A. 用成员函数read和write读写二进制文件

对二进制文件的读写主要用istream类的成员函数read和write来实现。这两个成员函数的原型为

istream& read(char *buffer,int len);

ostream& write(const char * buffer,int len);

字符指针buffer指向内存中一段存储空间。len是读写的字节数。调用的方式为

a. write(p1,50);

b. read(p2,30);

例14 将一批数据以二进制形式存放在磁盘文件中。

#include

using namespace std;

struct student

{char name[20];

int num;

 int age;

 char sex;

};

int main( )

{student stud[3]={″Li″,1001,18,′f′,″Fun″,1002,19,′m′,″Wang″,1004,17,′f′};

 ofstream outfile(″stud.dat″,ios::binary);

   if(!outfile)

    {cerr<<″open error!″<

     abort( );//退出程序

    }

   for(int i=0;i<3;i++)

    outfile.write((char*)&stud[i],sizeof(stud[i]));

   outfile.close( );

return 0;

}

其实可以一次输出结构体数组的3个元素,将for循环的两行改为以下一行:

outfile.write((char*)&stud[0],sizeof(stud));

执行一次write函数即输出了结构体数组的全部数据。

可以看到,用这种方法一次可以输出一批数据,效率较高。在输出的数据之间不必加入空格,在一次输出之后也不必加回车换行符。在以后从该文件读入数据时不是靠空格作为数据的间隔,而是用字节数来控制。

 

例15 将刚才以二进制形式存放在磁盘文件中的数据读入内存并在显示器上显示。

#include

using namespace std;

struct student

{string name;

 int num;

 int age;

 char sex;

};

int main( )

{student stud[3];

 int i;

 ifstream infile(″stud.dat″,ios::binary);

 if(!infile)

  {cerr<<″open error!″<

   abort( );

  }

  for(i=0;i<3;i++)

infile.read((char*)&stud[i],sizeof(stud[i]));

  infile.close( );

  for(i=0;i<3;i++)

   {cout<<″NO.″<

    cout<<″name:″<

    cout<<″num:″<

    cout<<″age:″<

    cout<<″sex:″<

   }

  return 0;

 }

运行时在显示器上显示:

NO.1

name: Li

num: 1001

age: 18

sex: f

NO.2

name: Fun

num: 1001

age: 19

sex: m

NO.3

name: Wang

num: 1004

age: 17

sex: f

请思考: 能否一次读入文件中的全部数据,如

infile.read((char*)&stud[0],sizeof(stud));

 

B. 与文件指针有关的流成员函数

在磁盘文件中有一个文件指针,用来指明当前应进行读写的位置。对于二进制文件,允许对指针进行控制,使它按用户的意图移动到所需的位置,以便在该位置上进行读写。文件流提供一些有关文件指针的成员函数。为了查阅方便,将它们归纳为书中表13.7,并作必要的说明。

说明:

(1) 这些函数名的第一个字母或最后一个字母不是g就是p。

(2) 函数参数中的“文件中的位置”和“位移量”已被指定为long型整数,以字节为单位。“参照位置”可以是下面三者之一:

•    ios::beg文件开头(beg是begin的缩写),这是默认值。

•    ios::cur指针当前的位置(cur是current的缩写)。

•    ios::end文件末尾。

它们是在ios类中定义的枚举常量。

举例如下:

infile.seekg(100);//输入文件中的指针向前移到100字节位置

infile.seekg(-50,ios::cur);    //输入文件中的指针从当前位置后移50字节

outfile.seekp(-75,ios::end);   //输出文件中的指针从文件尾后移50字节

 

C. 随机访问二进制数据文件

一般情况下读写是顺序进行的,即逐个字节进行读写。但是对于二进制数据文件来说,可以利用上面的成员函数移动指针,随机地访问文件中任一位置上的数据,还可以修改文件中的内容。

例16 有5个学生的数据,要求:

(1) 把它们存到磁盘文件中;

(2) 将磁盘文件中的第1,3,5个学生数据读入程序,并显示出来;

(3) 将第3个学生的数据修改后存回磁盘文件中的原有位置。

(4) 从磁盘文件读入修改后的5个学生的数据并显示出来。

要实现以上要求,需要解决3个问题:

(1) 由于同一磁盘文件在程序中需要频繁地进行输入和输出,因此可将文件的工作方式指定为输入输出文件,即ios::in|ios::out|ios::binary。

(2) 正确计算好每次访问时指针的定位,即正确使用seekg或seekp函数。

(3) 正确进行文件中数据的重写(更新)。

可写出以下程序:

#include

using namespace std;

struct student

{int num;

 char name[20];

 float score;

};

int main( )

{student stud[5]={1001,″Li″,85,1002,″Fun″,97.5,1004,″Wang″,54,

                  1006,″Tan″,76.5,1010,″ling″,96};

 fstream iofile(″stud.dat″,ios::in|ios::out|ios::binary); 

//用fstream类定义输入输出二进制文件流对象iofile

 if(!iofile)

  {cerr<<″open error!″<

   abort( );

  }

 for(int i=0;i<5;i++)//向磁盘文件输出5个学生的数据

   iofile.write((char *)&stud[i],sizeof(stud[i])); 

 student stud1[5];                  //用来存放从磁盘文件读入的数据

 for(int i=0;i<5;i=i+2)

   {iofile.seekg(i*sizeof(stud[i]),ios::beg);  //定位于第0,2,4学生数据开头

    iofile.read((char *)&stud1[i/2],sizeof(stud1[0]));

//先后读入3个学生的数据,存放在stud1[0],stud[1]和stud[2]中

    cout<

//输出stud1[0],stud[1]和stud[2]各成员的值

   }

 cout<

 stud[2].num=1012;                         //修改第3个学生(序号为2)的数据

 strcpy(stud[2].name,″Wu″);

 stud[2].score=60;

 iofile.seekp(2*sizeof(stud[0]),ios::beg);   //定位于第3个学生数据的开头

 iofile.write((char *)&stud[2],sizeof(stud[2])); //更新第3个学生数据

 iofile.seekg(0,ios::beg);                       //重新定位于文件开头

 for(int i=0;i<5;i++)

   {iofile.read((char *)&stud[i],sizeof(stud[i]));  //读入5个学生的数据

    cout<

   }

 iofile.close( );

return 0;

}

运行情况如下:

1001 Li 85(第1个学生数据)

1004 Wang 54        (第3个学生数据)

1010 ling 96        (第5个学生数据)

1001 Li 85          (输出修改后5个学生数据)

1002 Fun 97.5

1012 Wu 60          (已修改的第3个学生数据)

1006 Tan 76.5

1010 ling 96

本程序将磁盘文件stud.dat指定为输入输出型的二进制文件。这样,不仅可以向文件添加新的数据或读入数据,还可以修改(更新)数据。利用这些功能,可以实现比较复杂的输入输出任务。

请注意,不能用ifstream或ofstream类定义输入输出的二进制文件流对象,而应当用fstream类。

你可能感兴趣的:(C++学习笔记)