Oracle Solaris Studio 12.2:C++ 用户指南

13.3 使用传统 iostream

要使用来自传统 iostream 库的例程,必须针对所需的库部分将头文件包括进来。下表对头文件进行了具体描述。

表 13–1 iostream 例程头文件

头文件 

说明  

iostream.h

声明 iostream 库的基本功能。

fstream.h

声明文件专用的 iostreamstreambuf。包括了 iostream.h

strstream.h

声明字符数组专用的 iostreamstreambuf。包括了 iostream.h

iomanip.h

声明操纵符:iostream 中插入或提取的值有不同的作用。包括了 iostream.h

stdiostream.h

(已过时)声明 stdio 文件专用的 iostreamstreambuf。包括了 iostream.h

stream.h

(已过时)包括了 iostream.hfstream.hiomanip.hstdiostream.h。用于兼容 C++ 1.2 版的旧式流。

通常程序中不需要所有这些头文件,而仅包括所需声明的头文件。在兼容模式 (-compat[=4]) 下,传统 iostream 库是 libC 的一部分,它由 CC 驱动程序自动链接。在标准模式(缺省模式)下, libiostream 包含传统 iostream 库。

13.3.1 使用 iostream 进行输出

使用 iostream 进行的输出通常依赖于重载的左移运算符 (<<)(在 iostream 上下文中,称为插入运算符)。要将值输出到标准输出,应将值插入预定义的输出流 cout 中。例如,要将给定值 someValue 发送到标准输出,可以使用以下语句:


cout << someValue;

对于所有内置类型,都会重载插入运算符,且 someValue 表示的值转换为其适当的输出表示形式。例如,如果 someValuefloat<< 运算符会将该值转换为带小数点的适当数字序列。此处是将 float 值插入输出流中,因此 << 称为浮点插入器。通常,如果给定类型 X<< 称为 X 插入器。ios(3CC4) 手册页中讨论了输出格式以及如何控制输出格式。

iostream 库不支持用户定义的类型。如果要定义以自己的方式输出的类型,必须定义一个插入器(即,重载 << 运算符)来正确处理它们。

<< 可以多次应用。要将两个值插入 cout 中,可以使用类似于以下示例中的语句:


cout << someValue << anotherValue;

以上示例的输出在两个值间不显示空格。因此您可能会要按以下方式编写编码:


cout << someValue << " " << anotherValue;

运算符 << 优先作为左移运算符(其内置含义)使用。与其他运算符一样,总是可以使用圆括号来指定操作顺序。使用括号来避免优先级问题是个很好的方法。下列四个语句中,前两个是等价的,但后两个不是。


cout << a+b;              // + has higher precedence than <<
cout << (a+b);
cout << (a&y);            // << has precedence higher than &
cout << a&y;            // probably an error: (cout << a) & y

13.3.1.1 定义自己的插入运算符

以下示例定义了 string 类:


#include <stdlib.h>
#include <iostream.h>


class string {
private:
    char* data;
    size_t size;

public:
    // (functions not relevant here)

    friend ostream& operator<<(ostream&, const string&);
    friend istream& operator>>(istream&, string&);
};

在此示例中,必须将插入运算符和提取运算符定义为友元,因为 string 类的数据部分是 private


ostream& operator<< (ostream& ostr, const string& output)
{    return ostr << output.data;}

以下是为要用于 string 而重载的 operator<< 的定义。


cout << string1 << string2;

运算符 <<ostream&(也就是对 ostream 的引用)作为其第一个参数,并返回相同的 ostream,这样就可以在一个语句中合并多个插入。

13.3.1.2 处理输出错误

通常情况下,重载 operator<< 时不必检查错误,因为有 iostream 库传播错误。

出现错误时,所在的 iostream 会进入错误状态。iostream 状态中的各个位根据错误的一般类别进行设置。iostream 中定义的插入器不会尝试将数据插入处于错误状态的任何流,因此这种尝试不会更改 iostream 的状态。

通常,处理错误的推荐方法是定期检查某些中心位置中输出流的状态。如果有错误,就应该以某些方式来处理。本章假定您定义了函数 error,该函数可采用字符串并中止程序。error 不是预定义的函数。有关 error 函数的示例,请参见13.3.9 处理输入错误。可以使用运算符 ! 检查 iostream 的状态,如果 iostream 处于错误状态,该运算符会返回非零值。例如:


if (!cout) error("output error");

还有另外一种方法来测试错误。ios 类可定义 operator void *(),它在有错误时返回空指针。您可以使用如下语句:


if (cout << x) return; // return if successful

也可以使用函数 good,它是 ios 的成员:


if (cout.good()) return; // return if successful

错误位在 enum 中声明:


enum io_state {goodbit=0, eofbit=1, failbit=2,
badbit=4, hardfail=0x80};

有关错误函数的详细信息,请参见 iostream 手册页。

13.3.1.3 刷新

与大多数 I/O 库一样,iostream 通常会累积输出并将其发送到较大且效率通常较高的块中。如果要刷新缓冲区,只要插入特殊值 flush。例如:


cout << "This needs to get out immediately." << flush;
 

flush 是一种称为操纵符的对象示例,它是一个值,可以插入 iostream 中以起到一定作用,而不是使输出其值。它实际上是一个函数,采用 ostream&istream& 参数,在对其执行某些操作后返回其参数(请参见13.7 操纵符)。

13.3.1.4 二进制输出

要获得原始二进制形式的值输出,请使用以下示例所示的成员函数 write。该示例显示了原始二进制形式的 x 输出。


cout.write((char*)&x, sizeof(x));

