《C++ Primer Plus》第17章:输入、输出和文件(1)

C++的I/O系统基于流和缓冲区,通过iostream库中的类如cin、cout进行操作。输入输出不仅限于键盘和屏幕,还可以处理文件。iostream库包括streambuf、ios_base、ios、ostream和istream等类,管理流的读写和缓冲。C++98及后续版本对I/O进行了标准化和模板化,支持不同字符类型的I/O。重定向功能允许改变标准输入和输出的源和目标。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对C++输入和输出(简称I/O)的讨论提出了一个问题。一方面,几乎每个程序都要使用输入和输出,因此了解如何使用它们是每个学习计算机语言的人面临的首要任务;另一方面,C++使用了很多较为高级的语言特性来实现输入和输出,其中包括类、派生类、函数重载、虚函数、模板和多重继承。因此,要真正理解C++ I/O,必须了解C++的很多内容。为了帮助您起步,本书的开始几章介绍了使用 istream 类对象 cin 和 ostream 对象 cout 进行输入和输出的基本方法,同时使用了 ifstream 和 ofstream 对象进行文件输入和输出。本章将更详细地介绍C++的输入和输出类,看看它们是如何设计的,学习如何控制输出格式(如果您跳过很多章,直接学习高级格式,可浏览一下讨论该主题的一些小节,注意其中的技术,而忽略解释)。

用于文件输入和输出的C++工具都是基于 cin 和 cout 所基于的基本类定义,因此本章以对控制台 I/O(键盘和屏幕)的讨论为跳板,来研究文件 I/O。

ANSI/ISO C++ 标准委员会的工作是让 C++ I/O 与现有的 C I/O 更加兼容,这给传统的 C++ 做法带来了一些变化。

C++ 输入和输出概述

多数计算机语言的输入和输出是以语言本身为基础实现的。例如,从诸如 BASIC 和 Pascal 等语言的关键字列表中可知,PRINT 语句、Writeln 语句以及其他类似的语句都是语言词汇表的组成部分,但 C 和 C++ 都没有将输入和输出建立在语言中。这两种语言的关键字包括 for 和 if,但不包括与 I/O 有关的内容。C 语言最初把 I/O 留给了编译器实现人员。这样做的一个原因是为了让实现人员能够自由地设计 I/O 函数,使之最适合于目标计算机的硬件要求。实际上,多数实现人员都把 I/O 建立在最初为 UNIX 环境开发的库函数的基础之上。ANSI C 正式承认这个 I/O 软件包时,将其称为标准输入/输出包,并将其作为标准C库不可或缺的组成部分。C++也认可这个软件包,因此如果熟悉 stdio.h 文件中声明的 C 函数系列,则可以在 C++ 程序中使用它们(较新的实现使用头文件 cstdio 来支持这些函数)。

然而,C++ 依赖于 C++ 的 I/O 解决方案,而不是 C 语言的 I/O 解决方案,前者是在头文件 iostream(以前为 iostream.h)和 fstream(以前为 fstream.h)中定义一组类。这个类库不是正式语言定义的组成部分(cin和istream不是关键字);毕竟计算机语言定义了如何工作(例如如何创建类)的规则,但没有定义应按照这些规则创建哪些东西。然而,正如 C 实现自带了一个标准函数库一样,C++也自带了一个标准类库。首先,标准类库是一个非正式的标准,只是由头文件 iostream 和 fstream 中定义的类组成。ANSI/ISO C++委员会决定把这个类正式作为一个标准类库,并添加其他一些标准类,如第16章讨论的那些类。本章将讨论标准 C++ I/O。但首先看一看 C++ I/O 的概念框架。

17.1.1 流和缓冲区

C++ 程序把输入和输出看作字节流。输入时,程序从输入流中抽取字节;输出时,程序将字节插入到输出流中。对于面向文本的程序,每个字节代表一个字符,更通俗地说,字节可以构成字符或数值数据的二进制表示。输入流中的字节可能来自键盘,也可能来自存储设备(如硬盘)或其他程序。同样,输出流中的字节可以流向屏幕、打印机、存储设备或其他程序。流充当了程序和流源或流目标之间的桥梁。这使得C++程序可以以相同的方式对待来自键盘的输入和来自文件的输入。C++程序只是检查字节流,而不需要知道字节来自何方。同理,通过使用流,C++程序处理输出的方式将独立于其去向。因此管理输入包含两步:

  • 将流与输入去向的程序关联起来。
  • 将流与文件链接起来。

