Team LiB
Previous Section Next Section

A.3. The IO Library Revisited

A.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 State

A.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 列出)来修改对象的格式状态。简单说来,操纵符是可用作输入或输出操作符操作数的函数或对象。操纵符返回其应用于的流对象,所以可以在一个语句中输出多个操纵符和数据。

将真和假显示为 1, 0

Table A.2. Manipulators Defined in iostream
表 A.2. iostream 中定义的操纵符
 

boolalpha

Display true and false as strings

将真和假显示为字符串

x

noboolalpha

Display true and false as 0, 1

 

showbase

Generate prefix indicating numeric base

产生指出数的基数的前缀

x

noshowbase

Do not generate notational base prefix

不产生记数基数前缀

 

showpoint

Always display decimal point

总是显示小数点

x

noshowpoint

Only display decimal point if fraction

有小数部分才显示小数点

 

showpos

Display + in nonnegative numbers

显示非负数中的 +

x

noshowpos

Do not display + in nonnegative numbers

不显示非负数中的 +

 

uppercase

Print 0X in hexadecimal, E in scientific

在十六进制中打印 0X,科学记数法中打印 E

x

nouppercase

Print 0x in hexadecimal, e in scientific

在十六进制中打印 0x,科学记数法中打印 e

x

dec

Display in decimal numeric base

用十进制显示

 

hex

Display in hexadecimal numeric base

用十六进制显示

 

oct

Display in octal numeric base

用八进制显示

 

left

Add fill characters to right of value

在值的右边增加填充字符

 

right

Add fill characters to left of value

在值的左边增加填充字符

 

internal

Add fill characters between sign and value

在符号和值之间增加填充字符

 

fixed

Display floating-point in decimal notation

用小数形式显示浮点数

 

scientific

Display floating-point in scientific notation

用科学记数法显示浮点数

 

flush

Flush ostream buffer

刷新 ostream 缓冲区

 

ends

Insert null, then flush ostream buffer

插入空字符,然后刷新 ostream 缓冲区

 

endl

Insert newline, then flush ostream buffer

插入换行符,然后刷新 ostream 缓冲区

 

unitbuf

Flush buffers after every output operation

在每个输出操作之后刷新缓冲区

x

nounitbuf

Restore normal buffer flushing

恢复常规缓冲区刷新

x

skipws

Skip whitespace with input operators

为输入操作符跳过空白

 

noskipws

Do not skip whitespace with input operators

不为输入操作符跳过空白

 

ws

"Eat" whitespace

“吃掉”空白

x indicates default stream state

注:带 x 的是默认流状态。

Table A.3. Manipulators Defined in iomanip
表 A.3. iomanip 中定义的操纵符

setfill(ch)

Fill whitespace with ch

ch 填充空白

setprecision(n)

Set floating-point precision to n

将浮点精度置为 n

setw(w)

Read or write value to w characters

读写 w 个字符的值

setbase(b)

Output integers in base b

按基数 b 输出整数

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 State

A.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 字面值 truefalse 的格式,诸如此类。

Manipulators that change the format state of the stream usually leave the format state changed for all subsequent IO.

改变流格式状态的操纵符通常为后续 IO 保留改变后的格式状态。

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.

但是,许多程序(更重要的是,许多程序员)希望流的状态与标准库默认值匹配。这些情况下,使流状态停留在非标准状态可能会导致错误。

It is usually best to undo any state change made by a manipulator. Ordinarily, a stream should be in its ordinary, default state after every IO operation.

取消操纵符的任何状态改变通常是最好的。一般而言,流应该在每个 IO 操作之后处于通常的默认状态。

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 操作类似于管理流的条件状态的 rdstatesetstate 操作。这种情况下,标准库定义了一对 flags 函数:

  • flags() with no arguments returns the stream's current format state. The value returned is a library defined type named fmtflags.

    不带实参的 flags() 返回流的当前格式状态。返回值是名为 fmtflags 的标准库定义类型。

  • flags(arg) takes a fmtflags argument and sets the stream's format as indicated by the argument.

    flags(arg) 接受一个实参并将流格式置为实参所指定的格式。

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 Formats

A.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 值的操作将用 truefalse 进行显示。

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:

默认情况下,用十进制读写整型值。通过使用操纵符 hexoctdec,程序员可以将表示进制改为八进制、十六进制或恢复十进制(浮点值的表示不受影响):

     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 操纵符导致输出流使用的约定,与指定整型常量基数所用的相同:

  • A leading 0x indicates hexadecimal

    以 0x 为前导表示十六进制。

  • A leading 0 indicates octal

    以 0 为前导表示八进制。

  • The absence of either indicates decimal

    没有任何前导表示十进制。

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:

对于浮点值的格式化,可以控制下面三个方面:

  • Precision: how many digits are printed

    精度:显示多少位数字。

  • Notation: whether to print in decimal or scientific notation

    记数法:用小数还是科学记法法显示。

  • Handling of the decimal point for floating-point values that are whole numbers

    对是整数的浮点值的小数点的处理。

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 函数量重载的,可以用 floatdoublelong double 实参调用,它返回实参的平方根。

The setprecision manipulators and other manipulators that take arguments are defined in the iomanip header.

操纵符和其他接受实参的操纵符定义在头文件 iomanip 中。

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.

默认情况下,用于显示浮点值的记数法取决于数的大小:如果数很大或很小,将按科学记数法显示,否则,使用固定位数的小数。标准库选择使得数容易阅读的记数法。

When printing a floating-point number as a plain number (as opposed to printing money, or a percentage, where we want to control the appearance of the value), it is usually best to let the library choose the notation to use. The one time to force either scientific or fixed decimal is when printing a table in which the decimal points should line up.

将浮点数显示为普通数(相对于显示货币、百分比,那时我们希望控制值的外观)的时候,通常最好让标准库来选择使用的记数法。要强制科学记数法或固定位数小数的一种情况是在显示表的时候,表中的小数点应该对齐。

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 操纵符控制科学记数法中的 efixed 操纵符将流为使用固定位数小数表示。

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.

这些操纵符改变流精度的默认含义。执行 scientificfixed 之后,精度值控制小数点之后的数位。默认情况下,精度指定数字的总位数——小数点之前和之后。使用 fixedscientific 命名我们能够按列对齐来显示数,这一策略保证小数点总是在相对于被显示的小数部分固定的位置。

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 成员来取消 scientificfixed 所做的改变。要将流恢复为浮点值的默认处理,将名为 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:

按栏显示数据的时候,经常很希望很好地控制数据的格式化。标准库提供下面几个操纵帮助我们实现需要的控制:

  • setw to specify the minimum space for the next numeric or string value.

    setw,指定下一个数值或字符串的最小间隔。

  • left to left-justify the output.

    left,左对齐输出。

    right to right-justfiy the output. Output is right-justified by default.

    right,右对齐输出。输出默认为右对齐。

  • internal controls placement of the sign on negative values. internal left-justifies the sign and right-justifies the value, padding any intervening space with blanks.

    internal,控制负值的符号位置。internal 左对齐符号且右对齐值,用空格填充介于其间的空间。

  • setfill lets us specify an alternative character to use when padding the output. By default, the value is a space.

    setfill,使我们能够指定填充输出时使用的另一个字符。默认情况下,值是空格。

setw, like endl, does not change the internal state of the output stream. It determines the size of only the next output.

endl 一样,setw 不改变输出流的内部状态,它只决定下一个输出的长度。

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 Formatting

A.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 Operations

A.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 的低级操作,这些操作使我们能够将流作为未解释的字节序列处理,而不是作为数据类型(如 charintstring 等)的序列处理。

A.3.6. Single-Byte Operations

A.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 操作 getput 一次读一个字符:

     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

Table A.4. Single-Byte Low-Level IO Operations
表 A.4. 单字节低级 IO 操作

is.get(ch)

Puts next byte from the istream is in character ch. Returns is.

istream is 的下一个字节放入 ch,返回 is

os.put(ch)

Puts character ch onto the ostream os. Returns os.

将字符 ch 放入 ostream,返回 os

is.get()

Returns next byte from is as an int.

返回 is 的下一字节作为一个 int

is.putback(ch)

Puts character ch back on is; returns is.

将字符 ch 放回 is,返回 is

is.unget()

Moves is back one byte; returns is.

is 退回一个字节,返回 is

is.peek()

Returns the next byte as an int but doesn't remove it.

将下一字节作为 int 值返回但不移出它

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:

有时我们需要读一个字符才知道还没有为它作好准备,在这种情况下,希望将字符放回流上。标准库给出三种方法做到这一点,它们之间有下列微妙的区别:

  • peek returns a copy of the next character on the input stream but does not change the stream. The value returned by peek stays on the stream and will be the next one retrieved.

    peek,返回输入流上下一字符的副本但不改变流。peek 返回的值留在流上,且将是下一个被检索的。

  • unget backs up the input stream so that whatever value was last returned is still on the stream. We can call unget even if we do not know what value was last taken from the stream.

    unget,使输入流倒退,以便最后返回的值仍在流上。即使不知道最近从流获得的是什么值,也可以调用 unget

  • putback is a more specialized version of unget: It returns the last value read from the stream but takes an argument that must be the same as the one that was last read. Few programs use putback because the simpler unget does the same job with fewer constraints.

    putbackunget 的更复杂的版本。它返回从流中读到的最后一个值,但接受最后一次读的同一实参。很少有程序使用 putback,因为更简单的 unget 也可以完成相同工作而约束更少。

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.

一般而言,保证能够在下一次读之前放回最多一个值,也就是说,不保证能够连续调用 putbackunget 而没有介于其间的读操作。

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 定义了名为 EOFconst,可以使用它来测试 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 Operations

A.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 成员是重载的,存在读字符序列的第三个版本。

Table A.5. Multi-Byte Low-Level IO Operations
表 A.5. 多字节低级 IO 操作

is.get(sink, size, delim)

 

Reads up to size bytes from is and stores them in the character array pointed to by sink. Reads until encountering the delim character or until it has read size bytes or encounters end-of-file. If the delim is present, it is left on the input stream and not read into sink.

is 中读 size 个字节并将它们存储到 sink 所指向的字符数组中。读操作直到遇到 delim 字符,或已经读入了 size 个字节,或遇到文件结束符才结束。如果出现了 delim,就将它留在输入流上,不读入到 sink 中。

is.getline(sink, size, delim)

 

Same behavior as three-argument version of get but reads and discards delim.

与三个实参的 get 行为类似,但读并丢弃 delim

is.read(sink, size)

 

Reads up to size bytes into the character array sink. Returns is.

size 个字节到数组 sink。返回 is

is.gcount()

Returns number of bytes read from the stream is by last call to an unformatted read operation.

返回最后一个未格式化读操作从流 is 中读到的字节数

os.write(source, size)

 

Writes size bytes from the character array source to os. Returns os.

size 个字从数组 source 写至 os。返回 os

is.ignore(size, delim)

 

Reads and ignores at most size characters up to but not including delim. By default, size is 1 and delim is end-of-file.

读并忽略至多 size 个字符,直到遇到 delim,但不包括 delim。默认情况下,size 是 1 而 delim 是文件结束符

Caution: Low-Level Routines Are Error-Prone

警告:低级例程容易出错

In general, we advocate using the higher-level abstractions provided by the library. The IO operations that return int are a good example of why.

一般提倡使用标准库提供的高级抽象。返回 int 值的 IO 操作是一个很好的例子。

It is a common programming error to assign the return from get or one of the other int returning functions to a char rather than an int. Doing so is an error but an error the compiler will not detect. Instead, what happens depends on the machine and on the input data. For example, on a machine in which chars are implemented as unsigned chars, this loop will run forever:

get 或其他返回 int 值的函数的返回值赋给 char 对象而不是 int 对象,是常见的错误,但编译器不检测这样的错误,相反,发生什么取决于机器和输入数据。例如,在将 char 实现为 unsigned char 的机器上,这是一个死循环:


     char ch;    // Using a char here invites disaster!
     // return from cin.get is converted from int to char and
 then compared to an int
     while ((ch = cin.get()) != EOF)
              cout.put(ch);

The problem is that when get returns EOF, that value will be converted to an unsigned char value. That converted value is no longer equal to the integral value of EOF, and the loop will continue forever.

问题在于,当 get 返回 EOF 的时候,那个值将被转换为 unsigned char 值,转换后的值不再等于 EOF 的整型值,循环将永远继续。

At least that error is likely to be caught in testing. On machines for which chars are implemented as signed chars, we can't say with confidence what the behavior of the loop might be. What happens when an out-of-bounds value is assigned to a signed value is up to the compiler. On many machines, this loop will appear to work, unless a character in the input matches the EOF value. While such characters are unlikely in ordinary data, presumably low-level IO is necessary only when reading binary values that do not map directly to ordinary characters and numeric values. For example, on our machine, if the input contains a character whose value is '\377' then the loop terminates prematurely. '\377' is the value on our machine to which -1 converts when used as a signed char. If the input has this value, then it will be treated as the (premature) end-of-file indicator.

