2.3 Writing an unknown number of rows
We can think of our output as a rectangular array of characters, which we must write one row at a time. Although we don't know how many rows it has, we do know how to compute the number of rows.
The greeting takes up one row, as do the top and bottom rows of the frame. We've accounted for three rows so far. If we know how many blank rows we intend to leave between the greeting and the frame, we can double that number and add three to obtain the total number of rows in the output:
// the number of blanks surrounding the greetingconst int pad = 1;// total number of rows to writeconst int rows = pad * 2 + 3;
We want to make it easy to find the part of our program that defines the number of blanks, so we give that number a name. The variable called pad
represents the amount of padding around the frame. Having defined pad
, we use it in computing rows
, which will control how many rows we write.
The built-in type int
is the most natural type to use for integers, so we've chosen that type for pad
and rows
. We also said that both variables are const
, which we know from 1.2/13 is a promise that we will not change the value of either pad
or rows
.
Looking ahead, we intend to use the same number of blanks on the left and right sides as on the top and bottom, so one variable will serve for all four sides. If we are careful to use this variable every time we want to refer to the number of blanks, changing the size of the frame will require only changing the program to give the variable a different value.
We have computed how many rows we need to write; our next problem is to do so:
// separate the output from the inputstd::cout << std::endl;// write rows rows of outputint r = 0;// invariant: we have written r rows so farwhile (r != rows) { // write a row of output (as we will describe in 2.4/22) std::cout << std::endl; ++r;}
We start, as we did in 1.2/12, by writing a blank line, so that there will be some space between the input and the output. The rest of this fragment contains so many new ideas that we need to look at it closely. Once we've understood how it works, we'll think about how to write each individual row.
2.3.1 The while statement
Our program controls how many rows of output it writes by using a whilestatement, which repeatedly executes a given statement as long as a given condition is true. A while
statement has the form
while (condition) statement
The statement is often called the whilebody.
The while
statement begins by testing the value of the condition. If the condition is false, it does not execute the body at all. Otherwise, it executes the body once, after which it tests the condition again, and so on. The while
alternates between testing the condition and executing the body until the condition is false, at which point execution continues after the end of the entire while
statement.
Loosely speaking, we can think of the while
statement in our example as saying, "As long as the value of r
is not equal to rows
, do whatever is within the { }
."
It is conventional to put the while
body on a separate line and indent it, to make programs easier to read. The implementation doesn't stop us from writing
while (condition) statement
but if we do so, we should think about whether we might be making life harder for other people who might read our program.
Note that there is no semicolon after statement in this description. Either the statement is indeed just a statement, or it is a block, which is a sequence of zero or more statements enclosed in { }
. If the statement is just an ordinary statement, it will end with a semicolon of its own, so there's no need for another one. If it is a block, the block's }
marks the end of the statement, so again there's no need for a semicolon. Because a block is a sequence of statements enclosed by braces, we know from 0.7/5 that a block is a scope.
The while begins by testing its condition, which is an expression that appears in a context where a truth value is required. The expression r != rows
is an example of a condition. This example uses the inequality operator, !=
, to compare r
and rows
. Such an expression has type bool, which is a built-in type that represents truth values. The two possible values of type bool
are true and false, with the obvious meanings.
The other new facility in this program is the last statement in the while
body, which is
++r;
The ++
is the increment operator, which has the effect of incrementing-adding 1
to-the variable r
. We could have written
r = r + 1;
instead, but incrementing an object is so common that a special notation for doing so is useful. Moreover, as we shall see in 5.1.2/79, the idea of transforming a value into its immediate successor, in contrast with computing an arbitrary value, is so fundamental to abstract data structures that it deserves a special notation for that reason alone.
2.3.2 Designing a while statement
Determining exactly what condition to write in a while
statement is sometimes difficult. Similarly, it can be hard to understand precisely what a particular while
statement does. It is not too hard to see that the while
statement in 2.3/19 will write a number of output rows that depends on the value of rows
, but how can we be confident that we know exactly how many rows the program will write? For example, how do we know whether the number will be rows, rows - 1, rows + 1
, or something else entirely? We could trace through the while
by hand, noting the effect of each statement's execution on the state of the program, but how do we know that we haven't made a mistake along the way?
There is a useful technique for writing and understanding while
statements that relies on two key ideas-one about the definition of a while
statement, and the other about the behavior of programs in general.
The first idea is that when a while
finishes, its condition must be false
-otherwise the while
wouldn't have finished. So, for example, when the while
in 2.3/19 finishes, we know that r != rows
is false and, therefore, that r
is equal to rows
.
The second idea is that of a loop invariant, which is a property that we assert will be true about a while
each time it is about to test its condition. We choose an invariant that we can use to convince ourselves that the program behaves as we intend, and we write the program so as to make the invariant true at the proper times. Although the invariant is not part of the program text, it is a valuable intellectual tool for designing programs. Every useful while
statement that we can imagine has an invariant associated with it. Stating the invariant in a comment can make a while
much easier to understand.
To make this discussion concrete, we will look again at the while
statement in 2.3/19. The comment immediately before the while
says what the invariant is: We have written r rows of output so far.
To determine that this invariant is correct for this program fragment, we must verify that the invariant is true each time the