← 学习库 C++程序设计教程 本册目录

第18章 输入与输出

原书第 252 页

第 18 章 输入/输出的信息管理 ——输入/输出流

对于输入数据和输出显示的知识,大家已经非常熟悉了,回想一下,在编程三部曲的输入这部曲中,最拿手的就是用 cin 语句从键盘向程序中输入信息(数字或字符),或用赋值语句向程序中输入信息;而在输出这部曲中,则常用 cout 语句把程序中的信息往显示器上输出。以上这些就是这一站的景点——输入/输出流(I/O 流)中的一部分内容。就像物流公司把物品从某处流向(运送到)另一处一样,计算机、C++中的信息也同样是一种物品,它也是需要流动、传送的,这样才能使程序、生产正常运转,让生活变得更丰富多彩。

虽然已经体验了输入数据和输出显示这部分内容,但读者也许在编写程序中还是有许多困惑。例如,读者已经编写过的几个学生的成绩管理程序,每运行一次都要重新把几个学生的所有成绩从键盘上输进去(假设用cin语句),这显得麻烦又耗时。如果管理一个学校几千名学生的成绩,每次运行程序时都全部从键盘输入,那会消耗大量的时间,还怎么去运行程序、管理成绩呢?所以单用cin语句输入信息显然不够用。

有些读者会说,在编程时干脆就用赋值语句对这些数据信息进行初始化赋值。其实,这也不实用,因为有些学生的成绩需要修改、添加、删除,还随时会面临着学生人数的增减,另外,有些学生的信息不正确,还要修改,而运行程序的人员一般都不会改程序,一般也不允许这些运行人员改程序,这样一来,下一次运行的程序还是原来未改动的程序,即信息未被修改,所以此法不实用。

那究竟该怎么办呢?大家知道,程序可以存储成文件,使用时把程序打开,就可以方便地运行了,而信息和程序在计算机中都是用计算机的机器语言存储的,那么,信息可否也像程序那样,用文件保存起来,供日后随意使用呢?C++的回答是:可以。这真是车到山前必有路,柳暗花明又一村。下面就一起来观赏吧。

18.1 例题详解

18.1.1 运用 I/O 文本文件实现字符串的写入与读出

【例 18-1】通过 C++ 的输入/输出文本文件,实现将字符串 “I am a C++ champion!” 写入文本文件,并将写入的文本内容读出,然后在屏幕上显示。

  1. 用心分析人想通,机随人行写提纲

同样是按照 “人定胜机,机随人行” 的法则编程。

人的做法是:

1)写入:找到一本记录本→打开记录本→写入该字符串→合上记录本。

2)读出:找到该记录本→打开记录本→读出该字符串→抄在一张纸上(显示出来)→

原书第 253 页

合上记录本。

2. 对号入座写文章,头脑运行改文章

根据上述提纲,结合 C++ 的文件 I/O 流语句,对号入座写文章——编写 C++ 源程序代码。

程序设计思路:

对应于上述提纲,记录本就是文件,打开记录本就是打开文件,合上记录本就是关闭文件,其余同提纲。

这是用文件 I/O 流的方法,通过文本文件对一个字符串进行操作,适合采用顺序结构。

首先,在主程序中调用写入文件子函数,实现将字符串 “I am a C++ champion!” 写入文本文件,然后在主程序中调用读出文件子函数,实现将字符串 “I am a C++ champion!” 的内容读出到缓冲区,再将读取的字符串输出到屏幕显示。

程序代码如下:

//ex18-1.cpp
#include<fstream.h>
#include<iostream.h>
#include<stdlib.h>

void ord(fstream f1) //判断文件是否能打开的子函数
{
    if(!f1) //如果打开文件失败,该文件对象名返回0值,!0=1,
        //然后就执行if中的这段复合语句
    {
        cerr<<"文件打不开"<<endl; //cerr 输出错误信息:文件打不开
        exit(1); //退出
    }
}

void ffout() //写入文件子函数
{
    fstream f1("e:\\VCbook\\wf3.txt",ios::out); // fstream f1 是既定义了I/O流的对象 f1,
        //又以写方式(ios::out)打开文本文件 wf3.txt,
        //双引号内是文件路径(即文件夹)\\文件全名
    ord(f1); //调用判断文件是否能打开的子函数
    f1<<"I am a C++ champion! n"; //可以打开文件,就写入文本信息(字符串的内容)
        //到文件中
    f1.close(); //关闭文件
}

void ffint() //读出文件子函数
{
    char a[100];
    fstream f1("e:\\VCbook\\wf3.txt",ios::in); // fstream f1 是既定义了I/O流的对象 f1,
    //又以读方式(ios::in)打开文本文件 wf3.txt
    ord(f1); //调用判断文件是否能打开的子函数
}
原书第 254 页
while(!fl.eof())//当不是文件末尾时读出文本信息,
//eof 函数表示“文件末尾”,没到文件末尾时,fl.eof()=0,!0=1,继续循环(读);
//否则,结束循环