换句话说,输入流需要两个连接,每端各一个。文件端部连接了提供了流的来源,程序端连接将流的流出部分转储到程序中(文件端连接可以是文件,也可以是设备,如键盘)。同样,对输出的管理包括将输出流连接到程序以及将输出目标与流关联起来。这就像字节(而不是水)引入到水管中。

通常,通过使用缓冲区可以更高效地处理输入和输出。缓冲区是用作中介的内存块,它是将信息从设备传输到程序或从程序传输给设备的临时存储工具。通常,像磁盘驱动器这样的设备以 512 字节(或更多)的块为单位来传输信息,而程序通常每次只能处理一个字节的信息。缓冲区帮助匹配这两种不同的信息传输速率。例如,假设程序要计算记录在硬盘文件中的金额。程序可以从文件中读取一个字符,处理它,再从文件中读取下一个字符,再处理,依此类推。从磁盘文件中每次读取一个字符需要大量的硬件活动,速度非常慢。缓冲方法则从磁盘上读取大量信息,将这些信息存储在缓冲区中,然后每次从缓冲区读取一个字节。因为从内存中读取单个字节的速度比从硬盘上读取快很多,所以这种方法更快,也更方便。当然,到达缓冲区尾部后,程序将从磁盘上读取另一块数据。这种原理与水库在暴风雨中收集几兆加仑流量的水,然后以比较文明的速度给您家里供水是一样的。输出时,程序首先填满缓冲区,然后把整块数据传输给硬盘,并清空缓冲区,以备下一批输出使用。这被称为刷新缓冲区(flushing the buffer)。

键盘输入每次提供一个字符,因此在这种情况下,程序无需缓冲区来帮助匹配不同的数据传输速率。然而,对键盘输入进行缓冲可以让用户在将输入传输给程序之前返回并更正。C++程序通常在用户按下回车键时刷新输入缓冲区。这是为什么本书的例子没有一开始就处理输入,而是等到用户按下回车键后再处理的原因。对于屏幕输出,C++程序通常在用户发送换行符时刷新输出缓冲区。程序也可能会在其他情况下刷新输出,例如输入即将到来时,这取决于实现。也就是说,当程序到达输入语句时,它将刷新输出缓冲区中当前所有的输出。与 ANSI C 一致的 C++ 实现是这样工作的。

流、缓冲区和 iostream 文件

管理流和缓冲区的工作有点复杂,但 iostream(以前为 iostream.h)文件中包含一些专门设计用来实现、管理流和缓冲区的类。C++98 版本 C++ I/O 定义了一些类模板,以支持 char 和 wchar_t 数据;C++11 添加了 char16_t 和 char32_t 具体化。通过使用 typedef 工具,C++ 使得这些模板 char 具体化能够模仿传统的非模板 I/O 实现。

  • streambuf 类为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法;
  • ios_base 类表示流的一般特征,如是否可读取、是二进制还是文本流等;
  • ios类基于 ios_base,其中包括了一个指向 streambuf 对象的指针成员;
  • ostream 类是从 ios 类派生而来的,提供了输出方法;
  • istream 类也是从 ios 类派生而来的,提供了输入方法;
  • iostream 类是基于 istream 和 ostream 类的,因此继承了输入方法和输出方法。

要使用这些工具,必须使用适当的类对象。例如,使用 ostream 对象(如 cout)来处理输出。创建这样的对象将打开一个流,自动创建缓冲区,并将其与流关联起来,同时使得能够使用类成员函数。

重定义 I/O

ISO/ANSI 标准 C++98 对 I/O 作了两方面的修订。首先是从 stream.h 到 ostream 的变化,用 ostream 将类放到 std 名称空间中。其次,I/O类被重新编写。为成为国际语言,C++必须能够处理需要16位的国际字符集或更宽的字符类型。因此,该语言在传统的 8 位 char(“窄”)类型的基础上添加了 wchar_t(“宽”)字符类型;而 C++11 添加了类型 char16_t 和 char32_t。每种类型都需要有自己的 I/O 工具。标准委员会并没有开发两套(现在为 4 套)独立的类,而是开发了1套 I/O 类模板,其中包括 basic_istream<charT, traits\charT> > 和 basic_ostream<charT, traits<charT> >。traits<charT> 模板是一个模板类,为字符类型定义了具体特征,如如何比较字符是否相等以及字符的 EOF 值等。该 C++11 标准提供了 I/O 的 char 和 wchar_t 具体化。例如,istream 和 ostream 都是 char 具体化的 typedef。同样,wistream 和 wostream 都是 wchar_t 具体化。例如,wcout 对象用于输出宽字符流。头文件 ostream 中包含了这些定义。

ios 基类中的一些独立于类型的信息被移动到新的 ios_base 类中,这包括各种格式化常量,例如 ios::fixed(现在为 ios_base::fixed)。另外,ios_base 还包含了一些老式 ios 中没有的选项。

