再上一篇:13.2 几个特殊运算符的重载
上一篇:第十四章 输入/输出流类库
主页
下一篇:14.3 标准设备的输入/输出
再下一篇:14.4 文件流
文章列表

14.2 C++的基本流类体系

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

在前面的程序中,基本上都包含了头文件iostream.h,该头文件说明了C++语言中的一个基本的流类体系,为C++程序设计中的输入/输出提供了强有力的支持。

14.2.1 基本流类体系

C++中的流类库由几个进行输入/输出操作的基类和几个支持特定种类的源和目的的输入/输出操作的类组成。图14-1给出了C++中输入/输出的基本流体系。

该流类体系在头文件“iostream.h”中作了说明。类 ios为所有其它流类的基类,其它流类均由该类派生出来的;streambuf不是ios类的派生类,只是在类ios中有一个指针成员,它指向类streambuf的一个对象。类streambuf的作用是管理一个流的缓冲区。通常,只用到类ios、istream 、ostream和iostream中所提供的公共接口来进行输入/输出操作。类ios是类istream 和 ostream的虚基类,它提供了对流进行格式化输入/输出操作和错误处理的成员函数。类istream 和ostream均是类ios的公有派生类,前者提供完成输入操作的成员函数,而后者提供完成输出操作的成员函数。类iostream是由类istream 和ostream公有派生的,该类并没有提供新的成员函数,只是将类istream 和ostream组合在一起,以支持一个流既可完成输入操作,又可完成输出操作。

ios streambuf

istream ostream

iostream

图 14-1 输入/输出的基本流类体系

14.2.2 预定义的标准流与提取和插入运算符

在C++的输入/输出流类库中定义了四个流:cin、cout、cerr和clog。由类istream公有派生出类istream_withassign,cin是类istream_withassign的对象。由类ostream公有派生出类ostream_withassign,cout,cerr和clog都有是类ostream_withassign的对象。需要了解派生细节的读者可参看istream.h、ostream.h和iostream.h头文件。这四个对象流称为标准流。一旦用户程序中包含了头文件“iostream.h”,编译器调用相应的构造函数,产生这四个标准流,用户在程序中就可以直接使用这四个对象流了。

流是一个抽象的概念,当进行实际的输入/输出操作时,必须将流和一种具体的物理设备联系起来。流cin和cout分别称为标准输入流和标准输出流。在缺省的情况下,前者所联系的设备为键盘,实现从键盘输入数据;而后者所联系的设备为显示器,实现将信息输出到显示器上显示。流cerr和clog称为标准错误信息输出流(简称为输出流),在缺省的情况下,两者都对应于显示器。这四个标准流中,除了cerr为非缓冲流外,其余的三个流均为缓冲流。

标准流通过重载 “>>”和“<<” 运算符,执行输入/输出操作。执行输入操作可看作为从流中提取一个字符序列,因此将 “>>” 运算符称为提取运算符。输出操作看作为向流中插入一个字符序列,为此将“<<” 运算符称为插入运算符。cin使用提取运算符“>>”实现数据的输入,其余三个标准流使用插入运算符“<<”实现数据的输出。

用这四个标准流进行输入/输出时,系统自动地完成数据类型的转换。对于输入流,要将输入的字符序列形式的数据变换成计算机内部形式的数据(二进制或ASCII)后,再赋给变量,变换后的格式由变量的类型确定。对于输出流,将要输出的数据变换成字符串形式后,送到输出流(文件)中。

我们对于cin和cout的用法已相当熟悉了。下面通过一个例子来说明另外二个流的使用方法。

例14.1 使用流cerr和clog实现数据的输出。

#include

void main(void )

{

cerr << "输入i的值:";

int i;

cin >> i;

clog << "i*i="<

}

在上例中,可用cout代替cerr和clog,作用完全相同。作为输出提示信息或显示输出结果来说,这三个输出流的用法相同。不同之处在于:流cout允许输出重定向(有关输入/输出的重定向,请参看有关操作系统的书),而cerr和clog不允许输出重定向。通常,将程序中提示输入数据的信息用流clog来实现,提示错误信息用流cerr来实现,而输出的结果数据用流cout来实现。

14.2.3 流的格式控制

前面介绍的标准输入/输出流的使用,仍有许多不足之处。例如,为一个整型变量输入数据时,输入的数只能是十进制、八进制或十六进制数;输出数据时,只能用十进制数输出,不能按八进制或十六进制输出一个整数;在制表输出数据时,不能指定每一个输出的数据占用的宽度(占用的字符个数)。这些都属于流的格式控制。格式化输入/输出仅适用于输入/输出的文本流,二进制输入/输出流是不能指定输入/输出格式的。C++标准的输入/输出流提供了许多格式控制,限于篇幅,本书只介绍常用的格式控制方法。

1、预定义的格式控制函数

C++提供了13个预定义的格式控制函数,可直接用于控制输入/输出数据的格式。表14-1列出了格式控制函数,并简要地说明了控制函数的功能及适用于输入/输出流类的情况。

表14-1 C++中预定义的格式控制函数

格式控制函数名 功 能 适用于输入、

输出流

dec 设置为十进制 I/O

hex 设置为十六进制 I/O

oct 设置为八进制 I/O

ws 提取空白字符 I

endl 插入一个换行符 O

flush 刷新流 O

resetioflags(long) 取消指定的标志 I/O

setioflags(long) 设置指定的标志 I/O

setfill(int) 设置填充字符 O

setprecision(int) 设置实数的精度 O

Setw(int) 设置宽度 O

Ends 插入一个表示字符串结束的NULL字符

这些预定义的格式控制函数均在头文件iomanip.h中作了定义,因此当要使用这些格式控制函数时,必须在程序中包含该头文件。