{
    fl.getline(a,70); //用 I/O 流成员函数 getline 读取一行文本,并存入变量 a 中
    cout<<a<<endl; //将读取到 a 中的一行文本输出到屏幕
}

fl.close();

void main()
{
    ffout(); //调写入文件子函数
    ffint(); //调读出文件子函数
}

将写出的程序与上述提纲分析比照,并用人脑跟踪运行,直至认为正确(即可以完成题目的功能,也符合C++的语法及规定),就可以转入下一步了。

3. C++帮助且裁判,人定胜机凯歌扬

将上面的程序输入计算机→编译→连接→如果无错→运行[→调试修改→如果无错→运行]。

上机实验记录如表18-1所示。

表 18-1 上机实验记录表
出错现象出错原因解决办法
syntax error: missing"; before "<没“#”加“#”此错纠正

注意:使用完文件后,一定要关闭文件。

运行结果:

I am a C++ champion!

18.1.2 采用文件 I/O 流管理学生成绩档案

结果正确。

【例 18-2】请用文件 I/O 流的方法,对学生成绩档案进行管理。将学生成绩档案数据用二进制文件进行存储,包括学号、姓名、英语、计算机和中文成绩,并将其显示出来。

1. 用心分析人想通,机随人行写提纲

同样是按照 “人定胜机,机随人行” 的法则编程。

人的做法是:

1)写入:找到一本记录本→打开记录本→写入学生成绩档案信息→合上记录本。

2)读出:找到该记录本→打开记录本→读出学生成绩档案信息→抄在一张纸上(显示出来)→合上记录本。

2. 对号入座写文章,头脑运行改文章

根据上述提纲,结合 C++ 的文件 I/O 流语句,对号入座写文章——编写 C++ 源程序

原书第 255 页

代码。

程序设计思路:

这是用文件 I/O 流的方法对学生成绩档案进行管理,而且要用二进制文件存储,故也适合采用顺序结构。

首先,在主程序中打开文件 s6.dat,输入数据到结构变量 stu 中,并将数据写入文件 s6.dat 中,写完后,关闭文件;然后再打开文件 s6.dat,从中读出数据,并在屏幕上显示,读完后,再关闭文件。

程序代码如下:

#include<iostream.h>
#include<iomanip.h>
#include<fstream.h>
#include<stdlib.h>
#define MAX3 //自定义MAX代表课程的数目
#define NUM2 //自定义NUM代表学生的人数
struct student //声明结构 student
{
    int id;//学号
    char name[10]; //姓名
    int score[MAX]; //成绩数组
};

void main()
{
    long endpos;
    student stu;//定义 student 这种结构类型的变量 stu
    ofstream wr("e:\\VCbook\\mat3\\s6.dat", ios::binary | ios::app);
    // ios::binary 表示以写方式打开二进制文件 s6.dat
    // ios::app 表示将输出的内容追加到文件末尾
    if(!wr) //判断文件是否能打开
    {
        cerr<<"文件打不开"<<endl;
        exit(1);
    }
    for(int i=0; i<NUM; i++) //输入数据到 stu 中,并写入文件 s6.dat 中
    {
        cout<<"学号�";
        cin>>stu.id;
        cout<<"姓名";
        cin>>stu.name;
        cout<<"英语 计算机 中文成绩�";
        for(int j=0; j<MAX; j++)
            cin>>stu.score[j];
        wr.write((char*)& stu.sizeof(student)); //写入数据到文件 s6.dat 中
    }
    wr.close(); //关闭文件
    cout<<setiosflags(ios::left) //左对齐显示 stu 中的数据的字段名栏
}
原书第 256 页

<<setw(10)||<<"学号:"//每个字段的长度为10个字符

<<setw(10)||<"姓名:"

<<setw(10)||<"英语"

<<setw(10)||<"计算机"

<<setw(10)||<"中文成绩"<endl;

ifstream rr("e:\\VCbook\\mat3\\s6.dat",ios::binary);//以读方式打开二进制文件 s6.dat

if(!rr)
{
    cerr<<"文件打不开"<endl;
    exit(1);
}

rr.seekg(0,ios::end);//将读指针移到文件末尾

endpos=rr.tellg();//记录文件末尾位置

i=0;

rr.seekg(0,ios::beg);//将读指针移到文件头

while(rr.tellg()!=endpos)//读出文件 s6.dat 中的数据并左对齐显示,直到最后一条记录
{
    rr.read((char*)& stu,sizeof(student));//从文件中读出数据
    cout<<setw(10)||<stu.id
    <<setw(10)||<stu.name;
    for(int j=0;j<MAX;j++)
        cout<<setw(10)||<stu.score[j];
    cout<<endl;
}

rr.close();//关闭文件

将写出的程序与上述提纲分析比照,并用人脑跟踪运行,直至认为正确(即可以完成题目的功能,也符合C++的语法及规定),就可以转入下一步了。

3. C++帮助且裁判,人定胜机凯歌扬

将上面的程序输入计算机→编译→连接→如果无错→运行[→调试修改→如果无错→运行]。

运行结果(此次运行前该文本文件已写入了8位学生的数据):

学号:9

姓名:王五

英语 计算机 中文成绩:778899

学号:10

学号:10

姓名:孙六

英语 计算机 中文成绩:556677

学号:姓名:英语计算机中文成绩
1张一304050
2李二04050
3a718191
4b728292
5c8393100
6d657585
原书第 257 页
7e768696
08887898
9王五778899
10孙六556677

结果正确。

18.2 勤奋实践,总结评分

18.2.1 习题

1\. 将一段文本信息写入文本文件中,再将写入的信息读出,并显示读出的结果,请编程实现。

2.用二进制文件存储一些数据或信息,再读出这些被存储的数据或信息。请编写程序实现这些功能。

3.请对某个单位的职工工资数据进行读/写操作,其中,职工工资数据包括职工号、职工姓名、职工工资,并使用格式控制符打印输出这些数据,请编写实现上述功能的程序。

  1. 请将第3题的文件用文本文件存储。
  1. 在第3题的二进制文件的末尾添加一条新调入的职工的数据。

6.在第3题的职工工资文件中,假设某职工调动工作不在此单位了,请将该职工的数据删除。

  1. 在第3题的职工工资文件中,某职工增加了工资,请做修改,改完后要保存到文件中,请编写程序实现。

8.在第3题的职工工资文件中,只想显示最后一位职工的数据,请编写程序实现该功能。

9.在第3题中,把读、写函数写成子函数形式,并使其有判断文件是否能打开的功能,用主函数调用这些子函数完成第3题的工作。

10.对10个学生的简单档案进行文件存储,档案的数据项目包括学生姓名、学号、出生年月、性别、籍贯、政治面貌,请选择最佳的I/O流方式完成这项工作。

18.2.2 思考题

1.对第3题的职工工资数据进行插入、查询操作,然后用文件进行保存,并取出这些数据,请编写程序实现。

  1. 请编写程序,对例18-2中的学生成绩数据进行修改、删除等操作。
18.2.3 总结评分,继续提高

浏览、体验了例题、习题和思考题之后,说说自己的体会和收获,并认真总结,按表 18-2 给自己评判一下能得多少分。

原书第 258 页
表18-2 总结评分表
成绩ABCD不足的原因改进的方法备注
C++知识完全理解和会用基本理解和会用肤浅理解和会用基本不理解和不会用
程序原创(独立编程)的比例(%)≥9080~8960~79< 60

不论得分多少,大家都要总结经验,扬长避短,勤奋努力,不断提高。

最后浏览一下本章的第4节,可以先考考自己,然后再浏览,以便使重点知识掌握得更牢固。

18.3 输入/输出流知识

C++这个物流公司是如何管理物流(I/O流)的呢?它有什么高超的本事,能把输入/输出信息管理得井井有条呢?C++对输入/输出信息的管理主要分为三大类:第一类,仓储式管理——文件I/O流;第二类,运输式管理——标准I/O流;第三类,临时内存管理——字符串I/O流。本节将介绍前两类,请认真观赏吧。

18.3.1 在什么情况下用到输入/输出流

第一类:文件 I/O 流(仓储式管理)。

当需要用文件保存管理信息,使其能像打开和关闭程序文件一样方便时用此。

例如,数据库类型的数据、文字等信息,如学生档案信息、学生成绩信息、仓库物品信息、商场商品信息等;一些特殊的要保存的信息,如身份证号码、密码等,用文件 I/O 流管理为好。

第二类:标准设备 I/O 流(运输式管理)。

当在指定的标准输入或输出设备中管理信息时用此。

例如,从标准输入设备(键盘)上输入信息的语句 cin,向标准输出设备(显示器)输出信息的语句 cout 等。

18.3.2 关于输入/输出流

1. 什么叫输入/输出流

输入/输出流是 C++程序对输入/输出信息的管理,这些管理包括对输入/输出流的设备、使用(如打开、关闭文件,读出或写入文件信息)等方面的管理,它主要分为三类,前面已叙述过。

2. 文件 I/O 流

(1) 文件是什么

把这个概念单提出来是不是小题大做了?读者对这个概念及其应用早已熟悉了,如Word文件,C++的源文件、头文件等。是的,这些都是文件,但光有C++的源文件、头文件这类程序文件,程序的运行还是不完善的,还应有数据信息文件。一切用数据说话,数据

