- 内容提要
- 前言
- 第 1 章 预备知识
- 第 2 章 开始学习 C++
- 第 3 章 处理数据
- 第 4 章 复合类型
- 第 5 章 循环和关系表达式
- 第 6 章 分支语句和逻辑运算符
- 第 7 章 函数——C++的编程模块
- 第 8 章 函数探幽
- 第 9 章 内存模型和名称空间
- 第 10 章 对象和类
- 第 11 章 使用类
- 第 12 章 类和动态内存分配
- 第 13 章 类继承
- 第 14 章 C++中的代码重用
- 第 15 章 友元、异常和其他
- 第 16 章 string 类和标准模板库
- 第 17 章 输入、输出和文件
- 第 18 章 探讨 C++新标准
- 附录 A 计数系统
- 附录 B C++保留字
- 附录 C ASCII 字符集
- 附录 D 运算符优先级
- 附录 E 其他运算符
- 附录 F 模板类 string
- 附录 G 标准模板库方法和函数
- 附录 H 精选读物和网上资源
- 附录 I 转换为 ISO 标准 C++
- 附录 J 复习题答案
5.5 循环和文本输入
知道循环的工作原理后,来看一看循环完成的一项最常见、最重要的任务:逐字符地读取来自文件或键盘的文本。例如,读者可能想编写一个能够计算输入中的字符数、行数和字数的程序。传统上,C++和 C 语言一样,也使用 while 循环来完成这类任务。下面介绍这是如何完成的。即使熟悉 C 语言,也不要太快地浏览本节和下一节。尽管 C++中的 while 循环与 C 语言中的 while 循环一样,但 C++的 I/O 工具不同,这使得 C++循环看起来与 C 语言循环有些不同。事实上,cin 对象支持 3 种不同模式的单字符输入,其用户接口各不相同。下面介绍如何在 while 循环中使用这三种模式。
5.5.1 使用原始的 cin 进行输入
如果程序要使用循环来读取来自键盘的文本输入,则必须有办法知道何时停止读取。如何知道这一点呢?一种方法是选择某个特殊字符—有时被称为哨兵字符(sentinel character),将其作为停止标记。例如,程序清单 5.16 在遇到#字符时停止读取输入。该程序计算读取的字符数,并回显这些字符,即在屏幕上显示读取的字符。按下键盘上的键不能自动将字符显示到屏幕上,程序必须通过回显输入字符来完成这项工作。通常,这种任务由操作系统处理。运行完毕后,该程序将报告处理的总字符数。程序清单 5.16 列出了该程序的代码。
程序清单 5.16 textin1.cpp
下面是该程序的运行情况:
程序说明
请注意该程序的结构。该程序在循环之前读取第一个输入字符,这样循环可以测试第一个字符。这很重要,因为第一个字符可能是#。由于 textin1.cpp 使用的是入口条件循环,因此在这种情况下,能够正确地跳过整个循环。由于前面已经将变量 count 设置为 0,因此 count 的值也是正确的。
如果读取的第一个字符不是#,则程序进入该循环,显示字符,增加计数,然后读取下一个字符。最后一步是极为重要的,没有这一步,循环将反复处理第一个输入字符,一直进行下去。有了这一步后,程序就可以处理到下一个字符。
注意,该循环设计遵循了前面指出的几条指导原则。结束循环的条件是最后读取的一个字符是#。该条件是通过在循环之前读取一个字符进行初始化的,而通过循环体结尾读取下一个字符进行更新。
上面的做法合情合理。但为什么程序在输出时省略了空格呢?原因在 cin。读取 char 值时,与读取其他基本类型一样,cin 将忽略空格和换行符。因此输入中的空格没有被回显,也没有被包括在计数内。
更为复杂的是,发送给 cin 的输入被缓冲。这意味着只有在用户按下回车键后,他输入的内容才会被发送给程序。这就是在运行该程序时,可以在#后面输入字符的原因。按下回车键后,整个字符序列将被发送给程序,但程序在遇到#字符后将结束对输入的处理。
5.5.2 使用 cin.get(char) 进行补救
通常,逐个字符读取输入的程序需要检查每个字符,包括空格、制表符和换行符。cin 所属的 istream 类(在 iostream 中定义)中包含一个能够满足这种要求的成员函数。具体地说,成员函数 cin.get(ch) 读取输入中的下一个字符(即使它是空格),并将其赋给变量 ch。使用这个函数调用替换 cin>>ch,可以修补程序清单 5.16 的问题。程序清单 5.17 列出了修改后的代码。
程序清单 5.17 textin2.cpp
下面是该程序的运行情况:
现在,该程序回显了每个字符,并将全部字符计算在内,其中包括空格。输入仍被缓冲,因此输入的字符个数仍可能比最终到达程序的要多。
如果熟悉 C 语言,可能以为这个程序存在严重的错误!cin.get(ch) 调用将一个值放在 ch 变量中,这意味着将修改该变量的值。在 C 语言中,要修改变量的值,必须将变量的地址传递给函数。但程序清单 5.17 调用 cin.get( ) 时,传递的是 ch,而不是&ch。在 C 语言中,这样的代码无效,但在 C++中有效,只要函数将参数声明为引用即可。引用是 C++在 C 语言的基础上新增的一种类型。头文件 iostream 将 cin.get(ch) 的参数声明为引用类型,因此该函数可以修改其参数的值。我们将在第 8 章中详细介绍。同时,C 语言行家可以松一口气了—通常,在 C++中传递的参数的工作方式与在 C 语言中相同。然而,cin.get(ch) 不是这样。
5.5.3 使用哪一个 cin.get( )
在第 4 章的程序清单 4.5 中,使用了这样的代码:
最后一行相当于两个连续的函数调用:
cin.get( ) 的一个版本接受两个参数:数组名(字符串(char*类型)的地址)和 ArSize(int 类型的整数)。(记住,数组名是其第一个元素的地址,因此字符数组名的类型为 char*。)接下来,程序使用了不接受任何参数的 cin.get( )。而最近,我们这样使用过 cin.get( ):
这里 cin.get 接受一个 char 参数。
看到这里,熟悉 C 语言的读者将再次感到兴奋或困惑。在 C 语言中,如果函数接受 char 指针和 int 参数,则使用该函数时,不能只传递一个参数(类型不同)。但在 C++中,可以这样做,因为该语言支持被称为函数重载的 OOP 特性。函数重载允许创建多个同名函数,条件是它们的参数列表不同。例如,如果在 C++中使用 cin.get(name,ArSize),则编译器将找到使用 char*和 int 作为参数的 cin.get( ) 版本;如果使用 cin.get(ch),则编译器将使用接受一个 char 参数的版本;如果没有提供参数,则编译器将使用不接受任何参数的 cin.get( ) 版本。函数重载允许对多个相关的函数使用相同的名称,这些函数以不同方式或针对不同类型执行相同的基本任务。第 8 章将讨论该主题。另外,通过使用 istream 类中的 get( ) 示例,读者将逐渐习惯函数重载。为区分不同的函数版本,我们在引用它们时提供参数列表。因此,cin.get( ) 指的是不接受任何参数的版本,而 cin.get(char) 则指的是接受一个参数的版本。
5.5.4 文件尾条件
程序清单 5.17 表明,使用诸如#等符号来表示输入结束很难令人满意,因为这样的符号可能就是合法输入的组成部分,其他符号(如 @和%)也如此。如果输入来自于文件,则可以使用一种功能更强大的技术—检测文件尾(EOF)。C++输入工具和操作系统协同工作,来检测文件尾并将这种信息告知程序。
乍一看,读取文件中的信息似乎同 cin 和键盘输入没什么关系,但其实存在两个相关的地方。首先,很多操作系统(包括 Unix、Linux 和 Windows 命令提示符模式)都支持重定向,允许用文件替换键盘输入。例如,假设在 Windows 中有一个名为 gofish.exe 的可执行程序和一个名为 fishtale 的文本文件,则可以在命令提示符模式下输入下面的命令:
这样,程序将从 fishtale 文件(而不是键盘)获取输入。<符号是 Unix 和 Windows 命令提示符模式的重定向运算符。
其次,很多操作系统都允许通过键盘来模拟文件尾条件。在 Unix 中,可以在行首按下 Ctrl+D 来实现;在 Windows 命令提示符模式下,可以在任意位置按 Ctrl+Z 和 Enter。有些 C++实现支持类似的行为,即使底层操作系统并不支持。键盘输入的 EOF 概念实际上是命令行环境遗留下来的。然而,用于 Mac 的 Symantec C++模拟了 UNIX,将 Ctrl+D 视为仿真的 EOF。Metrowerks Codewarrior 能够在 Macintosh 和 Windows 环境下识别 Ctrl+Z。用于 PC 的 Microsoft Visual C++、Borland C++ 5.5 和 GNU C++ 都能够识别行首的 Ctrl + Z,但用户必须随后按下回车键。总之,很多 PC 编程环境都将 Ctrl+Z 视为模拟的 EOF,但具体细节(必须在行首还是可以在任何位置,是否必须按下回车键等)各不相同。
如果编程环境能够检测 EOF,可以在类似于程序清单 5.17 的程序中使用重定向的文件,也可以使用键盘输入,并在键盘输入中模拟 EOF。这一点似乎很有用,因此我们来看看究竟如何做。
检测到 EOF 后,cin 将两位(eofbit 和 failbit)都设置为 1。可以通过成员函数 eof( ) 来查看 eofbit 是否被设置;如果检测到 EOF,则 cin.eof( ) 将返回 bool 值 true,否则返回 false。同样,如果 eofbit 或 failbit 被设置为 1,则 fail( ) 成员函数返回 true,否则返回 false。注意,eof( ) 和 fail( ) 方法报告最近读取的结果;也就是说,它们在事后报告,而不是预先报告。因此应将 cin.eof( ) 或 cin.fail( ) 测试放在读取后,程序清单 5.18 中的设计体现了这一点。它使用的是 fail( ),而不是 eof( ),因为前者可用于更多的实现中。
注意:
有些系统不支持来自键盘的模拟 EOF;有些系统对其支持不完善。cin.get( ) 可以用来锁住屏幕,直到可以读取为止,但是这种方法在这里并不适用,因为检测 EOF 时将关闭对输入的进一步读取。然而,可以使用程序清单 5.14 中那样的计时循环来使屏幕在一段时间内是可见的。也可使用 cin.clear( ) 来重置输入流,这将在第 6 章和第 17 章介绍。
程序清单 5.18 textin3.cpp
下面是该程序的运行情况:
这里在 Windows 7 系统上运行该程序,因此可以按下 Ctrl+Z 和回车键来模拟 EOF 条件。请注意,在 Unix 和类 Unix(包括 Linux 和 Cygwin)系统中,用户应按 Ctrl+Z 组合键将程序挂起,而命令 fg 恢复执行程序。
通过使用重定向,可以用该程序来显示文本文件,并报告它包含的字符数。下面,我们在 Unix 系统运行该程序,并对一个两行的文件进行读取、回显和计算字数($是 Unix 提示符):
1.EOF 结束输入
前面指出过,cin 方法检测到 EOF 时,将设置 cin 对象中一个指示 EOF 条件的标记。设置这个标记后,cin 将不读取输入,再次调用 cin 也不管用。对于文件输入,这是有道理的,因为程序不应读取超出文件尾的内容。然而,对于键盘输入,有可能使用模拟 EOF 来结束循环,但稍后要读取其他输入。cin.clear( ) 方法可能清除 EOF 标记,使输入继续进行。这将在第 17 章详细介绍。不过要记住的是,在有些系统中,按 Ctrl+Z 实际上将结束输入和输出,而 cin.clear( ) 将无法恢复输入和输出。
2.常见的字符输入做法
每次读取一个字符,直到遇到 EOF 的输入循环的基本设计如下:
可以在上述代码中使用一些简捷方式。第 6 章将介绍的!运算符可以将 true 切换为 false 或将 false 切换为 true。可以使用此运算符将上述 while 测试改写成这样:
方法 cin.get(char) 的返回值是一个 cin 对象。然而,istream 类提供了一个可以将 istream 对象(如 cin)转换为 bool 值的函数;当 cin 出现在需要 bool 值的地方(如在 while 循环的测试条件中)时,该转换函数将被调用。另外,如果最后一次读取成功了,则转换得到的 bool 值为 true;否则为 false。这意味着可以将上述 while 测试改写为这样:
这比! cin.fail( ) 或!cin.eof( ) 更通用,因为它可以检测到其他失败原因,如磁盘故障。
最后,由于 cin.get(char) 的返回值为 cin,因此可以将循环精简成这种格式:
这样,cin.get(char) 只被调用一次,而不是两次:循环前一次、循环结束后一次。为判断循环测试条件,程序必须首先调用 cin.get(ch)。如果成功,则将值放入 ch 中。然后,程序获得函数调用的返回值,即 cin。接下来,程序对 cin 进行 bool 转换,如果输入成功,则结果为 true,否则为 false。三条指导原则(确定结束条件、对条件进行初始化以及更新条件)全部被放在循环测试条件中。
5.5.5 另一个 cin.get( ) 版本
“怀旧”的 C 语言用户可能喜欢 C 语言中的字符 I/O 函数—getchar( ) 和 putchar( ),它们仍然适用,只要像在 C 语言中那样包含头文件 stdio.h(或新的 cstdio)即可。也可以使用 istream 和 ostream 类中类似功能的成员函数,来看看这种方式。
不接受任何参数的 cin.get( ) 成员函数返回输入中的下一个字符。也就是说,可以这样使用它:
该函数的工作方式与 C 语言中的 getchar( ) 相似,将字符编码作为 int 值返回;而 cin.get(ch) 返回一个对象,而不是读取的字符。同样,可以使用 cout.put( ) 函数(参见第 3 章)来显示字符:
该函数的工作方式类似 C 语言中的 putchar( ),只不过其参数类型为 char,而不是 int。
注意:
最初,put( ) 成员只有一个原型—put(char)。可以传递一个 int 参数给它,该参数将被强制转换为 char。C++标准还要求只有一个原型。然而,有些 C++实现都提供了 3 个原型:put(char)、put(signed char) 和 put(unsigned char)。在这些实现中,给 put( ) 传递一个 int 参数将导致错误消息,因为转换 int 的方式不止一种。使用显式强制类型转换的原型(如 cin.put(char(ch)))可使用 int 参数。
为成功地使用 cin.get( ),需要知道其如何处理 EOF 条件。当该函数到达 EOF 时,将没有可返回的字符。相反,cin.get( ) 将返回一个用符号常量 EOF 表示的特殊值。该常量是在头文件 iostream 中定义的。EOF 值必须不同于任何有效的字符值,以便程序不会将 EOF 与常规字符混淆。通常,EOF 被定义为值−1,因为没有 ASCII 码为−1 的字符,但并不需要知道实际的值,而只需在程序中使用 EOF 即可。例如,程序清单 5.18 的核心是这样:
可以使用 int ch,并用 cin.get( ) 代替 cin.get(char),用 cout.put( ) 代替 cout,用 EOF 测试代替 cin.fail( ) 测试:
如果 ch 是一个字符,则循环将显示它。如果 ch 为 EOF,则循环将结束。
提示:
需要知道的是,EOF 不表示输入中的字符,而是指出没有字符。
除了当前所做的修改外,关于使用 cin.get( ) 还有一个微妙而重要的问题。由于 EOF 表示的不是有效字符编码,因此可能不与 char 类型兼容。例如,在有些系统中,char 类型是没有符号的,因此 char 变量不可能为 EOF 值(−1)。由于这种原因,如果使用 cin.get( )(没有参数)并测试 EOF,则必须将返回值赋给 int 变量,而不是 char 变量。另外,如果将 ch 的类型声明为 int,而不是 char,则必须在显示 ch 时将其强制转换为 char 类型。
程序清单 5.19 将程序清单 5.18 进行了修改,使用了 cin.get( ) 方法。它还通过将字符输入与 while 循环测试合并在一起,使代码更为简洁。
程序清单 5.19 textin4.cpp
注意:
有些系统要么不支持来自键盘的模拟 EOF,要么支持地不完善,在这种情况下,上述示例将无法正常运行。如果使用 cin.get( ) 来锁住屏幕直到可以阅读它,这将不起作用,因为检测 EOF 时将禁止进一步读取输入。然而,可以使用程序清单 5.14 那样的计时循环来使屏幕停留一段时间。还可使用第 17 章将介绍的 cin.clear( ) 来重置输入流。
下面是该程序的运行情况:
下面分析一下循环条件:
子表达式 ch=cin.get( ) 两端的括号导致程序首先计算该表达式。为此,程序必须首先调用 cin.get( ) 函数,然后将该函数的返回值赋给 ch。由于赋值语句的值为左操作数的值,因此整个子表达式变为 ch 的值。如果这个值是 EOF,则循环将结束,否则继续。该测试条件中所有的括号都是必不可少的。如果省略其中的一些括号:
由于!=运算符的优先级高于=,因此程序将首先对 cin.get( ) 的返回值和 EOF 进行比较。比较的结果为 false 或 true,而这些 bool 值将被转换为 0 或 1,并本质赋给 ch。
另一方面,使用 cin.get(ch)(有一个参数)进行输入时,将不会导致任何类型方面的问题。前面讲过,cin.get(char) 函数在到达 EOF 时,不会将一个特殊值赋给 ch。事实上,在这种情况下,它不会将任何值赋给 ch。ch 不会被用来存储非 char 值。表 5.3 总结了 cin.get(char) 和 cin.get( ) 之间的差别。
表 5.3 cin.get(ch) 与 cin.get( )
属 性 | cin.get(ch) | ch=cin.get( ) |
---|---|---|
传递输入字符的方式 | 赋给参数 ch | 将函数返回值赋给 ch |
用于字符输入时函数的返回值 | istream 对象(执行 bool 转换后为 true) | int 类型的字符编码 |
到达 EOF 时函数的返回值 | istream 对象(执行 bool 转换后为 false) | EOF |
那么应使用 cin.get( ) 还是 cin.get(char) 呢?使用字符参数的版本更符合对象方式,因为其返回值是 istream 对象。这意味着可以将它们拼接起来。例如,下面的代码将输入中的下一个字符读入到 ch1 中,并将接下来的一个字符读入到 ch2 中:
这是可行的,因为函数调用 cin.get(ch1) 返回一个 cin 对象,然后便可以通过该对象调用 get(ch2)。
get( ) 的主要用途是能够将 stdio.h 的 getchar( ) 和 putchar( ) 函数转换为 iostream 的 cin.get( ) 和 cout.put( ) 方法。只要用头文件 iostream 替换 stdio.h,并用作用相似的方法替换所有的 getchar( ) 和 putchar( ) 即可。(如果旧的代码使用 int 变量进行输入,而所用的实现包含 put( ) 的多个原型,则必须做进一步的调整。)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论