以上示例将 &x 转换为 char*,这违反了类型规程。一般情况下,这样做无关大碍,但如果 x 的类型是具有指针、虚拟成员函数的类或是需要 nontrivial 构造函数操作的类,就无法正确读回以上示例写入的值。

13.3.2 使用 iostream 进行输入

使用 iostream 进行的输入类似于输出。需要使用提取运算符 >>,可以像插入操作那样将提取操作串接在一起。例如:


cin >> a >> b;

该语句从标准输入获得两个值。与其他重载运算符一样,所用的提取器取决于 ab 的类型(如果 ab 的类型不同,则使用两个不同的提取器)。ios(3CC4) 手册页中详细讨论了输入格式以及如何控制输入格式。通常,前导空白字符(空格、换行符、标签、换页等)被忽略。

13.3.3 定义自己的提取运算符

要输入新的类型时,如同重载输出的插入运算符,请重载输入的提取运算符。

string 定义了其提取运算符,如以下代码示例所示:


示例 13–1 string 提取运算符


istream& operator>> (istream& istr, string& input)
{
    const int maxline = 256;
    char holder[maxline];
    istr.get(holder, maxline, ”\n’);
    input = holder;
    return istr;
}

get 函数从输入流 istr 读取字符,并将其存储在 holder 中,直到读取了 maxline-1 字符、遇到新行或 EOF(无论先发生哪一项)。然后,holder 中的数据以空终止。最后,holder 中的字符复制到目标字符串。

按照约定,提取器转换其第一个参数中的字符(此示例中是 istream& istr),将其存储在第二个参数(始终是引用),然后返回第一个参数。因为提取器会将输入值存储在第二个参数中,所以第二个参数必须是引用。

13.3.4 使用 char* 提取器

此处提及这个预定义的提取器是因为它可能产生问题。使用方法如下:


char x[50];
cin >> x;

该提取器跳过前导空白,提取字符并将其复制到 x 中,直至遇到另一个空白字符。最后完成具有终止空 (0) 字符的字符串。因为输入会溢出给定的数组,所以要小心操作。

您还必须确保指针指向了分配的存储。例如,下面列出了一个常见的错误:


char * p; // not initialized
cin >> p;

因为没有告知存储输入数据的位置,所以会导致程序的终止。

13.3.5 读取任何单一字符

除了使用 char 提取器外,还可以使用任一形式的 get 成员函数获取一个字符。例如:


char c;
cin.get(c); // leaves c unchanged if input fails

int b;
b = cin.get(); // sets b to EOF if input fails

注 –

与其他提取器不同,char 提取器不会跳过前导空白


以下方法可以只跳过空格,并在制表符、换行符或任何其他字符处停止:


int a;
do {
    a = cin.get();
   }
while(a ==’ ’);

13.3.6 二进制输入

如果需要读取二进制值(如使用成员函数 write 写入的值),可以使用 read 成员函数。以下示例说明了如何使用 read 成员函数输入原始二进制形式的 x,这是先前使用 write 的示例的反向操作。


cin.read((char*)&x, sizeof(x));

13.3.7 查看输入

可以使用 peek 成员函数查看流中的下一个字符,而不必提取该字符。例如:


if (cin.peek()!= c) return 0;

13.3.8 提取空白

缺省情况下,iostream 提取器会跳过前导空白。可以关闭 跳过标志防止这种情况发生。以下示例先关闭了 cin 跳过空白功能,然后将其打开:


cin.unsetf(ios::skipws); // turn off whitespace skipping
...
cin.setf(ios::skipws); // turn it on again

可以使用 iostream 操纵符 wsiostream 中删除前导空白,不论是否启用了跳过功能。以下示例说明了如何从 iostream istr 中删除前导空白:


istr >> ws;

13.3.9 处理输入错误

按照约定,第一个参数为非零错误状态的提取器不能从输入流提取任何数据,且不能清除任何错误位。失败的提取器至少应该设置一个错误位。

对于输出错误,您应该定期检查错误状态,并在发现非零状态时采取某些操作(诸如终止)。! 运算符测试 iostream 的错误状态。例如,如果输入字母字符用于输入,以下代码就会产生输入错误:


#include <stdlib.h>
#include <iostream.h>
void error (const char* message) {
     cerr << message << "\n";
     exit(1);
}
int main() {
     cout << "Enter some characters: ";
     int bad;
     cin >> bad;
     if (!cin) error("aborted due to input error");
     cout << "If you see this, not an error." << "\n";
     return 0;
}

ios 具有可用于错误处理的成员函数。详细信息请参见手册页。

13.3.10 结合使用 iostreamstdio

可以将 stdio 用于 C++ 程序,但在程序内的同一标准流中混合使用 iostreamstdio 时,可能会发生问题。例如,如果同时向 stdoutcout 写入,会发生独立缓冲,并产生不可预料的结果。如果从 stdincin 两者进行输入,问题会更加严重,因为独立缓冲可能会破坏输入。

要消除标准输入、标准输出和标准错误中的这种问题,就请在执行输入或输出前使用以下指令:它将所有预定义的 iostream 与相应的预定义 stdio 文件连接起来。


ios::sync_with_stdio();

因为在预定义流作为连接的一部分成为无缓冲流时,性能会显著下降,所以该连接不是缺省连接。可以在应用于不同文件的同一程序中同时使用 stdioiostream。也就是说,可以使用 stdio 例程向 stdout 写入,并向连接到 iostream 的其他文件写入。可以打开 stdio 文件进行输入,也可以从 cin 读取,只要不同时尝试从 stdin 读取即可。