原书第 259 页

文件相当于米,程序文件相当于巧妇,巧妇难为无米之炊啊!

有的读者会说,那以前单单有程序文件,这个巧妇不也可以“炊”得好吗?是的,但是要知道,那是因为在程序文件中有输入(信息数据)这部曲,所以巧妇能“炊”得好,但是在程序中输入的信息数据是不能保存下来的,当程序运行完毕,信息数据就消失了,也就产生了本章开头所提到的困惑。所以,要想程序能实际应用、完善应用,还需要用数据文件来支撑。

不论什么文件,都是保存在计算机的外存(外部存储器)中,如磁盘、硬盘、光盘、U盘等,这样才能在程序运行完毕或者断电后长期保存文件信息。

按文件信息的组织形式来分类,可分为 ASCII 码文件和二进制文件。ASCII 码文件也叫做文本文件(Text)或字符文件,二进制文件也称为内部格式文件或字节文件。

这两种文件的用途如下。

ASCII 码文件:当文件用于显示、打印或阅读时用此。

二进制文件:用于暂存,即经常要在程序运行时把数据保存进磁盘或从磁盘中取出来时用此。

(2)文件 I/O 流的定义和使用

这两种文件的使用没有绝对的界限,当两种用途均有,且某种用途多于另一种时,就采纳用途多的使用,或二者适时适地地使用。

通常情况下,在记录本上进行记录的步骤是:找到某本记录本→打开记录本→记录信息→合上记录本;在记录本上找出信息的步骤是:找到该本记录本→打开记录本→找出所要信息→合上记录本。类似地,可以把文件看成是记录本,因为文件就是用来存储信息的,与记录本的功能相似,只不过文件是通过计算机的操作来记录、管理信息而已。所以,文件的管理操作无外乎就是打开、关闭、读、写文件等。

C++对其管理的所有 I/O 流都定义了管理条例,这些管理条例分别声明在类中,所有 I/O 流的基类是 ios,其下有许多派生类,包括管理文件 I/O 流的派生类、声明这些派生类的头文件。

注意:要清楚输出(写)、输入(读)这些概念,它们均以计算机的内存为基准点。

写(到文件)=输出(内存→文件)。

读(出文件)=输入(内存←文件)。

下面来浏览和体验 C++ 是如何定义和使用文件 I/O 流的。

1)文件 I/O 流的定义。

这里的类还是第12章中讲的类,大家都知道,类是不能直接用类名来操作的,要定义类的对象,用对象来操作。前面已提到C++对文件I/O流的管理条例都已经制定在文件I/O流类中,那么,要对文件进行操作,也同样要定义这些类的对象,然后才能使用已制定的文件管理条例。所以,要先定义对象,再通过对象调用文件类的成员函数,然后才能对文件进行操作。另外,调用文件类的成员函数时还有一些规定,如文件是输入还是输出、怎样打开、是文本文件还是二进制文件等。归纳可知,定义文件I/O流有两种方式(通式)。

方式一:

类名 对象名://定义对象和引用对象成员的格式,与第12章中所讲的类的知识一样

原书第 260 页

对象名.成员函数名("文件名",[输入/输出方式][打开方式][二进制 binary]);

方式二(将方式一中的两句合二为一,一般多用此方式):

类名 对象名("文件名",[输入/输出方式][打开方式][二进制 binary]);

上述两件方式中,有以下几点需要注意。

① 类名主要用到以下3种。

ifstream——读文件时用此,此方式若不注明读方式,则取其默认方式:读方式 ios::in,见例 18-4 和例 18-5。

ofstream——写文件时用此,此方式若不注明写方式,则取其默认方式:写方式 ios::out 和 ios::trunc,见例 18-3 和例 18-6。

fstream——读、写或又读又写文件时都可以用此,但一定要注明读方式 ios::in 或写方式 ios::out 或二者同时存在方式 ios::in | ios::out,见例 18-7 和例 18-8。

这 3 个类名在头文件 fstream.h 中声明,程序中要加上声明类名的头文件的包括语句 “#include”。

②文件名包含了文件路径,转路径要用两个反斜杠(\\)表示。

③ 输入/输出方式和打开方式的定义如表 18-3 所示。文件名后若有选项,则应以“,”与第一个选项相隔,选项中若有多种方式,则用按位或的符号“|”分隔。

表 18-3 文件输入(读)/输出(写)方式和打开方式定义表
方式作用
ios::in读方式打开文件
ios::out写方式打开文件(这是默认方式),如原来有名称为此的文件,则将原有内容全部删除
ios::app写方式打开文件,在文件的末尾写入数据
ios::ate打开已有的一个文件,文件指针指向文件末尾
ios::trunc打开一个文件,如原来有名称为此的文件,则将原有内容全部删除;如文件不存在,则建立新文件
ios::binary以二进制方式打开文件,如无此项,则默认是 ASCII 方式
ios::nocreate打开一个已有的文件,如文件不存在,则打开失败,但不建立新文件
ios::noreplace如果文件不存在,则建立新文件,否则操作失败,也不去更新原来的文件
ios::in| ios::out读和写方式打开文件,文件可读又可写
ios::out| ios::binary以二进制方式打开并写文件
ios::in| ios::binary以二进制方式打开并读文件
【例18-3】

ofstream out1:

out1.open("e:\\VCbook\\stul");//以写方式打开文本文件 stul.txt,因为没注明方式,

//所以以默认的写方式 ios::out 和方式 ios::trunc 打开文件

ord(out1); //调用判断文件是否能打开的子函数

原书第 261 页

【例18-4】

ifstream out2("e:\\VCbook\\stu1");//以读方式打开文本文件 stu1.txt,与例 18-3 同理,//以默认的读方式 ios::in 打开文件

【例18-5】

ifstream out2;

out2.open("e:\\VCbook\\p2.dat",ios::binary);//以读方式打开二进制文件 p2.dat

【例 18-6】

ofstream out1("e:\\VCbook\\p2.dat",ios::binary);//以写方式打开二进制文件 p2.dat

【例 18-7】

fstream f1("e:\\VCbook\\wfl.txt",ios::in|ios::out);//以读和写方式打开文本文件 wfl.txt,

//使用 fstream 必须指明以什么方式打开文件,此处同时指明两种方式,

//即读方式 ios::in 和写方式 ios::out

【例 18-8】

fstream wr("e:\VCbook\mat3\s6.dat",ios::in|ios::out|ios::binary|ios::app);
//以读和写方式打开二进制文件 s6.dat,
//并将数据追加到文件末尾

2)文件 I/O 流的使用。

已经司空见惯的输出语句 cout 和输入语句 cin,实际上是 ostream 等流类的对象,在 iostream.h 头文件中已做了声明,所以在程序中加上此头文件的包括语句 “#include”。

输入/输出的方法有以下几种(注意,除了已有的 I/O 流对象外,均要先用定义文件 I/O 流的两种方式之一定义对象名,再进行下述操作)。

方法一(文本文件和二进制文件均可用此法):文件 I/O 流的使用方法与 cin、cout 的使用方法相同,只不过对象名换了,不是 cin 和 cout 了。

输出(写文件)时用:

对象名<<要输出的信息或其存储单元:

【例18-9】

cout<<学习 C++";

对应例 18-3 有:

out1<<12;//写入 12 到文件中

对应例 18-7 有:

f1<<"I am a C++ champion! \n";//写入文本信息到文件

输入(读文件)时用:

对象名>>被输入的信息的存储单元:

原书第 262 页

【例 18-10】

cin>>y;

对应例 18-4 有:

out2>>a; //用 I/O 流成员对象 out2 读取文本文件 stul.txt 的内容 12 到内存变量 a 中

方法二:二进制文件主要用 read(输入)和 write(输出)成员函数。

用 read 和 write 成员函数读/写二进制文件的方法如下。

read 语句的定义及使用:

对象名.read((char*))& 被读信息的首地址, sizeof(首地址的数据类型));

write 语句的定义及使用:

对象名.write((char)& 写入信息的首地址, sizeof(首地址的数据类型));

其中, $ (char^{*}) $是将读/写的信息强制转换成字符指针,sizeof(首地址的数据类型)是一次读/写文件的字节数(也可用其他方式表达此项)。

如例 18-2 所示。

3)文件的关闭。

注意:写完、读完文件后一定要将其关闭。

关闭文件的格式为:

对象名.close();

【例18-11】

f1.close(); //关闭文件,f1 是文件对象名

4)判断文件是否能打开。