至少还可能在测试中捕获这个错误。在将 char 实现为 signed char 的机器上,不能确切地说出该循环的行为。当将超出边界的值赋给 signed 值时发生什么由编译器负责。在许多机器上,这个循环看起来能工作,除非输入中的字符与 EOF 值匹配。虽然这样的字符不可能在普通数据中,但是,大概只有在读不直接映射到普通字符和数值的二进制值的时候,低级 IO 才是必要的。例如,在我们的机器上,如果输入包含值为 '\377' 的字符,循环就提前终止。'\377' 是我们的机器上 -1 作为 signed char 使用的时候所转换的值,如果输入中有这个值,就将它当作(提早的)文件结束指示符对待。

Such bugs do not happen when reading and writing typed values. If you can use the more type-safe, higher-level operations supported by the library, do so.

在读写类型化值的时候不会发生这样的错误。如果可以使用标准库支持的更为类型安全的、更高级的操作,就使用。

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 数组。函数进行读,直到下面条件中的一个发生:

  • size - 1 characters are read

    读到了 size - 1 个字符。

  • End-of-file is encountered

    遇到文件结束符。

  • The delimiter character is encountered

    遇到分隔符。

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 中存储分隔符。

It is a common error to intend to remove the delimiter from the stream but to forget to do so.

想要从流中移去分隔符但忘了这样做,是一个常见的错误。

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 之前调用 peekungetputback,则返回值将是 0!

A.3.8. Random Access to a Stream

A.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)相关流中的当前位置。

Random IO is an inherently system-dependent. To understand how to use these features, you must consult your system's documentation.

随机 IO 是一个固有的随系统而定的特征。要理解怎样使用这些特征,必须查阅系统的文档。

Seek and Tell Functions
seek 和 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 我们标记的当前位置。标准库实际上定义了两对 seektell 函数(表 A.6 给出对它们的描述),一对由输入流使用,另一对由输出流使用。输入和输出版本由后缀 gp 区分,g 版本指出正在“获取”(读)数据,p 函数指出正在“放置”(写)数据。

Table A.6. Seek and Tell Functions
表 A.6. seek 和 tell 函数

seekg

Reposition the marker in an input stream

重新定位输入流中的标记

tellg

Return the current position of the marker in an input stream

返回输入流中标记的当前位置

seekp

Reposition the marker for an output stream

重新定位输出流中的标记

tellp

Return the current position of the marker in an output stream

返回输出流中标记的当前位置

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 类型或其派生类型 ifstreamistringstream 之上使用 g 版本,并且只能在 ostream 类型或其派生类型 ofstreamostringstream 之上使用 p 版本。iostream 对象、fstream 对象或 stringstream 对象对相关流既能读又能写,可以在这些类型的对象上使用 gp 版本的任何一个。

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 位置都映射到这个标记。

Because there is only a single marker, we must do a seek to reposition the marker whenever we switch between reading and writing.

因为只有一个标记,所以,在读和写之间切换时必须进行 seek 来重新定位标记。

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 函数是为所有流类型定义的,它们是否完成有用的工作取决于流所绑定的对象的类别。在大多数系统上,绑定到 cincoutcerrclog 的流支持随机访问——直接写 cout 的时候,回跳 10 个位置会意味着什么?可以调用 seek 函数和 tell 函数,但这些函数将在运行时失败,使得流处于无效状态。

Because the istream and ostream types usually do not support random access, the remainder of this section should be considered as applicable to only the fstream and sstream types.

因为 istreamostream 类型一般不支持随机访问,所以,应该认为本节其余部分只能应用于 fstreamsstream 类型。

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 中列出。

Table A.7. Offset From Argument to seek
表 A.7. seek 实参的偏移量

beg

The beginning of the stream

流的开头

cur

The current position of the stream

流的当前位置

end

The end of the stream

流的末尾

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.

这些函数的实参类型和返回类型是与机器相关的类型,在 istreamostream 中定义。名为 pos_typeoff_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:

当前位置由 tellgtellp 返回,取决于正在查找读位置还是写位置。像前面一样,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 一样,我们不知道从 tellgtellp 返回的对象实际类型,而是使用适当流类的 pos_type 成员来代替。

A.3.9. Reading and Writing to the Same File

A.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.

这个程序使用 inoutate 模式打开 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 和输出表达式都将失败。

Team LiB
Previous Section Next Section