首页 第6章 输入输出流介绍

第6章 输入输出流介绍

举报
开通vip

第6章 输入输出流介绍 下载 第6章 输入输出流介绍 到目前为止,在这本书里,我们仍使用以前的可靠的 C标准I / O库,这是一个可变成类的完 美的例子。 事实上,处理一般的 I / O问题,比仅仅用标准库并把它变为一个类,需要做更多的工作。 如果能使得所有这样的“容器”—标准I / O、文件、甚至存储块—看上去都一样,只须记 住一个接口,不是更好吗?这种思想是建立在输入输出流之上的。与标准 C输入输出库的各种 各样的函数相比,输入输出流更容易、安全、有效。 输入输出流通常是C + +初学者学会使用的第一个类库。在这一章里,我们...

第6章 输入输出流介绍
下载 第6章 输入输出流介绍 到目前为止,在这本书里,我们仍使用以前的可靠的 C标准I / O库,这是一个可变成类的完 美的例子。 事实上,处理一般的 I / O问 快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题 ,比仅仅用标准库并把它变为一个类,需要做更多的工作。 如果能使得所有这样的“容器”—标准I / O、文件、甚至存储块—看上去都一样,只须记 住一个接口,不是更好吗?这种思想是建立在输入输出流之上的。与标准 C输入输出库的各种 各样的函数相比,输入输出流更容易、安全、有效。 输入输出流通常是C + +初学者学会使用的第一个类库。在这一章里,我们要考察输入输出 流的用途,这样,输入输出流要代替这本书剩余部分的 C I/O 函数。下一章,我们会发现如何 建立我们自己的与输入输出流相容的类。 6.1 为什么要用输入输出流 我们可能想知道以前的 C库有什么不好。为什么不把 C库封装成一个类,然后进行处理? 其实,当我们想使 C库用起来更安全、更容易一点时,在有些情况下,这样做很完美。例如, 当我们想确保一标准输入输出文件总是被安全地打开,被正确地关闭,而不依赖用户是否记得 调用c l o s e ( )函数: 在C中执行文件I / O时,要用一个没有保护的指针指向文件结构。而这个类封装了这个指针, 并用构造函数和析构函数保证它能被正确地初始化和清除。第二个构造函数参数是文件模式, 其缺省值为“r”,代 关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf “只读”。 在文件I / O函数中,为了取指针的值,可使用 fp( )访问函数。下面是成员函数的定义: 就像常常做的一样,构造函数调用 fopen( ),但它还检查结果,从而确保结果不是零,如 果结果是零意味着打开文件时出错。如果出错,这个文件名就被打印,而且函数 exit( )被调用。 析构函数关闭这个文件,存取函数 fp( )返回值f。下面是使用类文件的一个简单的例子: 在该例子中创建了文件对象,在正常C文件 I / O函数中通过调用fp( )来使用它。当我们使用 完毕时,就忘掉它,则这个文件在该范围的末端由析构函数关闭。 正式的真包装 即使文件指针是私有的,但由于 fp( )检索它,所以也不是特别安全的。仅有的保证作用是 初始化和销毁。既然这样,为什么不使文件指针是公有的,或者用结构体来代替?注意使用 f p ( ) 得到f的副本时,不能赋值给f—它完全处于类控制下。当然,一旦用户使用f p ( )返回的指针值, 他仍能对结构元素赋值。所以安全性是保证一个有效的文件指针而不是结构里的正确内容。 如果想绝对安全,必须禁止用户直接访问文件指针。这意味着所有正常的文件 I / O函数的 某些版本将必须作为类成员表现出来。这样,通过 C途径所做的每件事,在C + +类中均可做到: 84 C + +编程思想 下载 这个类包含几乎所有的来自S T D I O . H文件的I / O函数。没有v f p r i n t f ( )函数,它只是被用来实 现p r i n t f ( )成员函数。 F i l e有着与前面例子相同的构造函数,而且也有缺省的构造函数。如果要建立一个 F i l e对象 数组或使用一个F i l e对象作为另一个类的成员,在那个类里,构造函数不发生初始化(但有时 在被包含的对象创建后初始化),那么缺省构造函数是重要的。 缺省构造函数设置私有F i l e指针f为0,但是现在,在 f的任何引用之前,它的值必须要被检 第6章 输入输出流介绍 85 下载 查以确保它不为0。这是由类的最后一个成员函数 F()来完成的。F()函数仅由其他成员函 数使用,因此是私有的。(我们不想让用户直接访问这个类的F i l e结构)[ 1 ] 从任何意义上讲,这不是一个很坏的解决办法。它是相当有效的,可以设想为标准(控制 台)I / O和内核格式化(读/写一个存储块而不是一个文件或控制台)构造类似的类。 大的障碍是运行期间用作参数表函数的解释程序。这是在运行期间对格式串做语法分析以 及从参数表中索取并解释变量的代码。产生这个问题的四个原因是: 1) 即使仅使用解释程序的一部分功能,所有的东西将获得装载。假如说: p r i n t f ( " % c " , ' x ' ) ; 我们将得到整个包,包括打印出浮点数和串的那部分。没有办法可减少程序使用的空间。 2) 由于解释发生在运行期间,所以不能终止这个执行。令人灰心的是在编译时,格式串里 的所有信息都在这儿,但是直到运行时,才能对其求值。但是,如果能在编译时分析格式串里 的变量,就可以建立硬函数调用,它比运行期间解释程序快得多(虽然 p r i n t f ( )族函数通常已被 优化得很好)。 3) 由于直到运行期间才对格式串求值,一个更糟糕的问题出现了:可能没有编译时的错误 检查。如果我们已经尝试找出由于在 p r i n t f ( )说明里使用错误的数或变量类型而产生的错误,我 们大概对这个问题很熟悉了。 C + +对编译期间错误检查作了许多工作,使我们及早发现错误, 使工作更容易。特别是因为 I / O库用得很多,如果弃之不用,那是很不明智的。 4) 对C + +,最重要的问题是函数中的 p r i n t f ( )族不是能扩展的。它们被设计是用来处理 C中 四类基本的数据类型(字符,整型,浮点数,双精度及它们的变形)。我们可能认为每增加一 个新类时,都能增加一个重载的 p r i n t f ( )和s c a n f ( )函数(以及它们对文件和串的变量)。但是要 记住,重载函数在参数表里必须有不同的类型, p r i n t f ( )族把类型信息隐藏在可变参数表和格式 串中。对一个像C + +这样其目标是能容易地添加新的数据类型的语言,这是一个笨拙的限制。 6.2 解决输入输出流问题 所有这些问题都清楚地表明: C + +中应该有一个最高级别标准类库,用以处理 I / O。由于 “h e l l o , w o r l d”差不多是每个人使用一种新的语言所写的第一个程序 , 而且由于I / O通常是每个程 序的一部分,因此 C + +中的I / O库必须特别容易使用。这是一个巨大的挑战:它不知道必须适 应哪些类,但是它必须能适用于任何新的类。这样的约束要求这个最高级别的类是一个真正的 有灵感的设计。 这一章看不到这个设计的细节以及如何向我们自己的类中加入输入输出流功能(在以后的 章节里将会看到)。首先,我们必须学会使用输入输出流,其次,在处理 I / O和格式时,我们除 了能做大量的调节并提高清晰度外,还会看到,一个真正的、功能强大的C + +库是如何工作的。 6.2.1 预先了解操作符重载 在使用输入输出流库前,必须明白这个语言的一个新性能,这一性能的详细情况在下一章 介绍。要使用输入输出流,必须知道 C + +中所有操作符具有不同的含义。在这一章,我们特别 讲一下“< <”和“> >”,我们说“操作符具有不同的含义”,这值得进一步探讨。 在第5章中,我们已经学到怎样用不同的参数表使用相同的函数名。编译器在编译一个变 量后跟一个操作符再后跟一个变量组成的表达式时,它只调用一个函数。那就是说,一个操作 86 C + +编程思想 下载 [1] FULLWRAP test file和其实现在此书的源程序中提供,详见前言。 符只不过是不同语法的函数调用。 当然,这就是C + +在数据类型方面的特别之处。必须有一个事先说明过的函数来匹配操作 符和那些特别变量类型,否则编译器不接受这个表达式。 大多数人发现,操作符重载的麻烦是由于这样的想法:即我们知道 C操作符的所有知识是 错的。这是不对的。下面是C + +的设计的两个主要目的: 1) 一个用C编译过的程序用C + +也能编译。C + +编译器仅有的编译错误和警告源于C语言的 “漏洞”,修正这些需要局部编辑(其实, C + +编译器的提示一般会使我们直接查找到 C程序中 未被发现的错误)。 2) 用C + +重新编译,C ++编译器不会暗地里改变C程序的行为。 记住这些目的有助于回答许多问题。知道从 C转向C + +并不是无规律的变化,会使这种转 变更容易。特别是,内部数据类型的操作符将不会以不同的方式工作—不能改变它们的意思。 重载的操作符仅能在包含新的数据类型的地方创建。所以能为一个新类建立一个新的重载操作 符,但是表达式 1 < < 4; 不会突然间改变它的意思,而且非法代码 1 . 4 1 4 < < 1 ; 也不会突然能开始工作了。 6.2.2 插入符与提取符 在输入输出流库里,两个操作符被重载,目的是能容易地使用输入输出流。操作符“ < <” 经常作为输入输出流的插入符,操作符“> >”经常作为提取符。 一个流是一个格式化并保存字节的对象。可以有一个输入流( i s t r e a m)或一个输出流 (o s t r e a m)。有不同类型的输入流和输出流:文件输入流( i f s t r e a m s )和文件输出流( o f s t r e a m s )、c h a r * 内存的(内核格式化)输入流(i s t r s t r e a m s)和输出流( o s t r s t r e a m s )、以及与标准C + +串(s t r i n g) 类接口的串输入流(i s t r i n g s t r e a m s)和串输出流( o s t r i n g s t r e a m s )。不管在操作文件、标准I / O、存 储块还是一串对象中所有这些流对象有相同的接口。单个接口被扩展用于支持新的类。 如果一个流能产生字节(一个输入流),可以用一个提取符从这个流中获取信息。这个提 取符产生并格式化目的对象所期望的信息类型。可以使用 c i n对象看一下这个例子。 c i n对象相 当于C中s t d i n的输入输出流,也就是可重定向的标准输入。不论何时包含了 I O S T R E A M . H头文 件,这个对象就被预定义了(这样,输入输出流库能自动与大部分编译器连接)。 第6章 输入输出流介绍 87 下载 每个数据类型都有重载操作符“ > >”,这些数据类型在一个输入语句里作为“ > >”右边参 数使用(也可以重载我们自己的操作符,这一点将在下一章讲到)。 为了发现各种各样的变量里有什么,我们可以用带插入符“ < <”的c o u t对象(与标准输出 相对应,还有一个与标准错误相对应的c e r r对象): 这是特别乏味的,而且对于 p r i n t f ( )函数,看来类型检查没有多大或根本没有改进。幸运 的是,输入输出流的重载插入符和提取符,被设计在一起连接成一个更容易书写的复合表达 式: 在下一章中,我们将明白这是怎样发生的,但是现在,以类用户的态度知道它如何工作也 就足够了。 1. 操纵算子 这里已经添加了一个新的元素:一个称作 e n d l的操纵算子。一个操纵算子作用于流上,这 种情况下,插入一新行并清空流(消除所有存储在内部流缓冲区里的还没有输出的字符)。也 可以只清空流: c o u t < < f l u s h; 另外有一个基本的操纵算子把基数变为o c t (八进制),d e c (十进制)或h e x (十六进制): c o u t < < h e x < < " 0 x " < < i < < e n d l; 有一个用于提取的操纵算子“跳过”空格: c i n > > w s; 还有一个叫 e n d s的操纵算子和 e n d l操纵算子一样,仅仅用于 s t r s t r e a m s(稍后介绍)。这些是 I O S T R E A M . H里所有的操纵算子, I O M A N I P. H里会有更多的操纵算子,下一章介绍。 6.2.3 通常用法 虽然c i n和提取符“> >”为c o u t和插入符“< <”提供了一个良好的平衡,但在使用格式化 的输入机制,尤其是标准输入时,会遇到与 s c a n f ( )中同样的问题。如果输入一个非期望值,进 程则被偏离,而且它很难恢复。另外,格式化的输入缺省以空格为分隔符。这样,如果把上面 88 C + +编程思想 下载 的代码块搜集成一个程序: 给出以下输入: 我们应该得到与输入相同的输出 但输出是:(有点不是所期望的) 注意到b u f只得到第一个字,这是由于输入机制是通过寻找空格来分隔输入的,而空格在 “t h i s”的后面。另外,如果连续的输入串长于为b u f分配的存储空间,会发生b u f溢出现象。 看来提供c i n和提取符只是为了完整性,这可能是查看它的一个好方法。实际上,有时想 得到列字符的一行一行排列的输入,然后扫描它们,一旦它们安全地处于缓冲区就执行转换。 这样,我们就不必担心输入机制因非期望数据而阻塞了。 第6章 输入输出流介绍 89 下载 另一件要考虑的事是命令行接口的整体概念。当控制台还比不上一台玻璃打字机时人们就 对这一点有所认识。世界发展迅速,现在图形用户接口(G U I)处于支配地位。这样的世界里, 控制台 I / O的意思是什么呢?除了很简单的例子或测试外,可以完全忽略 c i n,并采取以下更有 意义的步骤: 1) 如果程序需要输入,从一个文件中读取输入——会发现使用输入输出流文件非常容易。 输入输出流在G U I(图形用户接口)下仍运行得很好。 2) 只读输入而不想去转换它。一旦在某处获得输入,它在转换时不会影响其他,那么我们 可以安全地扫描它。 3) 输出的情况有所不同。如果正在使用图形用户接口,则必须将输出送到一个文件中(这 与将输出送到c o u t是相同的)或者使用图形用户接口设备显示数据,c o u t不工作。然而,通常把 输出送到c o u t是有意义的。在这两种情况下,输入输出流的输出格式化函数都是很有用的。 6.2.4 面向行的输入 要获取一行输入,有两种选择:成员函数 g e t()或g e t l i n e()。两个函数都有三个参数: 指向存储结果字符的缓冲区指针、缓冲区大小(不能超过其限度)和知道什么时候停止读输入 的终止符。终止符有一个经常用到的缺省值“ \ n”。两个函数遇到输入终止符时,都把零储存 在结果缓冲区里。 其不同点是什么呢?差别虽小但极其重要: g e t()遇到输入流的分隔符时就停止,而不 从输入流中提取分隔符。如果用同样的分隔符再调用一次 g e t()函数,它会立即返回而不带 任何输入。(要么在下一个g e t()说明里用一个不同的分隔符,要么用一个不同的输入函数)。 g e t l i n e()与其相反,它从输入流中提取分隔符,但仍没有把它储存在结果缓冲区里。 总之,当我们在处理文本文件时,无论什么时候读出一行,都会想到用 g e t l i n e()。 1. get()的重载版本 g e t()有三种其他的重载版本:一个没有参数表,返回下一个字符,用的是一个 i n t返回 值;一个把字符填进字符参数表,用一个引用(想立即弄明白这个,要跳到第 1 0章看);一个 直接存储在另一个输入输出流对象的基本缓冲区结构里。这一点在本章的后面介绍。 2. 读原始字节 如果想确切知道正在处理什么,并把字节直接移进内存中的变量、数组或结构中,可以用 r e a d()函数。第一个参数是一个指向内存目的地址的指针,第二个参数指明要读的字节数。 预先将信息存储在一个文件里特别有用。例如,在二进制形式里,对一个输出流使用相反的 w r i t e()成员函数。以后我们会看到所有这些函数的例子。 3. 出错处理 除没有参数表的 g e t()外。所有 g e t()和g e t l i n e()的版本都返回字符来源的输入流, 没有参数表的g e t()返回下一个字符或E O F。如果取回输入流对象,要询问一下它是否正确。 事实上,我们可用成员函数 g o o d()、e o f()、f a i l()和b a d()询问任何输入输出流是否正 确。这些返回状态信息基于 e o f b i t(指缓冲位于序列的末尾)、f a i l b i t(指由于格式化问题或不 影响缓冲区的其他问题而使操作失败)和b a d b i t(指缓冲区出错)。 然而,正如前面提到的,如果想输入特定类型,而且从输入中读出的类型与所期望的不一 致时,输入流的状态一般要遭到莫名其妙的破坏。当然可通过处理输入流来改正这个问题。如 果大家按照我的建议,一次读入一行或一个大的字符段(用 r e a d()函数),并且除简单情况 外不使用输入格式函数,那么,所关心的只是读入位置是否处于输入的末尾。幸好这种测试很 90 C + +编程思想 下载 简单,而且能在条件内部完成,如 w h i l e ( c i n )和i f ( c i n )等。要接受这样的事实,当我们在上下文 环境中使用输入流对象时,正确值被安全、正确和魔术般地产生出来,以表明对象是否达到输 入的末尾。我们也可以像 i f(!c i n)一样,使用布尔非运算“!”,表明这个流不正确,即我 们可能已经达到输入的末尾了,应该停止读这个流。 有时候,流处于非正确状态,我们知道这个情况,并且想继续使用它。例如,如果我们到 达文件的末尾,e o f b i t和f a i l b i t被设置,这样,那个流对象的情况表明:这个流不再是正确的了。 我们可能想通过寻找以前的位置并读出更多的数据而继续使用这个文件。要改变这个情况,只 需简单地调用c l e a r ( )成员函数即可 [ 1 ]。 6.3 文件输入输出流 用输入输出流操作文件比在 C中用S T D I O . H要容易得多,安全得多。要打开文件,所做的 就是建立一个对象。构造函数做打开文件的工作。不必明确地关闭一个文件(尽管我们用c l o s e() 成员函数也能做到)。这是因为当对象超出范围时,析构函数将其关闭。要建立一缺省输出文 件,只要建一个 o f s t r e a m对象即可。下面这个例子说明到目前为止已讨论的很多特性。注意 F S T R E A M .H文件包含声明文件 I / O类,这也包含 I O S T R E A M . H文件。 第6章 输入输出流介绍 91 下载 [1] 更新的实现将支持输入输出流的这种处理错误的方式,但某些情况下也会弹出异常。 建立i f s t r e a m和o f s t r e a m,后跟一个a s s e r t ( )以保证文件能成功地被打开。还有一个对象,用 在编译器期望一个整型结果的地方,产生一个表明成功或失败的值。 (这样做要调用一个自动 类型转换成员函数,这一点将在第11章讨论)。 第一个w h i l e循环表明g e t ( )函数的两种形式的用法。一是不论读到第 S Z - 1个字符还是遇到 第三个参数(缺省值为“ \ n”),g e t()函数把字符取进一个缓冲区内,并放入一个零终止符。 g e t()把终止符留在输入流内,这样,通过使用不带参数形式的 g e t(),这个终止符必然通过 i n . g e t ( )而被扔掉,这个 g e t()函数取回一个字节并使它作为一个 i n t类型返回。二是可以用 i g n o r e()成员函数,它有两个缺省的参数,第一个参数是扔掉字符的数目,缺省值是 1,第二 参数表示i g n o r e ( )函数退出处的那个字符(在提取后),缺省值是E O F。 下面将看到两个看起来很类似的输出说明: c o u t和o u t。注意这是很合适的,我们不必担心 正在处理的是哪种对象,因为格式说明对所有的 o s t r e a m对象同样有效。第一类输入回显行至 标准输出,第二类写行至新文件并包括一个行数目。 为说明g e t l i n e(),打开我们刚刚建立的文件并除去行数是一件很有趣的事。在打开一个 要读的文件之前,为确保文件是正当关闭的,有两种选择。可以用大括号包住程序的第一部分 以迫使o u t对象脱离范围,这样,调用析构函数并在这里关闭这个文件。也可以为两个文件调 用c l o s e(),如想这样做,可以调用o p e n()成员函数重用 i n对象(也可以像第1 2章讲的那样, 在堆里动态地创建和消除对象)。 第二个w h i l e循环说明g e t l i n e()如何从其遇到的输入流中移走终止符(它的第三个参数缺 省值是“\ n”)。然而g e t l i n e ( )像g e t ( )一样,把零放进缓冲区,而且它同样不能插入终止符。 打开方式 可以通过改变缺省变量来控制文件打开方式。下表列出控制文件打开方式的标志。 标 志 函 数 i o s : : i n 打开一个输入文件,用这个标志作为 i f s t r e a m的打开方式,以防 止截断一个现成的文件 i o s : : o u t 打开一个输出文件,当用于一个没有 i o s : : a p p、i o s : : a t e或i o s : : i n 的o f s t r e a m时,i o s : : t r u n c被隐含 i o s : : a p p 以追加的方式打开一个输出文件 i o s : : a t e 打开一现成文件(不论是输入还是输出)并寻找末尾 i o s : : n o c r e a t e 仅打开一个存在的文件(否则失败) i o s : : n o r e p l a c e 仅打开一个不存在的文件(否则失败) i o s : : t r u n c 如果一个文件存在,打开它并删除旧的文件 i o s : : b i n a r y 打开一个二进制文件,缺省的是文本文件 92 C + +编程思想 下载 这些标志可用一个“位或”(O R)运算来连接。 6.4 输入输出流缓冲 无论什么时候建立一个新类,都应当尽可能努力地对类的用户隐藏类的基本实现详情,仅 显示他们需要知道的东西,把其余的变为私有以避免产生混淆。通常,当我们使用输入输出流 的时候,我们不知道或不关心在哪个字节被产生或消耗。其实,无论正在处理的是标准 I / O、 文件、内存还是某些新产生的类或设备,情况都有所不同。 这时,重要的是把消息发送到产生和消耗字节的输入输出流。为了提供通用接口给这些流 并且仍然隐藏其基本的实现,它被抽像成自己的类,叫 s t r e a m b u f。每一个输入输出流都包含一 个指针,指向某种s t r e a m b u f(这依赖于它是否处理标准 I / O、文件、内存等等)。我们可以直接 访问s t r e a m b u f。例如,可以向s t r e a m b u f移进、移出原始字节,而不必通过输入输出流来格式化 它们。当然,这是通过调用s t r e a m b u f对象的成员函数来完成的。 当前,我们要知道的最重要的事是:每个输入输出流对象包含一个指向 s t r e a m b u f的指针, 而且,如果需要调用的话,s t r e a m b u f有我们可以调用的成员函数。 为了允许我们访问s t r e a m b u f,每个流对象有一个叫做 r d b u f ( )的成员函数,这个函数返回指 向对象的s t r e a m b u f的指针。这样,我们可以为下层的 s t r e a m b u f调用任何成员函数。然而,对 s t r e a m b u f指针所做的最有兴趣的事之一是:使用“ < <”操作符将其与另一个输入输出流联结。 这使我们的对象中的所有字节流进“< <”左边的对象中。这意味着,如果把一个输入输出流的 所有字节移到另一个输入输出流,我们不必做读入它们的一个字节或一行这样单调的工作。这 是一流的方法。 例如,下面是打开一个文件并将其内容发送到标准输出(类似前面的例子)的一个很简单 的程序: 在确信命令行有一个参数后,通过使用这个变数建立一个文件输入流 i f s t r e a m。如果这个 文件不存在,打开它时将会失败,这个失败被a s s e r t ( i n )捕获。 所有的工作实际上在这个说明里完成: c o u t < < i n . r d b u f ( ); 它把文件的整个内容送到c o u t。这不仅比代码更简明扼要,也比在每次移动字节更加有效。 使用带streambuf的get()函数 有一种g e t()形式允许直接向另一对象的 s t r e a m b u f写入。第一个参数是 s t r e a m b u f的目的 第6章 输入输出流介绍 93 下载 地址(它的地址神秘地由一个引用携带,第 1 0章讨论这个问题)。第二个参数是终止符,它终 止g e t()函数。所以,打印一个文件到标准输出的另一方法是: r d b u f ( )返回一个指针,它必须逆向引用,以满足这个函数看到对象的需要。 g e t()函数不 从输入流中拖出终止符,必须通过调用 i g n o r e()移走终止符。所以,g e t ( )永远不会跳到新行 上去。 我们可能不必经常使用这样的技术,但知道它的存在是有用的。 6.5 在输入输出流中查找 每种输入输出流都有一个概念:“下一个”字符来自哪里(若是输入流)或去哪里(若是 输出流)。在某些情况下 ,可能需要移动这个流的位置,可以用两种方式处理:第一种方式是在 流里绝对定位,叫流定位( s t r e a m p o s);第二种方式像标准C库函数f s e e k ( )那样做,从文件的 开始、结尾或当前位置移动给定数目的字节。 流定位(s t r e a m p o s)方法要求先调用“ t e l l”函数:对一个输出流用 t e l l p()函数,对一 个输入流用 t e l l g()函数。(“p”指“放指针”,“g”指“取指针”)。要返回到流中的那个位置 时,这个函数返回一个 s t r e a m p o s,我们以后可以在用于输出流的 s e e k p()函数或用于输入流 的s e e k g()函数的单参数版本里使用这个s t r e a m p o s。 另一个方法是相对查找,使用 s e e k p()和s e e k g()的重载版本。第一个参数是要移动的 字节数,它可以是正的或负的。第二个参数是查找方向: I o s : : b e g 从流的开始位置 I o s : : c u r 从流的当前位置 I o s : : e n d 从流的末尾位置 下面是一个说明在文件中移动的例子,记住,不仅限于在文件里查找,就像在 C和C + +中 的S T D I O . H一样。在C + +中,我们可以在任何类型的流中查找 (虽然查找时,c i n和c o u t的方式未 被定义): 94 C + +编程思想 下载 这个程序从命令行中读取文件名,并作为一个文件输入流( i f s t r e a m)打开。a s s e r t()检 测打开是否失败。由于这是一种输入流,因此用 s e e k g()来定位“取指针”,第一次调用从文 件末查找零字节,即到末端。由于 s t r e a m p o s是一个l o n g的t y p e d e f,那里调用t e l l g(),返回被 打印文件的大小。然后执行查找,移取指针至文件大小的 1 / 1 0处—注意那是文件尾的反向查 找,所以指针从尾部退回。如我们想进行从文件尾的正向查找,取指针刚好停在文件尾。那里 的s t r e a m p o s被读进s p 2,然后,s e e k g()会到文件开始处执行,整个过程可通过由 r d b u f()产 生的s t r e a m b u f指针打印出来。最后, s e e k g()的重载版与streampos sp2一起使用,移到先前 的位置,文件的最后部分被打印出来。 建立读/写文件 既然了解s t r e a m b u f并知道怎样查找,我们会明白怎样建立一个既能读又能写文件的流对象。 下面的代码首先建立一个有标志的 i f s t r e a m,它既是一个输入文件又是一个输出文件,编译器 不会让我们向 i f s t r e a m写,因此,需要建立具有基本流缓冲区的输出流( o s t r e a m): 我们可能想知道向这样一个对象写内容时,会发生什么。下面是一个例子: 第6章 输入输出流介绍 95 下载 前五行把这个程序的源代码拷贝进一个名叫 i o f i l e . o u t的文件,然后关闭这个文件。这给了 我们一个可在其周围操作的安全的文本文件。那么前面提及的技术被用来建立两个对象,这两 个对象向同一个文件读和写。在 c o u t < < i n 2 . r d b u f ( )里,可看到“取”指针在文件的开始被初始 化。“放”指针被放到文件的末尾,这是由于“ Where does this end up?”追加到这个文件里。 然而,如果“放”指针移到 s e e k p()的开始处,所有插入的文本覆盖现成的文本。当“取” 指针用s e e k g()移回到开始处时,两次写结果均可见到,而且文件被打印出来。当然,当 o u t 2 脱离范围时,析构函数被调用,这个文件被自动保存和关闭。 6.6 strstreams 第三个标准型输入输出流可直接与内存而不是一个文件或标准输出一起工作。它允许我们 用同样的读函数和格式函数去操作内存里的字节。旧式的计算机,内存是指内核,所以,这种 功能有时叫内核格式。 s t r s t r e a m的类名字回显文件流的类名字。如想建立一个从中提取字符的 s t r s t r e a m,我们就 建立一个i s t r s t r e a m。如想把字符放进一个s t r s t r e a m,我们就建立一个o s t r s t r e a m。 串流与内存一起工作,所以我们必须处理这样的问题:内存来自哪里又去哪里。这个问题 并不复杂到令人害怕的程度,但我们必须弄懂并注意它。从 s t r s t r e a m s中得到的好处远大于这一 微小的不利。 6.6.1 为用户分配的存储 由用户负责分配存储空间,恰好是弄懂这个问题的最容易的途径。用 i s t r s t r e a m s,这是唯 一允许的方法。下面是两个构造函数: 第一个构造函数取一个指向零终止符数组的指针;我们可以提取字节直到零为止。第二个 构造函数另外还需要这个数组的大小,这个数组不必是零终止的。我们可以一直提取字节到 b u f [ s i z e ],而不管是否遇到一个零。 当移交数组地址给一个 i s t r s t r e a m构造函数时,这个数组必须已经填充了我们要提取的并且 假定格式化成某种其他数据类型的字符。下面是一个简单的例子 [ 1 ]: 96 C + +编程思想 下载 [1] 注意文件名必须被截断,以处理D O S对文件名的限制。如果我们的系统支持长文件名,我们必须调整头文件名 (否则只拷贝头文件)。 比起标准C库里a t o f ( )、a t o i ( )等等这样的函数,可以看到,这是把字符串转换成类型值更加 灵活和更加一般的途径。 编译器在下面的代码里处理这个串的静态存储分配: istrstream s("1.414 47 This is a test"); 我们还可以移交一个在栈或堆里分配的有零终止符的指针给它。 在s > > i > > f里,第一个数被提取到i,第二数被提取到f,这不是“字符的第一个空白分隔符”, 这是因为它依赖于它正被提取的数据类型。例如,如果这个串被替换成“1.414 47 This is a test”, 那么i取值1,这是因为输入程序停留在小数点上。然后 f取0 . 4 1 4。把一浮点数分成整数部分和 小数部分,是有用的。否则看起来似乎是一个错误。 就像已猜测的一样,b u f 2没有取串的其余部分,仅取空白分隔符的下一个字。一般地,使 用输入输出流提取符最好是当我们仅知道输入流里的确切的数据序列并且正在转换某些类型而 不是一字符串。然而,如果我们想立即提取这个串的其余部分并把它送给另一个输入输出流, 我们可以使用显示过的r d b u f()。 输出s t r s t r e a m s也允许我们提供自己的存储空间。在这种情况下,字节在内存中被格式化。 相应的构造函数是: o s t r s t r e a m : : o s t r s t r e a m ( c h a r * , i n t , i n t = i o s : : o u t ); 第一个参数是预分配的缓冲区,在那里字符将结束,第二个参数是缓冲区的大小,第三个 参数是模式。如果模式是缺省值,字符从缓冲区的开始地址格式化。如果模式是 i o s : : a t e或 i o s : : a p p(效果一样),字符缓冲区被假定已经包含了一个零终止字符串,而任何新的字符只能 从零终止符开始添加。 第二个构造函数参数表示数组大小,且被对象用来保证它不覆盖数组尾。如我们已填满数 组而又想添加更多的字节,这些字节是加不进去的。 关于o s t r s t r e a m s,记住重要的是:没有为我们插入一般在字符数组末尾所需要的零终止符。 当我们准备好零终止符时,用特别操纵算子e n d s。 一旦已建立一个o s t r s t r e a m,就可以插入我们需要插入的任何东西,而且它将在内存缓冲 区里完成格式化。下面是一个例子: 第6章 输入输出流介绍 97 下载 这类似于前面 i n t和f l o a t的例子。我们可能认为取一行其余部分的逻辑方法是使用 r d b u f ();这个当然可以,但它很笨拙,因为所有包括回车的输入一直被收集起来,直到用户按 c o n t r o l - Z (在u n i x中c o n t r o l - D )表明输入结束时才停下来。使用g e t l i n e()所表明的方法,一直取 输入直到用户按下回车才停下来。这个输入被取进 b u f,b u f用来构造ostrstream os 。如果未提 供第三个参数 i o s : : a p p,构造函数缺省地写在b u f的开头,覆盖刚被收集的行。然而,“追加”标 志使它把被格式化后的信息放在这个串的末尾。 像其他的输出流一样,可以用平常的格式化工具发送字节到 o s t r s t r e a m。区别是仅用e n d s在 末尾插入零。注意,e n d l是在流中插入一个新行,而不是插入零。 现在信息在b u f里格式化,可用c o u t < < b u f直接发送它。然而,也有可能用o s . r d b u f ( )发送它。 当我们这样做的时候,在s t r e a m b u f里的“取”指针随这字符被输出而向前移动。正因如此,第 二次用到c o u t < < o s . r d b u f ( )时,什么也没有发生—“取”指针已经在末端。 6.6.2 自动存储分配 输出s t r s t r e a m s(但不是 i s t r s t r e a m s)是另一种分配存储空间的方法:它们自己完成这个操 作,我们所做的是建立一个不带构造函数参数的 o s t r s t r e a m: ostrstream A; 现在,A关心它自己在堆中存储空间的分配,可以在 A中想放多少字节就放多少字节,它 用完存储空间,如有必要,它将移动存储块以分配更多的存储空间。 如果不知道需要多少空间,这是一个很好的解决办法,因为它很灵活。格式化数据到 s t r s t r e a m,然后把它的s t r e a m b u f移给另一个输入输出流。这样做很完美: 这是所有解决办法中最好的。但是,如果要 A的字符被格式化到内存物理地址,会发生什 么呢?这是很容易做到的——只要调用 s t r()成员函数即可: char* cp=A.str(); 还有一个问题,如果想在 A中放进更多的字符会发生什么呢?如果我们知道 A分配的存储 空间足够放进更多的字符,就好了,但那是不正确的。一般地,如给 A更多的字符,它将用 完存储空间。通常 A试图在堆中分配更多的存储空间,这经常需要移动存储块。但是流对象 刚刚把它的存储块的地址交给我们,所以我们不能很好地移动那个块,因为我们期望它处于 特定的位置。 o s t r s t r e a m处理这个问题的方法是“冻结”它自己。只要不用 s t r()请求内部c h a r *,就可 98 C + +编程思想 下载 以尽可能向串输出流中追加字符。它将从堆中分配所需的存储空间,当对象脱离作用域时,堆 存储空间自动被释放。 然而,如果调用s t r(),o s t r s t r e a m就“冻结”了,就不能再向给它添加字符,无需通过实 现来检测错误。向冻结的 o s t r s t r e a m添加字符导致未被定义的行为。另外, o s t r s t r e a m不再负责 清理存储器。当我们用s t r ( )请求c h a r *时,要负责清除存储器。 为了不让内存泄漏,必须清理存储器。有两种清理办法。较普通的办法是直接释放要处理 的内存。为搞懂这个问题,我们得预习一下 C + +中两个新的关键字:n e w和d e l e t e。就像第1 2章 学到的一样,这两个关键字用得相当多,但目前可认为它们是用来替代 C中m a l l o c ( )和f r e e ( )的。 操作符n e w返回一个存储块,而d e l e t e释放它。重要的是必须知道它们,因为实际上 C + +中所有 内存分配是由n e w完成的。o s t r s t r e a m也是这样的。如果内存是由n e w分配的,它必须由d e l e t e释 放。所以,如果有一个ostrstream A,用s t r ( )取得c h a r *,清理存储器的办法是: delete A.str(); 这能满足大部分需要,但还有另一个不是很普通的释放存储器的办法:解冻 o s t r s t r e a m,可通 过调用f r e e z e ( )来做。f r e e z e ( )是o s t r s t r e a m的s t r e a m b u f成员函数。f r e e z e有一个缺省参数,这个 缺省参数冻结这个流。用零参数对它解冻: A . r d b u f ( ) - > f r e e z e ( 0 ); 当A脱离作用域时,存储被重新分配,而且它的析构函数被调用。另外,可添加更多的字节给 A。但是这可能引起存储移动,所以最好不要用以前通过调用 s t r()得到的指针—在添加更 多的字符后,这个指针将不可靠。 下面的例子测试在一个流被解冻后追加字符的能力: 在放第一个串到 s后,添加一个e n d s,所以这个串能用由 s t r ( )产生的c h a r *打印出来。在这 个意义上,s被冻结了。为了添更多的字节给 s,“放”指针必须后移一步,这样下一个字符被 放到由e n d s插入的零的上面。(否则,这个串仅被打印到原来的零的上面)。这是由s e e k p() 完成的。然后通过使用r d b u f ( )和调用f r e e z e ( 0 )取回基本s t r e a m b u f指针,s被解冻。在这个意义上, 第6章 输入输出流介绍 99 下载 s就像它以前调用s t r ( )一样:我们可以添加更多的字符,清理由析构函数自动完成。 解冻一个o s t r s t r e a m并继续添加字符是有可能的,但通常不这样做。正常的情况下,如果 我们获得o s t r s t r e a m的c h a r *时想添加更多的字符,就建立一个新的 o s t r s t r e a m,通过使用 r d b u f ( ) 把旧的流灌到这个新的流中去,并继续添加新的字符到新的 o s t r s t r e a m中。 1. 检验移动 如果我们仍不相信调用s t r ( )就得对o s t r s t r e a m的存储空间负责,下面的例子说明存储定位被 移动了,因而由s t r()返回的旧指针是无效的: 在插入一个串到 s中并用s t r()捕获c h a r *后,这个串被解冻而且有足够的新字节被插入, 真正确保了内存被重新分配且大多数被移动。在打印出旧的和新的 c h a r *值后,存储明确地由 d e l e t e释放,因为第二次调用s t r ( )又冻结了这个串。 为了打印出它们指向的串的地址而不是这个串,必须把 c h a r *指派为v o i d *。c h a r *的操作符 “< <”打印出它正指向的串,而对应于v o i d *的操作符“< <”打印出指针的十六进制表示值。 有趣的是应注意到:在调用 s t r ( )前,如不插一个串到s中,结果则为0。这意味着直到第一 次插入字节到o s t r s t r e a m时,存储才被重新分配。 2. 一个更好的方法 标准C++ string类以及与其联系在一起工作的s t r i n g s t r e a m类,对解决这个问题做了很大的改进。 使用这两个类代替c h a r *和s t r s t r e a m时,不必担心负责存储空间的事—一切都被自动清理 [ 1 ]。 6.7 输出流格式化 全部的努力以及所有这些不同类型的输入输出流的目的,是让我们容易地从一个地方到另 一个地方移动并翻译字节。如果不能用 p r i n t f ( )族函数完成所有的格式化,这当然是没有用的。 我们将学到输入输出流所有可用的输出格式化函数,得到所需要的那些字节。 输入输出流的格式化函数开始有点使人混淆,这是因为经常有一种以上的方式控制格式化: 通过成员函数控制,也可以通过操纵算子来控制。更容易混淆的是,有一个派生的成员函数设 置控制格式化的状态标志,如左对齐或右对齐,对十六进制表示法是否用大写字母,是否总是 用十进制数表示浮点数的值等等。另一方面,这里有特别的成员函数用以设置填充字符、域宽 100 C + +编程思想 下载 [1] 这本书中,这些类仅是草稿,不能在编译器上实现。 和精度,并读出它们的值。 6.7.1 内部格式化数据 i o s类(在头文件 I O S T R E A M . H中可看到)包含数据成员以存储属于那个流的所有格式化 数据。有些数据的值有一定范围并被储存在变量里:浮点精度、输出域宽度和用来填充输出 (通常是一空格)的字符。格式化的其余部分是由标志所决定的,这些标志通常被连在一起以 节省空间,并一起被指定为格式标志。可以用 i o s : : f l a g s ( )成员函数发现格式化标志的值,这个 成员函数没带参数并返回一个包含当前格式化标志的 long(typedefed to fmtflags)型值。函数的 所有其余部分使格式化标志发生改变并返回格式化标志先前的值。 有时第一个函数迫使所有的标志改变。更多的是每次用剩下的三个函数来改变一个标志。 s e t f ( )的用法看来更加令人混淆:要想知道用哪个重载版本,必须知道正要改变的是哪类标 志。这里有两类标志:一类是简单的 o n或o ff,一类是与其他标志在一个组里工作的标志。 o n / o ff标志理解起来最简单,因为我们可用 s e t f ( f m t f l a g s )将它们变为o n,用u n s e t f ( f m t f l a g s )将它 们变为o ff。这些标志是: o n / o ff标志 作 用 i o s : : s k i p w s 跳过空白字符(对于输入这是缺省值) i o s : : s h o w b a s e 打印一个整数值时标明数值基数(十进制,八进制或十六进制), 使用的格式能被C + +编译器读出 i o s : : s h o w p o i n t 表明浮点数的小数点和后面的零 i o s : : u p p e r c a s e 显示十六进制数值的大写字母 A - F和科学记数法中的大写字母 E i o s : : s h o w p o s 显示加号(+)代表正值 i o s : : u n i t b u f “设备缓冲区”;在每次插入操作后,这个流被刷新 i o s : : s t d i o 使这个流与C标准I / O系统同步 例如,为 c o u t显示加号,可写成 c o u t . s e t f ( i o s : : s h o w p o s );停止显示加号,可写成 c o u t . u n s e t f ( i o s : : s h o w p o s )。应该解释一下最后两个标志。当一个字符一旦被插进一个输出流, 如果想确信它是一个输出时,可启用缓冲设备。也可以不用缓冲输出,但用缓冲设备会更好。 有一个程序用了输入输出流和C标准I / O库(用C库不是不可能的),标志i o s : : s t d i o就被采用。 如果发现输入输出流的输出和p r i n t f ( )输出出现了错误的次序,就要设置这个标志。 1. 格式域 第二类格式化标志在一个组里工作,一次只能用这些标志中的一种,就像旧式的汽车收音 机按钮一样—按下一个按钮,其余的弹出。可惜的是,这是不能自动发生的,我们必须注意 正在设置的是什么标志,这样就不会偶然调用错误的 s e t f()函数。例如,每一个数字基数有 一个标志:十六进制,十进制和八进制。这些标志一起被指定为 i o s : : b a s e f i e l d。如果i o s::d e c 标志被设置而调用s e t f(i o s : : h e x),将设置 i o s : : h e x标志,但不会清除 i o s : : d e c位,结果出现未被 定义的方式。适当的方法是像这样调用 s e t f ( )的第二种形式:s e t f ( i o s : : h e x , i o s : : b a s e f i e l d )。这个 函数首先清除 i o s : : b a s e f i e l d里的所有位,然后设置 i o s : : h e x。这样,s e t f ( )的这个形式保证无论什 么时候设置一个标志,这个组里的其他标志都会“弹出”。当然,所有这些操作由 h e x ( )操纵算 子自动完成。所以不必了解这个类实现的内部细节,甚至不必关心它是一个二进制标志的设置。 第6章 输入输出流介绍 101 下载 以后将会看到有一个与s e t ( )有提供同样的功能操纵算子。 下面是标志组和它们的作用: i s o : : b a s e f i e l d 作 用 i o s : : d e c 十进制格式整数值(十进制)(缺省基数) i o s : : h e x 十六进制格式整数值(十六进制) i o s : : o c t 八进制格式整数值(八进制) i o s : : f l o a t f i e l d 作 用 i o s : : s c i e n t i f i c 科学记数法表示浮点数值,精度域指小数点后面的数字数目 i o s : : f i x e d 定点格式表示浮点数值,精度域指小数点后面的数字数目 “a u t o
本文档为【第6章 输入输出流介绍】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_756169
暂无简介~
格式:pdf
大小:1MB
软件:PDF阅读器
页数:0
分类:互联网
上传时间:2011-08-23
浏览量:13