判断文件能否打开的方法有以下几种(注意,以下各种方法中均要有头文件包括语句“#include”。)

方法一:

① if(!文件对象名)//如果打开文件失败,该文件对象名返回0值,因为!0=1,//所以就执行if中的复合语句

{
    cerr<<"文件打不开"<endl;//cerr 输出错误信息:文件打不开
    exit(1);//退出
}
【例 18-12】判断文件是否能打开的子函数。
void ord(ofstream f)
{
    if(!f)//如果打开文件失败,该文件对象名 f 返回 0 值,因为!0=1,
//所以就执行 if 中的复合语句
{
    cerr<<"文件打不开"<endl;//cerr 输出错误信息:文件打不开
}
原书第 263 页
Image

方法二:

将方法一中的②句换为:

cout<<"文件打不开"<<endl;

方法三:

将方法一和方法二中的③句换为:

abort();

还有其他一些方法,此处不再一一介绍。

5)文件的随机访问。对文本文件的定位通常不准确,所以处理需要对文件准确定位的随机访问时,使用二进制文件较合适。它是靠文件中的一个文件指针来导引的。文件流中与文件指针关联的定位等成员函数如表18-4所示。

表 18-4 文件流中与文件指针关联的定位等成员函数
文件操作成员函数功能
读文件(输入)tellg()返回读文件指针的当前位置
seekg(在文件中的位置)将读文件指针移到指定的位置
seekg(位移量,参考位置)读文件指针以参考位置为基准,按位移量(字节数)移动
gcount()返回最后一次读文件时所读入的字节数
写文件(输出)tellp()返回写文件指针的当前位置
seekp(在文件中的位置)将写文件指针移到指定的位置
seekp(位移量,参考位置)写文件指针以参考位置为基准,按位移量(字节数)移动

在表18-4中,有以下几点需要注意。

① 先区分文件操作是读(输入)还是写(输出),再选用对应的成员函数。

② 既可以读又可以写的文件,则可以不用区分。

③读的成员函数的末尾是g(get,除了gcount()外)。

④ 写的成员函数的末尾是 p (put)。

⑤ 参考位置(即文件中的参考点)有3个:

ios::beg:文件的开头处,这是默认的。

ios::cur:指针目前的位置。

ios::end:文件的末尾处。

⑥ 位移量是以参考位置为基准所移动的字节的数量,即移动多少字节。

表18-4中的成员函数的使用方法与前面讲的类的成员函数的使用方法一样,即:

I/O 流对象名.成员函数(实参);

下面是一些关于文件的随机访问的例子,假设已声明了输入流对象名为 $ \pi $,输出流对象名为 wr。

【例 18-13】把写指针移到文件开头。

原书第 264 页

wr.seekp(0,ios::beg);//因为参考位置已是开头处,所以位移量为0字节;//因为写指针意味着进行输出操作,所以用seek后带p的成员函数

【例 18-14】返回例 18-13 的写指针的位置。

long curpos3=wr.tellp();//返回了写(输出文件)指针的开头位置;因为 tellp()函数的//返回值为长整型,所以存放读位置值的变量 curpos3 用长整型

【例 18-15】返回输入文件的倒数第 56 个字节的位置。

rr.seekg(-56,ios::end);//先把输入文件的指针移到倒数第56个字节的位置

long curpos=rr.tellg();//再返回该指针当前的位置值

【例 18-16】返回最后一次输入时的开始位置。

long endinput=rr.gcount();//返回最后一次输入所读入的字节数

rr.seekg(-endinput,ios::end);//从文件末尾起往后移动最后一次输入所读入的字节数

long curpos2=rr.tellg();//返回指针当前的位置值

【例 18-17】将输出文件的指针向前移动到第 84 个字节的位置,并返回该指针当前的位置值。

wr.seekp(84);//将输出文件的指针向前移动到第84个字节的位置

long curpos3=wr.tellp();//返回该指针当前的位置值

cout<<curpos3<<endl;//显示该指针当前的位置值

【例 18-18】当读入到第 112 个字节时,中间的 56 个字节的内容不要,移动读指针位置,并返回该指针当前的位置值。

if(long currun=rr.tellg()==112)//当读指针读入到第112个字节时
{
    rr.seekg(56,ios::cur);//将读指针向前移56个字节
    long inpos=rr.tellg();//返回该指针当前的位置值
    cout<<inpos<<endl;//显示该指针当前的位置值
}

(3) 查看写入的文本文件内容

在调试程序或运行程序时,可以查看写入的文本文件内容,也可以大致看看二进制文件是否写入了一些乱码,来判断文件是否写入、是否正常。

查看写入的文本文件内容的方法有以下7种。

1)直接双击该文本文件的图标查看。

2)用 Windows 的记事本打开查看。

3)在 VisualC++6.0 中,打开文件查看。

4)用 Windows 的写字板打开查看。

5)在 Windows 中用其他打开文本文件的方式查看。

6)在 DOS 界面下,找到文件路径,输入 “type 全文件名”,然后按〈Enter〉键,即可查看。

原书第 265 页

7)用 C++ 编写程序来打开文件、读出文件内容并显示,见例 18-1 和例 18-2。用这种方法还可以查看二进制文件。

3. 标准 I/O 流

读者已熟练运用的 cout、cin 和这一章讲到的 cerr 都是 iostream.h 头文件中定义的标准 I/O 流对象。所谓标准,就是这些对象输入或输出的具体行为是指定好的,例如,cout 是将具体内容输出到屏幕,cin 是从键盘输入信息,并写入 cin 之后的变量中,cerr 是输出错误信息到屏幕。

