📄 c++12.dat
字号:
第十二章 输入输出流
第一节 输入输出流类
C++语言同C语言一样,也不具有内部输入输出能力,这样做的目的是为了最大限度地保证语言与平台的无关性.计算机语言的输入输出功能都是与操作系统相关的,如果C++为某种操作系统实现内部输入输出功能,那它也就被限制在这个操作系统上了,这是我们所不希望的.
如果一个应用程序没有输入和输出,那它也就没有应用价值.在C++中,输入输出功能,是通过调用该操作系统的I/O库来实现的.
scanf、printf都是C语言标准输入输出库函数,不能否认,C语言的标准输入输出库函数也是安全、高效的,为什么说C++的输入输出更安全高效呢?关键在于C++的输入输出与C的输入输出实现方法不同.
C++的输入输出是如何实现的?是不是依据面向对象的思想,把C的标准I/O库封装成类,然后进行处理呢?比如操作一个文件,我们想确保文件能够安全地打开,及时地关闭,而不完全依赖用户调用open()、close()函数,可以构造一个文件类,定义一个成员变量,作为文件指针,分别在构造函数和析构函数中打开、关闭文件.再进一步,可以在类中实现C的标准输入输出库中所有的I/O函数,并把文件指针置为私有变量.从外面看来,这已经是一个封装得很好的文件类.在某种意义上讲,这种方法是相当有效的,我们也可以为标准I/O和存储块构造类似的类.
我们已经知道,C++是使用iostream流库,并没有使用C的输入输出库,是不是iostream流库更好呢?答案是肯定的.C的I/O库函数主要用来处理基本数据类型(字符、整型、浮点数等),使用参数表进行数据传输,使用格式字符串指定数据类型和输入输出格式.它们在运行时对格式字符串进行语法分析,并据此对变量进行解释.例如:
printf("a=%d\tb=%d\ta+b=%d\n",a,b,a+b);
C语言I/O库的缺点是:
1. 即使只使用了解释程序的一个功能,也要全部装载.如上面的例子,我们要装载整个包,包括解释浮点数和字符串那部分程序段,无法减少程序的长度.
2. 虽然printf族函数已经优化得很好,但是,它是在运行期间进行解释,如果能在编译期间分析格式字符串里的变量,根据不同的类型调用各自的函数处理,运行会快得多,而且C++编译期间的类型检查会有助于我们发现错误.
3. 对于C++来说,printf不能被扩展是它最大的缺点.我们不能通过重载函数对它进行扩展,因为重载函数要有不同类型的参数,而printf族函数把类型信息隐藏在可变参数表和格式字符串中.
iostream是通过类的继承,类成员函数的重载来实现的,利用类的可继承性和多态性,使iostream类库使用统一的函数接口操作标准I/O、文件、存储块等输入输出设备.通过函数重载,为每种内部数据类型定义了流输入输出函数,使得用户可以用相同的格式对各种数据类型进行操作,编译程序根据数据的类型自动选择相应的输入输出函数,不必将所有函数一并加载.同时,iostream拥有了很好的扩展性,用户通过重载还可以对自定义对象进行流的操作.因此,与标准C输入输出库的各种各样的函数相比,输入输出流更容易、更安全、更有效.
1.1 输入输出流类层次
iostream是一组C++类,用于实现面向对象模型的输入输出,可以提供无缓冲的(低级)和缓冲的I/O操作.在某些情况下,如果C++编译器提供的iostream库中没有合适的输入输出函数可用,我们还可以利用类的继承和多态特性来改进它们.
抽象流基类
ios 流基类
输入流类
istream 普通输入流类和用于其它输入流的基类
ifstream 输入文件流类
istream_withassign 用于cin的输入流类
istrstream 输入串流类
输出流类
ostream 普通输出流类和用于其它输出流类的基类
ofstream 输出文件流类
ostream_withassign 用于cout、cerr和clog的流类
ostrstream. 输出串流类
输入输出流类
iostream 普通输入输出流类和用于其它输入输出流的基类
fstream 输入输出文件流类
strstream 输入输出串流类
stdiostream 用于标准输入输出文件的输入输出类
缓冲流类
streambuf 抽象缓冲流基类
filebuf 用于磁盘文件的缓冲流类
strstreambuf. 用于串的缓冲流类
stdiobuf 用于标准输入输出文件的缓冲流类
预定义流初始化类
iostream_init 预定义流初始化的类
其中,ios、istream、ostream和streambuf类构成了C++中iostream输入输出功能的基础.
1.2标准输入和输出
输出流类在iostream.h中预定义了四个全局的流对象:cout、cerr、clog和cin,用于标准输出和输入,cout和cin在程序设计中会经常用到.
cout流对象控制向控制台(显示器)的标准输出,cin控制从控制台(键盘)输入.cerr与标准错误设备连在一起,是非缓冲输出,也就是说插入到cerr的数据会被立刻显示出来,非缓冲输出可以迅速把出错信息告知用户.clog也是与标准错误设备连在一起的,但它是缓冲输出.
一.标准输出
程序1
#include <iostream.h>
void main()
{
float pi=3.14159;
cout<<"pi=";
cout<<pi;
cout<<endl;
}
程序的输出结果为:
pi=3.14159
程序2
#include <iostream.h>
void main()
{
float pi=1.4;
cout.put('A');
cout.write((unsigned char*)&pi,sizeof(float));
}
二、标准输入
#include <iostream.h>
void main()
{
int n;
cin>>n;
char ch;
cin>>ch;
float pi;
cin>>"pi=";
char str[20];
cin>>str;
cout<<"n="<<n<<endl;
cout<<"ch="<<ch<<endl;
cout<<"pi="<<pi<<endl;
cout<<"string="<<str<<endl;
}
当输入是:5 c 3.14159 hello时,程序运行结果是:
n=5
ch=c
pi=3.14159
string=hello
1.3 操纵算子
操纵算子是插入到流中或从流中抽取出来、影响流的格式状态的函数或对象.流的格式状态由ios类定义,其中包括指定数据对象的基数,如十进制、八进制、十六进制等,还有输出宽度、精度、填充字符等等.事实上,ios类有自己的成员函数可以设置、清除这些格式变量.操纵算子与这些成员函数的功能是重叠的,但是引入操纵算子为我们提供了很大的方便和表达能力,它们有助于提高程序的可读性.
第二节 文件流
利用文件流操作打开一个文件,只需要建立一个对象,它的构造函数负责打开文件,当该对象生存期结束时,它会调用析构函数关闭文件.当然,我们也可以调用成员函数open()和close()进行文件的打开和关闭,下面这个例子说明了如何用文件流进行文件操作.
文件流类其实是输入输出流类的一部分,由于在实际中,会经常用到文件操作,所以我们把文件流类再单独介绍.
一、打开文件
用文件流打开文件可以利用无参的构造函数,然后调用open():
ofstream outfile;
outfile.open("outfile", iosmode);
也可调用带参数的构造函数,指定文件名和打开方式:
ofstream outfile("outfile", iosmode);
二、文件操作
由于iostream的设备无关性,构造了文件流以后.就可以象前面标准输入输出流的方法一样使用了.
三、关闭文件
在文件操作结束时,可以用close()成员函数关闭该文件.
Outfile.close();
不过,在该文件流对象生存期结束时,对象也会自动调用析构函数来关闭文件.最好在文件操作结束时,关闭文件,这样会使程序的可读性更好.
第三节 字节流
字节流可直接与内存而不是与文件或标准输出一起工作.我们可以用与标准输出同样的格式,操作内存里的数据(字节).如果我们想把数据放入字节流,可以建立一个ostrstream对象;如果想从字节流中提取数据,就建立一个istrstream对象.
3.1 输入流
istrstream类支持一个字符数组作为源的输入流.在构造istrstream对象前,必须存在一个字符数组,而且这个数组中已经填充了我们想要提取的字符.下面是两个构造函数的原型:
istrstream::istrstream(char* buf);
istrstream::istrstream(char* buf, int size);
第一个构造函数取一个指向以"\0"作为结尾符的字符数组的指针,我们可以提取字节直至遇到"\0"为止.第二个构造函数还需要这个数组的大小,但不需要数组包含字符串的结尾符"\0",我们可以一直提取字节到buf[size-1],而不管是否遇到"\0".
3.2 输出流
ostrstream类支持一个字符数组作为数据传输目的地的输出流,它可以使用我们为它申请的存储空间,这时字节在内存中被格式化;也可以使用自动分配的存储空间.
我们为ostrstream申请存储空间的方法是通过ostrstream有参的构造函数:
ostrstream(char*, int, int=ios::out);
第一个参数是缓冲区的指针,第二个参数是缓冲区的大小,第三个参数是打开模式.如果是缺省的模式,则从缓冲区头部开始添加新的字符;如果打开模式是ios::ate或ios::app,则从缓冲区中的字符串的结尾符处开始添加新的字符 (结尾符不后移,只是被简单地覆盖,下面程序中os<<ends的作用就是在buf后面加上结尾符).
第四节 流错误处理
4.1 流状态测试
在iostream中,每一个流对象都有一个表示操作是否成功的状态位.
每一步操作后流的状态有下面五种:
表1
good 流状态正常.
end-of-file 表明输入操作到达输入序列尾部.
fail 表明出现了格式化问题或者不影响缓冲区的其他问题,如果fail位被清除,流还 是可用的.
bad 表明缓冲区出现错误,数据丢失.
hardfail 出现不可恢复性错误.
iostream提供了一些成员函数,如good()、fail()等来查询当前流的状态,下表列出了所有用于状态查询的成员函数.
表2
int rdstate() 返回当前状态.
int good() 若未设置错误状态时返回非零值.
int eof() 当end-of-file位设置时,返回非零值.通常在执行过程中遇到文件的 末尾时设置.不能从缓冲区读入更多的字符,也不能向缓冲区输出更多 的字符.
int fail() 当fail、bad或hardfail位设置时,返回非零值.
int bad() 当bad或hardfail位设置时,返回非零值.
int operator!() 当流状态非正常时,返回非零值.
在对流进行操作时,我们应该先对流的状态进行检测,以确保流的状态正常,通常的做法是:
if (!(cout << "Hello World !"))
handle_error();
使用插入或者抽取运算符的优点就是用户可以把插入或者抽取操作成组进行.例如,
int n=5;
cout<<"n="<<n;
if (!cout) handle_error();
这样可以使程序简洁明了,但是随之会产生一个问题:用户不可能在每次流操作结束后检测流的状态.C++的例外可以解决这个问题,因此标准iostream允许使用例外处理流错误.
4.2 例外处理流错误
I/O流库中允许使用例外对流错误进行处理.在ios类中添加了两个成员函数:
void exceptions(iostate except_mask);
iostate exceptions();
第一个函数用来设置标志位,是流抛出相应的例外.这些标志位可以是eof、bad、fail或者它们的组合,第二个函数用来返回当前的标志.例
#include "iostream.h"
void main()
{
int a=5;
try
{
cout.exceptions(ios::failbit);
cout<<"a="<<a<<endl;
}
catch(ios_base::failure& excep)
{
cerr<<excep.what()<<endl;
}
}
在try模块里cout调用exceptions()抛出一个例外,使catch模块里的程序能够执行,这一步仅仅是为了测试之用.
例外处理是C++语言的一个新特征,它提出了出错处理更完美的方法,并使出错处理程序和主代码分离,从而简化了出错处理程序的编写.例外处理的一般格式:
try {
//可能会生成例外的代码段
} catch (type id1) {
//例外处理程序
}
关键字try引导的程序段为测试程序段,在测试程序段中生成的例外由catch引导的例外处理器捕获并进行处理,catch带的参数是要捕获的例外类型.
当例外抛出时,如果被捕获,主程序段立即中止执行,转到例外处理程序中执行例外处理,处理结束并不返回到异常抛出的地方,通常的作法是报告错误,进行一些保护性工作,如关闭文件,然后退出函数段.
例外处理的内容很多,详细了解请参考其它书籍.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -