A.3. The IO Library RevisitedA.3. 再谈 IO 库In Chapter 8 we introduced the basic architecture and most commonly used parts of the IO library. This Appendix completes our coverage of the IO library. 第八章介绍过 IO 库的基本体系结构以及最常使用的部分,本附录完成对 IO 库的讨论。 A.3.1. Format StateA.3.1. 格式状态In addition to a condition state (Section 8.2, p. 287), each iostream object also maintains a format state that controls the details of how IO is formatted. The format state controls aspects of formatting such as the notational base for an integral value, the precision of a floating-point value, the width of an output element, and so on. The library also defines a set of manipulators (listed in Tables A.2 (p. 829) and A.3 (p. 833) for modifying the format state of an object. Simply speaking, a manipulator is a function or object that can be used as an operand to an input or output operator. A manipulator returns the stream object to which it is applied, so we can output multiple manipulators and data in a single statement. 除了条件状态(第 8.2 节)之外,每个 iostream 对象还维持一个控制 IO 格式化细节的格式状态。格式状态控制格式化特征,如是整型值的基数、浮点值的精度、输出元素的宽度等。标准库还定义了一组操纵符(在表 A.2 和表 A.3 列出)来修改对象的格式状态。简单说来,操纵符是可用作输入或输出操作符操作数的函数或对象。操纵符返回其应用于的流对象,所以可以在一个语句中输出多个操纵符和数据。
When we read or write a manipulator, no data are read or written. Instead, an action is taken. Our programs have already used one manipulator, endl, which we "write" to an output stream as if it were a value. But endl isn't a value; instead, it performs an operation: It writes a newline and flushes the buffer. 读写操纵符的时候,不读写数据,相反,会采取某种行动。示例程序已经使用过一个操纵符——endl,我们将它“写”至输出流,就好像它是一个值一样。但 endl 并不是一个值,相反,它执行一个操作:写换行符并刷新缓冲区。 A.3.2. Many Manipulators Change the Format StateA.3.2. 许多操纵符改变格式状态Many manipulators change the format state of the stream. They change the format of how floating-pointer numbers are printed or whether a bool is displayed as a numeric value or using the bool literals, true or false, and so forth. 许多操纵符改变流的格式状态。它们改变显示浮点数的格式,将 bool 值显示为数值还是使用 bool 字面值 true 和 false 的格式,诸如此类。
Most of the manipulators that change the format state provide set/unset pairs; one manipulator sets the format state to a new value and the other unsets it, restoring the normal default formatting. 大多数改变格式状态的操纵符提供设置/复原对,一个操纵符将格式状态置为新值而另一个进行复原,恢复常规默认格式。 The fact that a manipulator makes a persistent change to the format state can be useful when we have a set of IO operations that want to use the same formatting. Indeed, some programs take advantage of this aspect of manipulators to reset the behavior of one or more formatting rules for all its input or output. In such cases, the fact that a manipulator changes the stream is a desirable property. 操纵符进行格式状态的持久改变,在有一组 IO 操作希望使用相同格式化的时候,这一事实有用。事实上,一些各市利用操纵符的这个特征,为自己的所有输入或输出重置一个或多个格式化规则的行为。这种情况下,操纵符改变流是希望得到的性质。 However, many programs (and, more importantly, programmers) expect the state of the stream to match the normal library defaults. In these cases, leaving the state of the stream in a nonstandard state can lead to errors. 但是,许多程序(更重要的是,许多程序员)希望流的状态与标准库默认值匹配。这些情况下,使流状态停留在非标准状态可能会导致错误。
Using flags Operation to Restore the Format State用 flags 操纵恢复格式状态An even better approach to managing changes to format state uses the flags operations. The flags operations are similar to the rdstate and setstate operations that manage the condition state of the stream. In this case, the library defines a pair of flags functions: 管理格式状态改变的一个更好的办法是使用 flags 操作。flags 操作类似于管理流的条件状态的 rdstate 和 setstate 操作。这种情况下,标准库定义了一对 flags 函数:
We can use these functions to remember and restore the format state of either an input or output stream: 可以使用这些函数记住并恢复输入或输出流的格式状态: void display(ostream& os) { // remember the current format state ostream::fmtflags curr_fmt = os.flags(); // do output that uses manipulators that change the format state of os os.flags(curr_fmt); // restore the original format state of os } A.3.3. Controlling Output FormatsA.3.3. 控制输出格式Many of the manipulators allow us to change the appearance of our output. There are two broad categories of output control: controlling the presentation of numeric values and controlling the amount and placment of padding. 许多操纵符使我们能够改变输出的外观。有两大类的输出控制:控制数值的表示,以及控制填充符的数量和布局。 Controlling the Format of Boolean Values控制布尔值和格式One example of a manipulator that changes the formatting state of its object is the boolalpha manipulator. By default, bool values print as 1 or 0. A true value is written as the integer 1 and a false value as 0. We can override this formatting by applying the boolalpha manipulator to the stream: 改变对象格式化状态的操纵符的一个例子是 boolalpha 操纵符。默认情况下,将 bool 值显示为 1 或 0,true 值显示为 1,而 false 值显示为 0。可以通过流的 boolalpha 操纵符覆盖这个格式化: cout << "default bool values: " << true << " " << false << "\nalpha bool values: " << boolalpha << true << " " << false << endl; When executed, the program generates the following: 执行时,这段程序产生下面的输出: default bool values: 1 0 alpha bool values: true false Once we "write" boolalpha on cout, we've changed how cout will print bool values from this point on. Subsequent operations that print bools will print them as either true or false. 一旦将 boolalpha “写”至 cout,从这个点起就改变了 cout 将怎样显示 bool 值,后续显示 bool 值的操作将用 true 或 false 进行显示。 To undo the format state change to cout, we must apply noboolalpha: 要取消 cout 的格式状态改变,必须应用 noboolalpha: bool bool_val; cout << boolalpha // sets internal state of cout << bool_val << noboolalpha; // resets internal state to default formatting Now we change the formatting of bool values only to print of bool_val and immediately reset the stream back to its initial state. 现在只改变 bool 值的格式化来显示 bool_val,并且立即将流重置为原来的状态。 Specifying the Base for Integral Values指定整型值的基数By default, integral values are written and read in decimal notation. The programmer can change the notational base to octal or hexadecimal or back to decimal (the representation of floating-point values is unaffected) by using the manipulators hex, oct, and dec: 默认情况下,用十进制读写整型值。通过使用操纵符 hex、oct 和 dec,程序员可以将表示进制改为八进制、十六进制或恢复十进制(浮点值的表示不受影响): const int ival = 15, jval = 1024; // const, so values never change cout << "default: ival = " << ival << " jval = " << jval << endl; cout << "printed in octal: ival = " << oct << ival << " jval = " << jval << endl; cout << "printed in hexadecimal: ival = " << hex << ival << " jval = " << jval << endl; cout << "printed in decimal: ival = " << dec << ival << " jval = " << jval << endl; When compiled and executed, the program generates the following output: 编译和执行的时候,程序产生下面的输出: default: ival = 15 jval = 1024 printed in octal: ival = 17 jval = 2000 printed in hexadecimal: ival = f jval = 400 printed in decimal: ival = 15 jval = 1024 Notice that like boolalpha, these manipulators change the format state. They affect the immediately following output, and all subsequent integral output, until the format is reset by invoking another manipulator. 注意,像 boolalpha 一样,这些操纵符改变格式状态。它们影响紧接在后面的输出,以及所有后续的整型输出,直到通过调用另一操纵符重围格式为止。 Indicating Base on the Output指出输出的基数By default, when we print numbers, there is no visual cue as to what notational base was used. Is 20, for example, really 20, or an octal representation of 16? When printing numbers in decimal mode, the number is printed as we expect. If we need to print octal or hexadecimal values, it is likely that we should also use the showbase manipulator. The showbase manipulator causes the output stream to use the same conventions as used for specifying the base of an integral constant: 默认情况下,显示数值的时候,不存在关于所用基数的可见记号。例如,20 是 20,还是 16 的八进制表示?按十进制模式显示数值的时候,会按我们期待的格式打印数值。如果需要打印八进制或十六进制值,可能应该也使用 showbase 操纵符。showbase 操纵符导致输出流使用的约定,与指定整型常量基数所用的相同:
Here is the program revised to use showbase: 修改程序使用 showbase 如下: const int ival = 15, jval = 1024; // const so values never change cout << showbase; // show base when printing integral values cout << "default: ival = " << ival << " jval = " << jval << endl; cout << "printed in octal: ival = " << oct << ival << " jval = " << jval << endl; cout << "printed in hexadecimal: ival = " << hex << ival << " jval = " << jval << endl; cout << "printed in decimal: ival = " << dec << ival << " jval = " << jval << endl; cout << noshowbase; // reset state of the stream The revised output makes it clear what the underlying value really is: 修改后的输出使得基础值到底是什么很清楚: default: ival = 15 jval = 1024 printed in octal: ival = 017 jval = 02000 printed in hexadecimal: ival = 0xf jval = 0x400 printed in decimal: ival = 15 jval = 1024 The noshowbase manipulator resets cout so that it no longer displays the notational base of integral values. noshowbase 操纵符重置 cout,以便它不再显示整型值的表示基数。 By default, hexadecimal values are printed in lowercase with a lowercase x. We could display the X and the hex digits af as uppercase by applying the uppercase manipulator. 默认情况下,十六进制值用带小写 x 的小写形式打印。可以应用 uppercase 操纵符显示 X 并将十六进制数字 a - f 显示为大写字母。 cout << uppercase << showbase << hex << "printed in hexadecimal: ival = " << ival << " jval = " << jval << endl << nouppercase << endl; The preceding program generates the following output: 前面的程序产生下面的输出:
printed in hexadecimal: ival = 0XF jval = 0X400
To revert back to the lowercase x, we apply the nouppercase manipulator. 要恢复小写,就应用 nouppercase 操纵符。 Controlling the Format of Floating-Point Values控制浮点值的格式There are three aspects of formatting floating-point values that we can control: 对于浮点值的格式化,可以控制下面三个方面:
By default, floating-point values are printed using six digits of precision. If the value has no fractional part, then the decimal point is omitted. Whether the number is printed using decimal or scientific notation depends on the value of the floating-point number being printed. The library chooses a format that enhances readability of the number. Very large and very small values are printed using scientific notation. Other values use fixed decimal. 默认情况下,使用六位数字的精度显示浮点值。如果值没有小数部分,则省略小数点。使用小数形式还是科学记数法显示数值取决于被显示的浮点数的值,标准库选择增强数值可读性的格式,非常大和非常小的值使用科学记数法显示,其他值使用小数形式。 Specifying How Much Precision to Print指定显示精度By default, precision controls the total number of digits that are printed. When printed, floating-point values are rounded, not truncated, to the current precision. Thus, if the current precision is four, then 3.14159 becomes 3.142; if the precision is three, then it is printed as 3.14. 默认情况下,精度控制显示的数字总位数。显示的时候,将浮点值四舍五入到当前精度。因此,如果当前精度是 4,则 3.14159 成为 3.142;如果精度是 3,打印为 3.14。 We can change the precision through a member function named precision or by using the setprecision manipulator. The precision member is overloaded (Section 7.8, p. 265): One version takes an int value and sets the precision to that new value. It returns the previous precision value. The other version takes no arguments and returns the current precision value. The setprecision manipulator takes an argument, which it uses to set the precision. 通过名为 precision 的成员函数,或者通过使用 setprecision 操纵符,可以改变精度。precision 成员是重载的(第 7.8 节):一个版本接受一个 int 值并将精度设置为那个新值,它返回先前的精度值;另一个版本不接受实参并返回当前精度值。setprecision 操纵符接受一个实参,用来设置精度。 The following program illustrates the different ways we can control the precision use when printing floating point values: 下面的程序说明控制显示浮点值所用精度的不同方法: // cout.precision reports current precision value cout << "Precision: " << cout.precision() << ", Value: " << sqrt(2.0) << endl; // cout.precision(12) asks that 12 digits of precision to be printed cout.precision(12); cout << "Precision: " << cout.precision() << ", Value: " << sqrt(2.0) << endl; // alternative way to set precision using setprecision manipulator cout << setprecision(3); cout << "Precision: " << cout.precision() << ", Value: " << sqrt(2.0) << endl; When compiled and executed, the program generates the following output: 编译并执行后,程序产生下面的输出: Precision: 6, Value: 1.41421 Precision: 12, Value: 1.41421356237 Precision: 3, Value: 1.41 This program calls the library sqrt function, which is found in the cmath header. The sqrt function is overloaded and can be called on either a float, double, or long double argument. It returns the square root of its argument. 这个程序调用标准库中的 sqrt 函数,可以在头文件 cmath 中找到它。sqrt 函数量重载的,可以用 float、double 或 long double 实参调用,它返回实参的平方根。
Controlling the Notation控制记数法By default, the notation used to print floating-point values depends on the size of the number: If the number is either very large or very small, it will be printed in scientific notation; otherwise, fixed decimal is used. The library chooses the notation that makes the number easiest to read. 默认情况下,用于显示浮点值的记数法取决于数的大小:如果数很大或很小,将按科学记数法显示,否则,使用固定位数的小数。标准库选择使得数容易阅读的记数法。
If we want to force either scientific or fixed notation, we can do so by using the appropriate manipulator: The scientific manipulator changes the stream to use scientific notation. As with printing the x on hexadecimal integral values, we can also control the case of the e in scientific mode through the uppercase manipulator. The fixed manipulator changes the stream to use fixed decimal. 如果希望强制科学记数法或固定位数小数表示,可以通过使用适当的操纵符做到这一点:scientific 操纵符将流变为使用科学记数法。像在十六进制值上显示 x 一样,也可以通过 uppercase 操纵符控制科学记数法中的 e。fixed 操纵符将流为使用固定位数小数表示。 These manipulators change the default meaning of the precision for the stream. After executing either scientific or fixed, the precision value controls the number of digits after the decimal point. By default, precision specifies the total number of digitsboth before and after the decimal point. Using fixed or scientific lets us print numbers lined up in columns. This strategy ensures that the decimal point is always in a fixed position relative to the fractional part being printed. 这些操纵符改变流精度的默认含义。执行 scientific 或 fixed 之后,精度值控制小数点之后的数位。默认情况下,精度指定数字的总位数——小数点之前和之后。使用 fixed 或 scientific 命名我们能够按列对齐来显示数,这一策略保证小数点总是在相对于被显示的小数部分固定的位置。 Reverting to Default Notation for Floating-Point Values恢复浮点值的默认记数法Unlike the other manipulators, there is no manipulator to return the stream to its default state in which it chooses a notation based on the value being printed. Instead, we must call the unsetf member to undo the change made by either scientific or fixed. To return the stream to default handling of float values we pass unsetf function a library-defined value named floatfield: 与其他操纵符不同,不存在将流恢复为根据被显示值选择记数法的默认状态的操纵符,相反,我们必须调用 unsetf 成员来取消 scientific 或 fixed 所做的改变。要将流恢复为浮点值的默认处理,将名为 floatfield 的标准库定义值传给 unsetf 函数:
// reset to default handling for notation
cout.unsetf(ostream::floatfield);
Except for undoing their effect, using these manipulators is like using any other manipulator: 除了取消它们的效果之外,使用这些操纵符像使用任意其他操纵符一样:
cout << sqrt(2.0) << '\n' << endl;
cout << "scientific: " << scientific << sqrt(2.0) << '\n'
<< "fixed decimal: " << fixed << sqrt(2.0) << "\n\n";
cout << uppercase
<< "scientific: " << scientific << sqrt(2.0) << '\n'
<< "fixed decimal: " << fixed << sqrt(2.0) << endl
<< nouppercase;
// reset to default handling for notation
cout.unsetf(ostream::floatfield);
cout << '\n' << sqrt(2.0) << endl;
produces the following output: 产生如下输出: 1.41421 scientific: 1.414214e+00 fixed decimal: 1.414214 scientific: 1.414214E+00 fixed decimal: 1.414214 1.41421 Printing the Decimal Point显示小数点By default, when the fractional part of a floating-point value is 0, the decimal point is not displayed. The showpoint manipulator forces the decimal point to be printed: 默认情况下,当浮点值的小数部分为 0 的时候,不显示小数点。showpoint 操纵符强制显示小数点: cout << 10.0 << endl; // prints 10 cout << showpoint << 10.0 // prints 10.0000 << noshowpoint << endl; // revert to default handling of decimal point The noshowpoint manipulator reinstates the default behavior. The next output expression will have the default behavior, which is to suppress the decimal point if the floating-point value has a 0 fractional part. noshowpoint 操纵符恢复默认行为。下一个输出表达式将具有默认行为,即,如果浮点值小数部分为 0,就取消小数点。 Padding the Output填充输出When printing data in columns, we often want fairly fine control over how the data are formatted. The library provides several manipulators to help us accomplish the control we might need: 按栏显示数据的时候,经常很希望很好地控制数据的格式化。标准库提供下面几个操纵帮助我们实现需要的控制:
The following program illustrates these manipulators 下面程序段说明了这些操纵符: int i = -16; double d = 3.14159; // pad first column to use minimum of 12 positions in the output cout << "i: " << setw(12) << i << "next col" << '\n' << "d: " << setw(12) << d << "next col" << '\n'; // pad first column and left-justify all columns cout << left << "i: " << setw(12) << i << "next col" << '\n' << "d: " << setw(12) << d << "next col" << '\n' << right; // restore normal justification // pad first column and right-justify all columns cout << right << "i: " << setw(12) << i << "next col" << '\n' << "d: " << setw(12) << d << "next col" << '\n'; // pad first column but put the padding internal to the field cout << internal << "i: " << setw(12) << i << "next col" << '\n' << "d: " << setw(12) << d << "next col" << '\n'; // pad first column, using # as the pad character cout << setfill('#') << "i: " << setw(12) << i << "next col" << '\n' << "d: " << setw(12) << d << "next col" << '\n' << setfill(' '); // restore normal pad character When executed, this program generates 执行时,该程序段产生如下输出: i: -16next col d: 3.14159next col i: -16 next col d: 3.14159 next col i: -16next col d: 3.14159next col i: - 16next col d: 3.14159next col i: -#########16next col d: #####3.14159next col A.3.4. Controlling Input FormattingA.3.4. 控制输入格式化By default, the input operators ignore whitespace (blank, tab, newline, formfeed, and carriage return). The following loop 默认情况下,输入操作符忽略空白(空格、制表符、换行符、进纸和回车)。对下面的循环: while (cin >> ch) cout << ch; given the input sequence 给定输入序列 a b c d executes four times to read the characters a through d, skipping the intervening blanks, possible tabs, and newline characters. The output from this program is 循环执行四次从字符 a 读到 d,跳过介于其间的空格、可能的制表符和换行符。该程序段的输出是:
abcd
The noskipws manipulator causes the input operator to read, rather than skip, whitespace. To return to the default behavior, we apply skipws manipulator: noskipws 操纵符导致输入操作符读(而不是跳过)空白。要返回默认行为,应用 skipws 操纵符: cin >> noskipws; // set cin so that it reads whitespace while (cin >> ch) cout << ch; cin >> skipws; // reset cin to default state so that it discards whitespace Given the same input as before, this loop makes seven iterations, reading white-space as well as the characters in the input. This loop generates 给定与前面相同的输入,该循环进行 7 次迭代,读输入中的空白以及字符。该循环产生如下输出: a b c d A.3.5. Unformatted Input/Output OperationsA.3.5. 未格式化的输入/输出操作So far, our programs have used only formatted IO operations. The input and output operators (<< and >>) format the data they read or write according to the data type being handled. The input operators ignore whitespace; the output operators apply padding, precision, and so on. 迄今为止,示例程序中只使用过格式化的 IO 操作。输入和输出操作符(<< 和 >>)根据被处理数据的类型格式化所读写的数据。输入操作符忽略空白,输出操作符应用填充、精度等。 The library also provides a rich set of low-level operations that support unformatted IO. These operations let us deal with a stream as a sequence of uninterpreted bytes rather than as a sequence of data types, such as char, int, string, and so on. 标准库还提供了丰富的支持未格式化 IO 的低级操作,这些操作使我们能够将流作为未解释的字节序列处理,而不是作为数据类型(如 char、int、string 等)的序列处理。 A.3.6. Single-Byte OperationsA.3.6. 单字节操作Several of the unformatted operations deal with a stream one byte at a time. They read rather than ignore whitespace. For example, we could use the unformatted IO operations get and put to read the characters one at a time: 几个未格式化的操作一次一个字节地处理流,它们不忽略空白地读。例如,可以使用未格式化 IO 操作 get 和 put 一次读一个字符: char ch; while (cin.get(ch)) cout.put(ch); This program preserves the whitespace in the input. Its output is identical to the input. Given the same input as read by the previous program that used noskipws, this program generates the same output: 该程序段保持输入中的空白。它的输出与输入相同。给定与前面使用 noskipws 的程序段相同的输入,该程序段产生相同的输出: a b c d
Putting Back onto an Input Stream在输入流上倒退Sometimes we need to read a character in order to know that we aren't ready for it yet. In such cases, we'd like to put the character back onto the stream. The library gives us three ways to do so, each of which has subtle differences from the others: 有时我们需要读一个字符才知道还没有为它作好准备,在这种情况下,希望将字符放回流上。标准库给出三种方法做到这一点,它们之间有下列微妙的区别:
In general, we are guaranteed to be able to put back at most one value before the next read. That is, we are not guaranteed to be able to call putback or unget successively without an intervening read operation. 一般而言,保证能够在下一次读之前放回最多一个值,也就是说,不保证能够连续调用 putback 或 unget 而没有介于其间的读操作。 int Return Values from Input Operations输入操作的 int 返回值The version of get that takes no argument and the peek function return a character from the input stream as an int. This fact can be surprising; it might seem more natural to have these functions return a char. 不接受实参的 get 版本和 peek 函数从输入流返回一个字符作为 int 值。这个事实可能令人惊讶,因为这些函数返回 char 值似乎更自然。 The reason that these functions return an int is to allow them to return an end-of-file marker. A given character set is allowed to use every value in the char range to represent an actual character. Thus, there is no extra value in that range to use to represent end-of-file. 这些函数返回 int 值的原因是为了允许它们返回一个文件结束标记。允许给定字符集使用 char 范围的每一个值来表示实际字符,因此,该范围中没有额外值用来表示文件结束符。 Instead, these functions convert the character to unsigned char and then promote that value to int. As a result, even if the character set has characters that map to negative values, the int returned from these operations will be a positive value (Section 2.1.1, p. 36). By returning end-of-file as a negative value, the library guarantees that end-of-file will be distinct from any legitimate character value. Rather than requiring us to know the actual value returned, the iostream header defines a const named EOF that we can use to test if the value returned from get is end-of-file. It is essential that we use an int to hold the return from these functions: 相反,这些函数将字符转换为 unsigned char,然后将那个值提升为 int,因此,即使字符集有映射到负值的字符,从这些操作返回的值也将是一个正值(第 2.1.1 节)。通过将文件结束符作为负值返回,标准库保证文件结束符区别于任意合法字符值。为了不要求我们知道返回的实际值,头文件 iostream 定义了名为 EOF 的 const,可以使用它来测试 get 的返回值是否为文件结束符。实质上我们使用 int 对象来保存这些函数的返回值: int ch; // NOTE: int, not char!!!! // loop to read and write all the data in the input while ((ch = cin.get()) != EOF) cout.put(ch); This program operates identically to one on page 834, the only difference being the version of get that is used to read the input. 这个程序段与 A.3.6 节中的那个程序段同样操作,唯一的区别是用来读输入的 get 版本不同。 A.3.7. Multi-Byte OperationsA.3.7. 多字节操作Other unformatted IO operations deal with chunks of data at a time. These operations can be important if speed is an issue, but like other low-level operations they are error-prone. In particular, these operations require us to allocate and manage the character arrays (Section 4.3.1, p. 134) used to store and retrieve data. 其他未格式化 IO 操作一次处理数据块。如果速度是一个问题,这些操作可能就很重要。但像其他低级操作一样,它们是容易出错的。尤其是,这些操作要求我们分配和管理用于存储和检索数据的字符数组(第 4.3.1 节)。 The multi-byte operations are listed in Table A.5 (p. 837). It is worth noting that the get member is overloaded; there is a third version that reads a sequence of characters. 多字节操作在表 A.5 列出。值得注意的是,get 成员是重载的,存在读字符序列的第三个版本。
The get and getline functions take the same parameters, and their actions are similar but not identical. In each case, sink is a char array into which the data are placed. The functions read until one of the following conditions occurs: get 函数和 getline 函数接受相同形参,它们的行为类似但不相同。在每个函数中,sink 是一个存放数据的 char 数组。函数进行读,直到下面条件中的一个发生:
Following any of these conditions, a null character is put in the next open position in the array. The difference between these functions is the treatment of the delimiter. get leaves the delimiter as the next character of the istream. getline reads and discards the delimiter. In either case, the delimiter is not stored in sink. 遵循这些条件中的任意一个,将一个空字符放到数组中下一个开放位置。两个函数之间的区别是对待分隔符的方法。get 将分隔符留作 istream 的下一个字符,getline 读入并丢弃分隔符,两种情况下,都不在 sink 中存储分隔符。
Determining How Many Characters Were Read确定读了多少字符Several of the read operations read an unknown number of bytes from the input. We can call gcount to determine how many characters the last unformatted input operation read. It is esssential to call gcount before any intervening unformatted input operation. In particular, the single-character operations that put characters back on the stream are also unformatted input operations. If peek, unget, or putback are called before calling gcount, then the return value will be 0! 有几个读操作中从输入中读未知数目的字节。可以调用 gcount 操作来确定最后一个未格式化输入操作读了多少字符。有必要在任意插入的未格式化输入操作之前调用 gcount。尤其是,将字符放回流上的单字符操作也是未格式化输入操作,如果在调用 gcount 之前调用 peek、unget 或 putback,则返回值将是 0! A.3.8. Random Access to a StreamA.3.8. 流的随机访问The various stream types generally support random access to the data in their associated stream. We can reposition the stream so that it skips around, reading first the last line, then the first, and so on. The library provides a pair of functions to seek to a given location and to tell the current location in the associated stream. 不同的流类型一般支持对相关流中数据的随机访问。可以重新定位流,以便环绕跳过,首先读最后一行,再读第一行,以此类推。标准库提供一对函数定位给定位置并告诉(tell)相关流中的当前位置。
Seek and Tell Functionsseek 和 tell 函数To support random access, the IO types maintain a marker that determines where the next read or write will happen. They also provide two functions: One repositions the marker by seeking to a given position; the second tells us the current position of the marker. The library actually defines two pairs of seek and tell functions, which are described in Table A.6. One pair is used by input streams, the other by output streams. The input and output versions are distinguished by a suffix that is either a g or a p. The g versions indicate that we are "getting" (reading) data, and the p functions indicate that we are "putting" (writing) data. 为了支持随机访问,IO类型维持一个标记,该标记决定下一个读或写发生在哪里。IO 类型还提供两个函数:一个通过 seek 指定位置重新安置该标记,另一个 tell 我们标记的当前位置。标准库实际上定义了两对 seek 和 tell 函数(表 A.6 给出对它们的描述),一对由输入流使用,另一对由输出流使用。输入和输出版本由后缀 g 和 p 区分,g 版本指出正在“获取”(读)数据,p 函数指出正在“放置”(写)数据。
Logically enough, we can use only the g versions on an istream or its derived types ifstream, or istringstream, and we can use only the p versions on an ostream or its derived types ofstream, and ostringstream. An iostream, fstream, or stringstream can both read and write the associated stream; we can use either the g or p versions on objects of these types. 逻辑上,只能在 istream 类型或其派生类型 ifstream 或 istringstream 之上使用 g 版本,并且只能在 ostream 类型或其派生类型 ofstream 或 ostringstream 之上使用 p 版本。iostream 对象、fstream 对象或 stringstream 对象对相关流既能读又能写,可以在这些类型的对象上使用 g 或 p 版本的任何一个。 There Is Only One Marker只有一个标记The fact that the library distinguishes between the "putting" and "getting" versions of the seek and tell functions can be misleading. Even though the library makes this distinction, it maintains only a single marker in the filethere is not a distinct read marker and write marker. 标准库区分 seek 函数和 tell 函数的“放置”和“获取”版本,这一事实可能会令人误解。虽然标准库进行这种区分,但它只在文件中维持一个标记——没有可区分的读标记和写标记。 When we're dealing with an input-only or output-only stream, the distinction isn't even apparent. We can use only the g or only the p versions on such streams. If we attempt to call tellp on an ifstream, the compiler will complain. Similarly, it will not let us call seekg on an ostringstream. 处理只输入或只输出的流的时候,区别并不明显。在这样的流上,只能使用 g 版本或只能使用 p 版本。如果 ifstream 对象上调用 tellp,编译器将会给出错误提示。类似地,编译器不允许在 ostringstream 对象上调用 seekg。 When using the fstream and stringstream types that can both read and write, there is a single buffer that holds data to be read and written and a single marker denoting the current position in the buffer. The library maps both the g and p positions to this single marker. 使用既能读又能写的 fstream 类型和 stringstream 类型的时候,只有一个保存数据的缓冲区和一个表示缓冲区中当前位置的标记,标准库将 g 位置和 p 位置都映射到这个标记。
Plain iostreams Usually Do Not Allow Random Access普通 iostream 对象一般不允许随机访问The seek and tell functions are defined for all the stream types. Whether they do anything useful depends on the kind of object to which the stream is bound. On most systems, the streams bound to cin, cout, cerr and clog do not support random accessafter all, what would it mean to jump ten places back when writing directly to cout? We can call the seek and tell functions, but these functions will fail at run time, leaving the stream in an invalid state. seek 函数和 tell 函数是为所有流类型定义的,它们是否完成有用的工作取决于流所绑定的对象的类别。在大多数系统上,绑定到 cin、cout、cerr 和 clog 的流不支持随机访问——直接写 cout 的时候,回跳 10 个位置会意味着什么?可以调用 seek 函数和 tell 函数,但这些函数将在运行时失败,使得流处于无效状态。
Repositioning the Marker重新定位标记The seekg and seekp functions are used to change the read and write positions in a file or a string. After a call to seekg, the read position in the stream is changed; a call to seekp sets the position at which the next write will take place. seekg 函数和 seekp 函数用于改变文件或 string 对象中的读写位置。调用 seekg 函数之后,改变流中的读位置,seekp 函数调用将位置置于下一个写将发生的地方。 There are two versions of the seek functions: One moves to an "absolute" address within the file; the other moves to a byte offset from a given position: seek 函数有两个版本:一个移动到文件中的一个“绝对”地址,另一个移动到给定位置的字节偏移量处: // set the indicated marker a fixed position within a file or string seekg(new_position); // set read marker seekp(new_position); // set write marker // offset some distance from the indicated position seekg(offset, dir); // set read marker seekp(offset, dir); // set write marker The first version sets the current position to a given location. The second takes an offset and an indicator of where to offset from. The possible values for the offset are listed in Table A.7. 第一个版本将当前位置置于给定地点,第二个版本接受一个偏移量以及从何处偏移的指示器。偏移量的可能值在表 A.7 中列出。
The argument and return types for these functions are machine-dependent types defined in both istream or ostream. The types, named pos_type and off_type, represent a file position and an offset from that position, respectively. A value of type off_type can be positive or negative; we can seek forward or backward in the file. 这些函数的实参类型和返回类型是与机器相关的类型,在 istream 和 ostream 中定义。名为 pos_type 和 off_type 的类型分别表示文件位置和从该位置的偏移量。off_type 类型的值可以为正也可以为负,在文件中可以进行前向或后向 seek。 Accessing the Marker访问标记The current position is returned by either tellg or tellp, depending on whether we're looking for the read or write position. As before, the p indicates putting (writing) and the g indicates getting (reading). The tell functions are usually used to remember a location so that we can subsequently seek back to it: 当前位置由 tellg 或 tellp 返回,取决于正在查找读位置还是写位置。像前面一样,p 指出放置(写)而 g 指出获取(读)。tell 函数一般用于记住一个位置,以便随后可以通过 seek 回到那里: // remember current write position in mark ostringstream writeStr; // output stringstream ostringstream::pos_type mark = writeStr.tellp(); // ... if (cancelEntry) // return to marked position writeStr.seekp(mark); The tell functions return a value that indicates the position in the associated stream. As with the size_type of a string or vector, we do not know the actual type of the object returned from tellg or tellp. Instead, we use the pos_type member of the appropriate stream class. tell 函数返回一个值,指出相关流中的位置。像 string 对象或 vector 对象的 size_type 一样,我们不知道从 tellg 或 tellp 返回的对象实际类型,而是使用适当流类的 pos_type 成员来代替。 A.3.9. Reading and Writing to the Same FileA.3.9. 读写同一文件Let's look at a programming example. Assume we are given a file to read. We are to write a new line at the end of the file that contains the relative position at which each line begins. For example, given the following file, 看一个程序示例,假定给定一个文件来读,我们将在文件的末尾写一个新行,该行包含每一行开头的相对位置。例如,给定下面的文件, abcd efg hi j the program should produce the following modified file: 这段程序应产生修改过后文件如下: abcd efg hi j 5 9 12 14 Note that our program need not write the offset for the first lineit always occurs at position 0. It should print the offset that corresponds to the end of the data portion of the file. That is, it should record the position after the end of the input so that we'll know where the original data ends and where our output begins. 注意,程序不必写第一行的偏移量——它总是出现在位置 0,程序应该显示对应于文件数据部分末尾的偏移量,也就是说,它应该记录输入末尾之后的位置,以便我们知道原始数据在何处结束以及我们的输出从何处开始。 We can write this program by writing a loop that reads a line at a time: 通过编写一次读一行的循环可以编写这个程序: int main() { // open for input and output and pre-position file pointers to end of file fstream inOut("copyOut", fstream::ate | fstream::in | fstream::out); if (!inOut) { cerr << "Unable to open file!" << endl; return EXIT_FAILURE; } // inOut is opened in ate mode, so it starts out positioned at the end, // which we must remember as it is the original end-of-file position ifstream::pos_type end_mark = inOut.tellg(); inOut.seekg(0, fstream::beg); // reposition to start of the file int cnt = 0; // accumulator for byte count string line; // hold each line of input // while we haven't hit an error and are still reading the original data // and successfully read another line from the file while (inOut && inOut.tellg() != end_mark && getline(inOut, line)) { cnt += line.size() + 1; // add 1 to account for the newline // remember current read marker ifstream::pos_type mark = inOut.tellg(); inOut.seekp(0, fstream::end);// set write marker to end inOut << cnt; // write the accumulated length // print separator if this is not the last line if (mark != end_mark) inOut << " "; inOut.seekg(mark); // restore read position } inOut.clear(); // clear flags in case we hit an error inOut.seekp(0, fstream::end); // seek to end inOut << "\n"; // write a newline at end of file return 0; } This program opens the fstream using the in, out, and ate modes. The first two modes indicate that we intend to both read and write to the same file. By also opening it in ate mode, the file starts out positioned at the end. As usual, we check that the open succeeded, and exit if it did not. 这个程序使用 in、out 和 ate 模式打开 fstream。前两种模式指出,我们打算对同一文件进行读写;通过用 ate 模式打开,文件首先定位于末端。照常,要检查打开是否成功,如果不成功就退出。 Initial Setup初始设置The core of our program will loop through the file a line at a time, recording the relative position of each line as it does so. Our loop should read the contents of the file up to but not including the line that we are adding to hold the line offsets. Because we will be writing to the file, we can't just stop the loop when it encounters end-of-file. Instead, the loop should end when it reaches the point at which the original input ended. To do so, we must first remember the original end-of-file position. 这段程序的核心部分将循环通过文件,一次一行,循环过程中记录每一行的相对位置。循环应该读文件的内容,直到但不包含正在增加的保存行偏移量的那一行。因为将对文件进行写,所以不是在遇到文件结束符时停止循环,相反,应在到达原始输入结束处结束循环。要做到这一点,必须首先记住原来文件结束符的位置。 We opened the file in ate mode, so it is already positioned at the end. We store the initial end position in end_mark. Of course, having remembered the end position, we must reposition the read marker at the beginning of the file before we attempt to read any data. 因为是以 ate 模式打开文件,所以文件已经定位在末端。将原来的末端位置存储在 end_mark 中。当然,记住了结束位置之后,在试图读任意数据之前,必须将读标记重新定位于文件开头。 Main Processing Loop主处理循环Our while loop has a three-part condition. while 循环有三部分条件。 We first check that the stream is valid. Assuming the first test on inOut succeeds, we then check whether we've exhausted our original input. We do this check by comparing the current read position returned from tellg with the position we remembered in end_mark. Finally, assuming that both tests succeeded, we call getline to read the next line of input. If getline succeeds, we perform the body of the loop. 首先检察流是否有效。假定第一个测试 inOut 成功,接着检查是否已经耗尽了原始输入,通过将从 tellg 返回的当前读位置与 end_mark 中记录的位置相比较,进行这个检查。最后,假定两个测试都成功,就调用 getline 来调用 getline 来读下一行输入,如果 getline 成功,就执行循环体。 The job that the while does is to increment the counter to determine the offset at which the next line starts and write that marker at the end of the file. Notice that the end of the file advances on each trip through the loop. while 所做的工作是将计数器增量,以确定下一行开始处的偏移量,并将那个标记写至文件末尾。注意,每通过一次循环都向前推进文件的末尾。 We start by remembering the current position in mark. We need to keep that value because we have to reposition the file in order to write the next relative offset. The seekp call does this repositioning, resetting the file pointer to the end of the file. We write the counter value and then restore the file position to the value we remembered in mark. The effect is that we return the marker to the same place it was after the last read. Having restored the marker, we're ready to repeat the condition in the while. 首先将当前位置记在 mark 中,需要保存那个值,是因为要写下一个相对偏移量必须重新定位文件。seekp 的调用进行这个重新定位,将文件指针重置到文件末尾。写计数器值然后将文件位置恢复为 mark 中所记录的值,效果是将标记返回到最后一次读之后的同一地方。恢复了标记之后,就准备重复检测 while 中的条件。 Completing the File完成文件Once we exit the loop, we have read each line and calculated all the starting offsets. All that remains is to print the offset of the last line. As with the other writes, we call seekp to position the file at the end and write the value of cnt. The only tricky part is remembering to clear the stream. We might exit the loop due to an end-of-file or other input error. If so, inOut would be in an error state, and both the seekp and the output expression would fail. 一旦退出了循环,就已经读过了每一行并计算了所有行开头的偏移量。剩下的是显示最后一行的偏移量。像对其他写操作一样,调用 seekp 将文件定位在末尾,并写 cnt 的值。唯一复杂的部分是记得对流进行 clear。可能因为文件结束符或其他输入错误而退出循环,如果是这样,inOut 将处于错误状态,而 seekp 和输出表达式都将失败。 |