还可用 get、getline、put 等输入、输出的公有成员函数提取和插入信息。

【例18-19】

c2=cin.get();//将从键盘读入的内容存放在变量 c2 中

cout.put(c2);//把变量 c2 的内容显示在屏幕上

对应例 18-1 有:

f1.getline(a,70); //用 I/O 流成员函数 getline 读取一行文本

18.3.3 如何运用输入/输出流

运用 I/O 流时,根据前面所讲述的知识,选用合适的 I/O 流方式,并正确书写语句,再加上调试和查看,一般就可以了。

下面举两个综合性的例子。

1. 关于文本信息的例子

【例 18-20】将 “我爱中国” 这段文字信息写入二进制数据文件中,然后读出该文件的内容,并显示在屏幕上。

(1)用心分析人想通,机随人行写提纲

同样是按照 “人定胜机,机随人行” 的法则编程。

人的做法是:

1)写入:找到一本记录本→打开记录本→写入“我爱中国”→合上记录本。

2)读出:找到该记录本→打开记录本→读出“我爱中国”→抄在一张纸上(显示出来)→合上记录本。

(2) 对号入座写文章,头脑运行改文章

根据上述提纲,结合 C++ 的文件 I/O 流语句,对号入座写文章——编写 C++ 源程序代码。

程序设计思路:

对应于上述提纲,记录本就是文件,打开记录本就是打开文件,合上记录本就是关闭文件,其余同提纲。

这是用文件 I/O 流的方法,通过二进制文件对一个字符串进行操作,适合采用顺序结构。

首先,在主程序中调用写入文件子函数,实现将字符串“我爱中国”写入二进制文件,然后在主程序中调用读出文件子函数,实现将字符串“我爱中国”的内容读出到缓冲区,再将读取的字符串输出到屏幕进行显示。

原书第 266 页

程序代码如下:

//ex18-10.cpp
#include<fstream.h>
#include<iostream.h>
#include<stdlib.h>

void ord(fstream f1)//判断文件是否能打开的子函数
{
    if(!f1)
        {
            cerr<<"文件打不开"<<endl;
            exit(1);
        }
}

void ffout()//写入文件子函数
{
    fstream f1("e:\\VCbook\\wf2.dat",ios::out|ios::binary|ios::ate|ios::trunc);
    //以写方式打开二进制文件 wf2.dat, ios::ate 表示文件指针指向文件末尾;
    //ios::trunc 表示将原有内容全部删除; 如文件不存在, 就建立新文件
    ord(f1);//调用判断文件是否能打开的子函数
    f1<<"我爱中国!\\n";//写入文本信息到文件
    f1.close();//关闭文件
}

void ffint()//读出文件子函数
{
    char a[100];
    fstream f1("e:\\VCbook\\wf2.dat",ios::in|ios::binary);
        //以读方式打开二进制文件 wf2.dat
    ord(f1);//调用判断文件是否能打开的子函数
    while(!f1.eof())//当不是文件末尾时读出文本信息
    {
        f1.getline(a,70);//用 I/O 流成员函数 getline 读取一行文本
        cout<<a<<endl;//将读取的一行文本输出到屏幕
    }
    f1.close();
}

void main()
{
    ffout();//调用写入文件子函数
    ffint();//调用读出文件子函数
}

将写出的程序与上述提纲分析比照,并用人脑跟踪运行,直至认为正确(即可以完成题

原书第 267 页

目的功能,也符合 C++ 的语法及规定),就可以转入下一步了。

(3)C++帮助且裁判,人定胜机凯歌扬

将上面的程序输入计算机→编译→连接→如果无错→运行[→调试修改→如果无错→运行]。

运行结果:

我爱中国!

结果正确。

2. 关于数字信息的例子

【例 18-21】在主函数中对一个数组赋值,在子函数中将该数组信息写入二进制数据文件中,并以带参数的方式传递数组信息,然后再用另一个子函数从二进制文件中读出其内容,并显示在屏幕上。

(1)用心分析人想通,机随人行写提纲

同样是按照 “人定胜机,机随人行” 的法则编程。

人的做法是:

1)在主函数中声明一个数组并赋值→调用写入文件子函数,并将该数组信息以实参方式传递给写入文件子函数→再调用读出文件子函数。

2)写入文件子函数:找到一本记录本→打开记录本→写入该数组信息→合上记录本。

(2)对号入座写文章,头脑运行改文章

3)读出文件子函数:找到该记录本→打开记录本→读出该数组信息→抄在一张纸上(显示出来)→合上记录本。

根据上述提纲,结合 C++ 的文件 I/O 流语句,对号入座写文章——编写 C++ 源程序代码。

程序设计思路:

这是用文件 I/O 流的方法,通过二进制文件对一个数组进行操作,适合采用顺序结构。

程序代码如下:

//ex18-18.cpp
#include <iostream>
#include <fstream>
#include <fstream>
using namespace std;

/#include

#define N 10

void fread()//文件数据读取
{
    ifstream infile("date12.dat", ios::in|ios::binary);
    if(!infile)
        cout<<"error"<<endl;
    exit(1);
}
原书第 268 页
int b[N];//定义存放读出的文件数据的数组 b
for (int i=0;i<N;i++)
{
    infile>>b[i];//将文件数据一个个地读出,并存放在数组的对应元素中
}
infile.close();
for (int j=0;j<N;j++)
{
    cout<<b[j]<<endl;
}

void fwrite(int a[])//文件数据写入
{
    fstream outfile("date12.dat",ios::out[ios::binary]);
    if(!outfile)
    {
        cout<<"error"<<endl;
        exit(1);
    }
    for(int i=0;i<N;i++)
        outfile<<a[i]<<"";//将数组 a 中的元素一个个地写入到文件中
    outfile.close();
}

int main()//主函数
{
    int a[N];
    for(int i=0;i<N;i++)//给数组 a 赋值
    a[i]=10*i;
    fwrite(a);//带着实际参数(数组 a)调用写入文件子函数
    fread();//调用读出文件子函数
    return 0;
}

将写出的程序与上述提纲分析比照,并用人脑跟踪运行,直至认为正确(即可以完成题目的功能,也符合C++的语法及规定),就可以转入下一步了。

(3)C++帮助且裁判,人定胜机凯歌扬

将上面的程序输入计算机→编译→连接→如果无错→运行[→调试修改→如果无错→运行]。

运行结果:

0

10

20

30

40

50

60

原书第 269 页

70

80

90

结果正确。

此题若用 read 和 write 成员函数编写程序,又该如何进行呢?请读者自行设计。

关于二进制文件的例子还可参考例 18-2 和例 20-2。

18.4 输入/输出流的亮点

输入/输出流是 C++ 程序对输入/输出信息的管理。

1. 在什么情况下用输入/输出流的亮点

第一类:文件 I/O 流(仓储式管理)。当需要用文件保存管理信息,使其能像打开和关闭程序文件一样方便时用此。

第二类:标准 I/O 流(运输式管理)。当在指定的标准输入或输出设备中管理信息时用此。

2. 输入/输出流具体知识的亮点

1)文件是能长期保存信息的,它保存在计算机的外部存储器中。

文件分为文本文件和二进制文件,文本文件多用于显示、阅读、打印等直观输出,二进制文件多用于暂存,可根据实际情况对二者进行适当选用。

2)使用文件时的步骤:定义→打开→使用(读或写)→关闭。

定义和打开文件这两步可以分开,也可以合二为一。

要清楚输出(写)、输入(读)这些概念,它们均以计算机的内存为基准点。

写(到文件)=输出(内存→文件)。

读(出文件)=输入(内存←文件)。

文件 I/O 流的定义和打开一般多用合二为一的方式:

类名 对象名("文件名",[输入/输出方式][打开方式][二进制 binary]);

类名主要用到以下三种。

ifstream——读文件时用此。

ofstream——写文件时用此。

fstream——读、写或又读又写文件时都可以用此。

对于文件 I/O 流,输入/输出的方法有以下几种。

方法一(文本文件和二进制文件均可用此法):文件 I/O 流的使用方法与 cin、cout 的使用方法相同,只不过对象名换了,不是 cin 和 cout 了。

输出(写文件)时用:

对象名<<要输出的信息或其存储单元:

输入(读文件)时用:

原书第 270 页

对象名>>被输入的信息的存储单元:

方法二:二进制文件主要用 read(输入)和 write(输出)成员函数。

用 read 和 write 成员函数读/写二进制文件的方法如下。

read 语句的定义及使用:

对象名.read((char)& 被读信息的首地址, sizeof(首地址的数据类型));

write 语句的定义及使用:

对象名.write((char)& 写入信息的首地址,sizeof(首地址的数据类型));

写完、读完文件后一定要将其关闭,关闭文件的格式为:

对象名.close();

3)标准 I/O 流不能保存信息,即只是输入、输出到指定的设备上,并没有以文件的形式保存在计算机的外部存储器中,例如,cin——从键盘输入,cout——输出到屏幕。

3. 运用输入/输出流的亮点

1)首先,按照题述的任务要求进行规划设计,采用合适的 I/O 流方式,并规划好在程序的何处设置输入、输出。

例如,若采用文件 I/O 流方式,当添加了数据或修改了数据时,就应更新文件,即要写入文件;当要显示、打印时,就应从文件中读出信息。

2)按各种 I/O 流的格式,正确书写和使用。

3)调试中还可以查看文件,以帮助确认程序的对错。

← 第17章 友元第19章 异常处理 →