例14.2 使用格式控制函数实现指定域宽和数制。

#include

#include

void main(void)

{

int a=256,b=128;

cout <

cout<

}

A行指定输出a的域宽为8,b按缺省的域宽输出。B行指定a按十六进制输出,b按十进制输出。

应当说明的是,setw设置的域宽仅对其后的一次插入起作用;而hex、dec、oct的设置是互斥的,一旦设置后一直延续到下一次数制设置时均有效。

2、使用缓冲区

除cerr 是非缓冲流外, cin,cout和clog都是缓冲流。对于缓冲的输出流来说,仅当输出缓冲区满时,才将缓冲区中的信息输出。对于输入缓冲区,仅当输入一行后,才开始从缓冲区中取数据。当希望把输出信息送到缓冲区后立即输出时,必须强制刷新输出流,告诉系统,立即将缓冲区中的输出信息送到与流相联系的设备上输出。刷新输出流可用函数 flush来实现。

例14.3 输出的信息在显示器上不显示。

#include

#include

void main(void )

{

double num =-234567987;

cout << "num="<

cout <

int *p;

*p=34567; //B

cout<<*p<<'\n'; //C

delete p; //D

}

执行这个程序时,产生运行错误,显示器上只显示出错信息,用cou输出的信息都在缓冲区中,并没有送到显示器上显示。显然B行中的赋值是不对的,执行D行时出现运行错误。为了将缓冲区中的信息显示出来,只要将A行改为:

cout<< setprecision(10)<<"num="<

当执行到flush时,无条件地将缓冲区中的输出信息送显示器显示。

3、流的错误处理

在输入/输出过程中,C++输入/输出流类一旦发现操作错误,就将发生的错误记录下来,程序设计者可使用C++提供的错误检测功能,检测发生错误的原因和性质。在类ios中说明了一个名为io_state公有枚举类型,它的定义为:

enum io_state{

goodbit = 0x00, //输入/输出操作正常

eofbit = 0x01, //已到达文件尾

failbit = 0x02, //输入/输出操作出错

badbit = 0x04 //非法输入/输出操作

};

其中,状态为goodbit时,表示当前流的输入/输出正常;状态为eofbit时,表示从输入流中取数据时,已到达文件尾,流中已无数据可取;状态为 failbit时,表示输入/输出过程中出现了错误,如输入的应是一个整数,而在流中却是一个字符或字符串;状态为badbit时,表示出现了非法的输入/输出操作,如向只能读的文件中写数据,或从只能写的文件中读取数据。

每当发生一次输入/输出操作错误时,系统根据当前这次输入/输出的实际情况,设置状态位。程序设计者可以使用类ios中提供的几个成员函数来读取状态,并根据状态是否正常作出相应的处理。这些成员函数是:

int ios::rdstate() const { return state; }

int ios::bad() const { return state & badbit; }

void ios::clear(int _i=0){ state = _i; }

int ios::eof() const { return state & eofbit; }

int ios::fail() const { return state & (badbit | failbit); }

int ios::good() const { return state == 0; }

函数rdstate读取输入/输出状态字;当状态字的badbit位为1时,函数bad返回值为非零值(4),否则返回值为0;函数clear用来清除流中的错误,参数的缺省值为0;函数eof用来判断是否到达文件的尾,若到达文件尾,则返回非零值,否则返回0;当流出现输入/输出操作错误或非法的输入/输出操作时,函数fail返回非零值,否则返回0;当流输入/输出操作正常时,函数good返回0,否则返回1。若要清除状态字中的某一个状态位,则可使用如下形式的clear:

cin. clear(cin.rdstate() & ~ios::badbit );

则将流中的非法输入/输出操作位置为0;若要将输入流中的非法输入/输出操作位置为1,则为:

cin. clear(cin.rdstate( ) | ios::badbit );

通常,在进行一次输入/输出操作后,程序都要检测是否发生了输入/输出错误。一旦发生了输入/输出错误,在对错误作出处理后,必须使用函数clear来清除流中的错误,以便接着进行输入/输出操作。

注意,不适当的输入/输出检测可能导致程序不能正常运行。

例14.4 输入不正确的数据时,导致程序出错。

#include

void main(void )

{

int i,s;

cout << "输入一个整数:";

cin >> i;

s=cin.rdstate(); //A

cout <<"s="<

while (s){

cin.clear(); //B

cout<< "非法的输入,重新输入一个整数:";

cin>> i; //C

s=cin.rdstate();

}

cout <<"num="<

}

这程序检测输入的数据是否为整数,若不是,则要求重新输入。在程序运行时,输入一个字符或一个字符串将导致程序的死循环。因为从输入流中提取整数值时,发现是字符或字符串,则不从缓冲区中提取字符,仅设置非法输入/输出操作错误。A行得到的s值为2。尽管在 while 循环中清除了错误状态位(B行),但并没有清除输入流中仍在缓冲区中的字符或字符串。执行 C行时,又从缓冲区提取整数,发现是字符或字符串,则不提取字符仅置错误状态标志。这必然导致输入流不能正常工作,而产生死循环。

例14.5 输入不正确的数据时,取完缓冲区中的字符。

#include

void main(void )

{

int i,s;

char str[80];

cout << "输入一个整数:";

cin >> i;

s=cin.rdstate();

cout <<"s="<

while (s){

cin.clear();

cin.getline(str,80); //取完缓冲区中的字符或字符串

cout<< "非法的输入,重新输入一个整数:";

cin>> i;

s=cin.rdstate();

}

cout <<"num="<

}

当出现输入/输出错误时,通过读入一个字符串来取出输入流缓冲区中的所有字符或字符串,接着给出提示信息后重新输入。该程序在执行期间,输入不正确的数据时,程序均能正确执行。