C++ 的 iostream 类库管理了很多细节。例如,在程序中包含 iostream 文件将自动创建 8 个流对象(4个用于窄字符流,4个用于宽字符流)。

  • cin对象对应于标准输入流。在默认情况下,这个流被关联到标准输入设备(通常为键盘)。wcin 对象与此类似,但处理的是 wchar_t 类型。
  • cout 对象与标准输出流相对应。在默认情况下,这个流被关联到标准输出设备(通常为显示器)。wcout 对象与此类似,但处理的是 wchar_t 类型。
  • cerr 对象与标准错误流相对应,可用于显示错误消息。在默认情况下,这个流被关联到标准输出设备(通常为显示器)。这个流没有被缓冲,这意味着信息将被直接发送给屏幕,而不会等到缓冲区填满或新的换行符。wcerr 对象与此类似,但处理的是 wchar_t 类型。
  • clog 对象也对应着标准错误流。在默认情况下,这个流被关联到标准输出设备(通常为显示器)。这个流被缓冲。wclog 对象与此类似,但处理的是 wchar_t 类型。
  • 对象代表流——这意味着什么呢?当 iostream 文件为程序声明一个 cout 对象时,该对象将包含存储了与输出有关的信息的数据成员,如显示数据时使用的字段宽度、小数位数、显示整数时采用的计数方法以及描述用来处理输出流的缓冲区的 streambuff 对象的地址。下面的语句通过只想的 streambuf 对象将字符串“Bjarna free”中的字符放到 cout 管理的缓冲区中:
    cout << "Bjarne free";
    
    ostream 类定义了上述语句中使用的 operator<<() 函数, ostream 类还支持 cout 数据成员以及其他大量的类方法(如本章稍后将讨论的那些方法)。另外,C++ 注意到,来自缓冲区的输出被导引到标准输出(通常是显示器,由操作系统提供)。总之,流的一端与程序相连,另一端与标准输出相连,cout 对象凭借 streambuf 对象的帮助,管理着流中的字节流。

重定向

标准输入和输出流通常连接着键盘和屏幕。但很多操作系统(包括 UNIX、Linux 和 Windows)都支持重定向,这个工具使得能够改变标准输入和标准输出。例如,假设有一个名为 counter.exe 的、可执行的 Windows 命令提示符 C++ 程序,它能够计算输入中的字符数,并报告结果(在大多数Windows系统中,可以选择“开始” > “程序”,再单击 “命令提示符” 来打开命令提示符窗口)。该程序的运行情况如下:

C>counter
Hello
and goodbye!
Control-Z		 		<< simulated end-of-file
Input contained 19 characters.
C>

其中的输入来自键盘,输出的被显示到屏幕上。
通过输入重定向(<) 和输出重定向(>),可以使用上述程序计算文件 oklahoma 中的字符数,并将结果放到 cow_cnt 文件中:

cow_cnt file:
C>counter <oklahama >cow_cnt
C>

命令行中的 <oklahoma 将标准输入与 oklahoma 文件关联起来,使 cin 从该文件(而不是键盘)读取输入。换句话说,操作系统改变了输入流的流入端连接,而流出端仍然与程序相连。命令行中的 >cow_cnt 将标准输出与 cow_cnt 文件关联起来,导致 cout 将输出发送给文件(而不是屏幕)。也就是说,操作系统改变了输出流的流出端连接,而流入端仍与程序相连。DOS、Windows 命令提示符模式、Linux 和 UNIX 能自动识别这种重定向语法(除早期的 DOS 外,其他操作系统都允许在重定向运算符与文件名之间加上可选的空格)。

cout 代表的标准输出流是程序输出的常用通道。标准错误流(由cerr和clog代表)用于程序的错误消息。默认情况下,这3个对象都被发送给显示器。但对标准输出重定向并不会影响 cerr 或 clog,因此,如果使用其中一个对象来打印错误消息,程序将在屏幕上显示错误消息,即使常规的 cout 输出被重定向到其他地方。例如,请看下面的代码片段:

if (success)
	std::cout << "Here come the goodies!\n";
else {
	std::cerr << "Something horrible has happened.\n";
	exit(1);
}

如果重定向没有起作用,则选定的消息都将被显示在屏幕上。然而,如果程序输出被重定向到一个文件,则第一条消息(如果被选定)将被发送到文件中,而第二条消息(如果被选定)将被发送到屏幕。顺便说一句,有些操作系统也允许对标准错误进行重定向。例如,在 UNIX 和 Linux 中,运算符 2>重定向标准错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值