14. 数据的输入输出
一、流和缓冲区
C++ 程序把输入和输出看作字节流。输入时,程序从输入流中抽取字节;输出时,程序将字节插入到输出流中。对于面向文本的程序,每个字节代表一个字符。更通俗的说,字节可以构成字符或数值数据的二进制表示。输入流中的字节可能来自键盘,也可能来自存储设备(如硬盘)或其它程序。同样,输出流中的字节可以流向屏幕、打印机、存储设备或其他程序。流充当了程序和流源或目标之间的桥梁。这使得 C++ 程序可以以相同的方式对待来自键盘和来自文件的输入。C++ 程序只是检查字节流,而不需要知道字节来自何方。同理,通过使用流,C++ 程序处理输出的方式将独立于其去向。
换句话说,输入流需要两个连接,每端各一个。文件端部连接提供了流的来源,程序端部连接将流的流出部分转存到文件中(文件端连接可以是文件,也可以是设备,如键盘)。同样,对输出的管理包括将输出流连接到程序以及将输出目标与流管理起来。
通常,通过使用缓冲区可以更高效地处理输入和输出。缓冲区是用作中介的内存块,它是将信息从设备传输到程序或从程序传输给设备的临时存储工具。通常,像磁盘驱动器这样的设备以 512 字节(或更多)的块为单位来传输信息,而程序通常每次只能处理一个字节的信息。缓冲区帮助匹配这两种不同的信息传输速率。
缓冲方法则从磁盘上读取大量数据,将这些信息存储在缓冲区中,然后每次从缓冲区里读取一个字节。因此从内存读取单个字节的速度比从磁盘上读取快的多。当然,到达缓冲区尾部后,程序将从磁盘山读取另一块数据。输出时,程序首先填满缓冲区,然后把整块
键盘输入每次提供一个字符,因此在这种情况下,程序无需缓冲区来帮助匹配不同的数据传输速率。然而,对键盘输入进行缓冲可以让用户在将输入传输给程序之前返回并更正。C++ 程序通常在用户按下回车键是刷新输入缓冲区。对于屏幕输出,C++ 程序通常在用户发送换行符是刷新缓冲区。程序也可能在其它情况下刷新输入。也就是说,当程序到达输入语句时,它将刷新输出缓冲中当前所有的输出。
二、使用cout进行输出
C++ 中提供给了一个 ostream 类,它将数值类型转换为以文本形式表示的字节流。也就是说,ostream 类将数据内部表示(二进制位模式)转换为字符字节组成的输出流。ostream 类将 << 运算符重载为插入运算符,它的返回值类型是 ostream 的引用。也就是说,原型的格式如下:
ostream & operator<<(type)
其中,type 是要显示的数据的类型。返回类型 ostream & 意味着使用该运算符将返回一个指向 ostream 对象的引用。函数定义指出,引用将指向用于调用该运算符的对象。换句话说,运算符函数的返回值为调用运算符对象。
除了各种 operator<<() 函数外,ostream 类还提供了 put() 方法和 write() 方法,前者用于显示字符,后者用于显示字符串。
put() 函数和 << 运算符一样,也是返回一个指向调用对象的引用,因此可以把它拼接输出。在原型合适的情况下,可以将数值型参数用于 put(),让函数原型自动将参数转换为正确的 char 值。
write() 方法显示整个字符串,其模板原型如下:
basic_ostream<charTm traits> & write(const char_type * , streamsize n);
write() 第一个参数提供了要显示的字符串的地址,第二个参数指出要显示多少个字符。使用 cout 调用 write() 时,将调用 char 具体化,因此返回类型为 ostream &。write() 方法不会在遇到空字符时自动停止打印字符,而只是打印指定数组的字符,即使超出了字符串的边界。write() 方法也可用于数值数据。
由于 ostream 类对 cout 对象处理的输出进行缓冲,所以输出不会立即发送到目标地址,而是被存储在缓冲区中,直到缓冲区填满。然后,程序将刷新缓冲区,把内容发送出去,并清空缓冲区,以存储新的数据。我们可以使用控制符 flush 或 endl 手动刷新缓冲区。endl 在刷新缓冲区的同时会插入一个换行符。
三、使用cin进行输入
cin 对象将标准输入表示为字节流。通常情况下,通过键盘来生成这种字符流。cin 对象根据接收值的变量的类型,使用其方法将字符序列转换为所需的类型。典型的而运算符函数的原型如下:
istream * operator>>(type &);
参数和返回值都是引用。引用参数意味着 opeator>>() 函数处理变量本身,而不是像常规参数那样处理它的副本。由于参数类型为引用,因此 cin 能够直接修改用作参数的变量的值。每个抽取运算符都返回调用对象的引用,这使得能够将输入拼接起来。
不同版本的抽取运算查看输入流的方法时相同的。它们跳过空白(空格、换行符和制表符),直到遇到非空白字符。即使对于单字符,情况也是如下。在单字符模式下,>> 运算符见读取该字符,将它放置到指定的位置。在其他模式下,>> 运算符见读取一个指定类型的数据。也就是说,它读取从非空白字符开始,到与目标类型不匹配的第一个字符之间的全部内容。
四、其它istream类成员方法
4.1、单字符输入
在使用 char 参数或没有参数的情况下,get() 方法读取下一个输入字符,即使该字符是空格、制表符或换行符。get(char &) 版本将输入字符赋给其参数,而 get(void) 版本见输入字符转换为整型,并将其返回。
get(char &) 成员函数返回一个指向用于调用它的 istream 对象的引用,这意味着可以拼接 get(char &) 后面的其它抽取。如果 cin.get(char &) 到达文件尾(无论是真正的文件尾,还是通过键盘仿真的文件尾),它都不会给其参数赋值。只要存在有效输入,cin.get(char &) 的返回值都将是 cin。
get(void) 成员函数还读取空白,但使用返回值来将输入传递给程序。get(void) 成员函数的返回值为 int。由于返回值不是类对象,因此不能对它应用成员运算符和抽取运算符。到达文件尾后(无论是真正的文件尾,还是通过键盘仿真的文件尾),cin.get(void) 都将返回 EOF(头文件 iostream 提供的一个符号常量)。
4.2、字符串输入
getline() 成员函数和 get() 字符串读取版本都读取字符串,它们的函数特征标相同。
istream & get(char *, int, char);
istream & get(char *, int);
istream & getline(char *, int, char);
istream & getline(char *, int);
第一个参数是用于放置输入字符串的内存单元的地址。第二个参数比要读取的最大字符数大 1(额外的一个字符用于存储结尾的空字符,以便将输入存储为一个字符串)。第三个参数指定用作分界符的字符,只有两个参数的版本将换行符用作分界符。上述函数都在读取最大数目的字符或遇到分界符后为止。
get() 和 getline() 之间的主要区别在于,get() 将换行符留在输入流中,而 getline() 抽取并丢弃输入流中的换行符。
ignore() 成员函数接受两个参数,一个是数字,指定要读取的最大字符数。另一个是字符,用作输入分界符。
istream & ignore(int = 1, int = EOF);
默认参数 EOF 导致 ignore() 读取指定数目的字符或读取到文件尾。该函数返回调用对象,这使得能够拼接函数调用。
4.3、其它istream方法
read() 函数读取指定数目的字节,并将它们存储在指定的位置中。read() 不会在输入后加上空值字符,因此不能将输入转换为字符串。该方法的返回类型为 istream &。
peek() 函数返回输入中的下一个字符,但不能抽取输入流中的字符。也就是说,它使得能够查看下一个字符。
gcount() 方法返回最后一个非格式化抽取方法读取的字符数。这意味着字符是由 get()、getline()、ignore() 或 read() 方法读取的,不是由抽取运算符(>>)读取的,抽取运算符对输入进行格式化,使之与特定的数据类型匹配。
putback() 函数将一个字符插入到输入字符串中,被插入的字符价格是下一条输入语句读取的第一个字符。putback() 方法接受一个 char 参数(要插入的字符),其返回类型为 istream &,这使得可以将该函数调用与其它 istream 方法拼接起来。