7

Additional Control Structures

images To know how to choose the most appropriate looping statement for a given problem.

images To understand the purpose of the Break and Continue statements.

images To be familiar with specialized C++ operators and expressions.
 
 

To be able to:

images Write a Switch statement for a multiway branching problem.

images Write a Do-While statement and contrast it with a While statement.

images Write a For statement to implement a count-controlled loop.

images Cast a value from one type to another.

In the preceding chapters, we introduced C++ statements for sequence, selection, and loop structures. In some cases, we introduced more than one way of implementing these structures. For example, selection may be implemented by an If-Then structure or an If-Then-Else structure. The If-Then statement is sufficient to implement any selection structure, but C++ provides the If-Then-Else statement for convenience because the two-way branch is frequently used in programming.

This chapter introduces five new statements that are also nonessential to, but nonetheless convenient for, programming. One, the Switch statement, makes it easier to write selection structures that have many branches. Two new looping statements, For and Do-While, make it easier to program certain types of loops. The other two statements, Break and Continue, are control statements that are used as part of larger looping and selection structures.

7.1 The Switch Statement

The Switch statement is a selection control structure that allows us to list any number of branches. In other words, it is a control structure for multiway branches. A Switch is similar to nested If statements. The value of the switch expression—an expression whose value is matched with a label attached to a branch—determines which one of the branches is executed. For example, look at the following statement:

Switch expression The expression whose value determines which switch label is selected. It cannot be a floating-point or string expression.

         switch (letter)
{
  case 'X' : Statement1;
             break;
  case 'L' :
  case 'M' : Statement2;
             break;
  case 'S' : Statement3;
             break;
  default  : Statement4;
}
Statement5;

In this example, letter is the switch expression. The statement means “If letter is 'X', execute Statement1 and break out of the Switch statement, and proceed with Statement5. If letter is 'L' or 'M', execute Statement2 and break out to Statement5. If letter is 'S', execute Statement3 and go to Statement5. If letter is none of the characters mentioned, execute Statement4 and exit to Statement5.” The Break statement causes an immediate exit from the Switch statement. We'll see shortly what happens if we omit the Break statements.

The syntax template for the Switch statement is

images

IntegralOrEnumExpression is an expression of integral type—char, short, int, long, bool— or of enum type (we discuss enum in Chapter 10). The optional SwitchLabel in front of a statement is either a case label or a default label:

images

In a case label, ConstantExpression is an integral or enum expression whose operands must be literal or named constants. The following are examples of constant integral expressions (where CLASS_SIZE is a named constant of type int):

3
CLASS_SIZE
'A'
2 * CLASS_SIZE + 1

The data type of ConstantExpression is coerced, if necessary, to match the type of the switch expression.

In our opening example that tests the value of letter, the case labels have the following form:

case 'X' :
case 'L' :
case 'M' :
case 'S' :

As that example shows, a single statement may be preceded by more than one case label. Each case value may appear only once in a given Switch statement. If a value appears more than once, a syntax error results. Also, there can be only one default label in a Switch statement.

The flow of control through a Switch statement goes like this. First, the switch expression is evaluated. If this value matches one of the values in the case labels, control branches to the statement following that case label. From there, control proceeds sequentially until either a Break statement or the end of the Switch statement is encountered. If the value of the switch expression doesn't match any case value, then one of two things happens. If there is a default label, control branches to the statement following that label. If there is no default label, all statements within the Switch are skipped and control simply proceeds to the statement following the entire Switch statement.

The following Switch statement prints an appropriate comment based on a student's grade (grade is of type char):

switch (grade)
{
  case 'A' :
  case 'B' : cout << “Good Work”;
             break;
  case 'C' : cout << “Average Work”;
             break;
  case 'D' :
  case 'F' : cout << “Poor Work”;
             numberInTrouble++;
             break;                // Unnecessary, but a good habit
}

Notice that the final Break statement is unnecessary. Programmers often include it anyway, because it's easier to insert another case label at the end if a Break statement is already present.

If grade does not contain one of the specified characters, none of the statements within the Switch is executed. Unless a precondition of the Switch statement is that grade is definitely one of 'A', 'B', 'C', 'D', or 'F', it would be wise to include a default label to account for an invalid grade:

switch (grade)
{
  case 'A' :
  case 'B' : cout << “Good Work”;
             break;
  case 'C' : cout << “Average Work”;
             break;
  case 'D' :
  case 'F' : cout << “Poor Work”;
             numberInTrouble++;
             break;
  default  : cout << grade << “ is not a valid letter grade.”;
             break;
}

A Switch statement with a Break statement after each case alternative behaves exactly like an If-Then-Else-If control structure. For example, our Switch statement is equivalent to the following code:

if (grade == 'A' || grade == 'B')
  cout << “Good Work”;
else if (grade == 'C')
  cout << “Average Work”;
else if (grade == 'D' || grade == 'F')
{
  cout << “Poor Work”;
  numberInTrouble++;
}
else
  cout << grade << “ is not a valid letter grade.”;

Is either of these two versions better than the other? There is no absolute answer to this question. For this particular example, the Switch statement seems easier to understand because of its two-dimensional, table-like form—but some people may find the If-Then-Else-If version easier to read. When implementing a multiway branching structure, our advice is to write both a Switch and an If-Then-Else-If, and then compare the two for readability. Keep in mind that C++ provides the Switch statement as a matter of convenience. Don't feel obligated to use a Switch statement for every multiway branch.

Finally, we said we would look at what happens if you omit the Break statements inside a Switch statement. Let's rewrite our letter grade example without the Break statements:

switch (grade)                          // Wrong version
{
  case 'A' :
  case 'B' : cout << “Good Work”;
  case 'C' : cout << “Average Work”;
  case 'D' :
  case 'F' : cout << “Poor Work”;
             numberInTrouble++;
  default  : cout << grade << “ is not a valid letter grade.”;
}

If grade happens to be 'H', control branches to the statement at the default label and the output is

H is not a valid letter grade.

Unfortunately, this case alternative is the only one that works correctly. If grade is 'A', the resulting output is this:

Good WorkAverage WorkPoor WorkA is not a valid letter grade.

Remember—after a branch is taken to a specific case label, control proceeds sequentially until either a Break statement or the end of the Switch statement is encountered. Forgetting a Break statement in a case alternative is a very common source of errors in C++ programs.

On page 203 in Chapter 5, we showed a listing of a program that printed a message depending on the temperature and whether it was raining. Here is the same program using a Switch statement rather than Boolean assignment statements and If Statements.

images

images

When this program was run four times, it produced the following output. It should be easy to convince yourself that this program is functionally equivalent to the other one.

images

7.2 The Do-While Statement

The Do-While statement is a looping control structure in which the loop condition is tested at the end (bottom) of the loop. This format guarantees that the loop body executes at least once. Here is the syntax template for the Do-While:

images

As usual in C++, Statement is either a single statement or a block. Also, note that the Do-While ends with a semicolon.

The statement

do
{
   Statement1;
   Statement2;
   :
   StatementN;
} while (Expression);

means “Execute the statements between do and while as long as Expression still has the value true at the end of the loop.”

Let's compare a While loop and a Do-While loop that do the same task: They find the first period in a file of data. Assume that there is at least one period in the file.

While Solution

dataFile >> inputChar;
while (inputChar != '.')
  dataFile >> inputChar;

Do-While Solution

do
  dataFile >> inputChar;
while (inputChar != '.'),

The While solution requires a priming read so that inputChar has a value before the loop is entered. This isn't required for the Do-While solution because the input statement within the loop executes before the loop condition is evaluated.

Let's look at another example. Suppose a program needs to read a person's age interactively. The program requires that the age be positive. The following loops ensure that the input value is positive before the program proceeds any further.

While Solution

cout << “Enter your age: “;
cin >> age;
while (age <= 0)
{
  cout << “Your age must be positive.” << endl;
  cout << “Enter your age: “;
  cin >> age;
}

Do-While Solution

do
{
  cout << “Enter your age: “;
  cin >> age;
  if (age <= 0)
    cout << “Your age must be positive.” << endl;
} while (age <= 0);

Notice that the Do-While solution does not require the prompt and input steps to appear twice—once before the loop and once within it—but it does test the input value twice.

We can also use the Do-While to implement a count-controlled loop if we know in advance that the loop body should always execute at least once. Following are two versions of a loop to sum the integers from 1 through n.

While Solution

sum = 0;
counter = 1;
while (counter <= n)
{
  sum = sum + counter;
  counter++;
}

Do-While Solution

sum = 0;
counter = 1;
do
{
  sum = sum + counter;
  counter++;
} while (counter <= n);

If n is a positive number, both of these versions are equivalent. But if n is 0 or negative, the two loops give different results. In the While version, the final value of sum is 0 because the loop body is never entered. In the Do-While version, the final value of sum is 1 because the body executes once, after which the loop test is made.

Because the While statement tests the condition before executing the body of the loop, it is called a pretest loop. The Do-While statement does the opposite and thus is known as a posttest loop. FIGURE 7.1 compares the flow of control in the While and Do-While loops. After we look at some additional looping constructs, we offer some guidelines for determining when to use each type of loop.

images

Figure 7.1 While and Do-While Loops

Here is a program that encloses the activity message code in a Do-While statement:

images

images

Here is a sample run of this program:

You might wonder why we would want to repeat this particular process. After all, the weather doesn't change that often, in spite of how it may seem on some days! The preceding loop is an example of what is called a test harness (or driver)—a very simple program that surrounds a module so that we can directly enter a series of test data values. In many situations, a module's implementation can be destined for placement deep inside a complex program, where it would be difficult to thoroughly test. By separately enclosing it in a test driver, we gain complete control of its environment and can directly supply the module's inputs and check its outputs to ensure that it is correct. Once it has been tested, the code can be transplanted to its final place in the real program, where we can have confidence that it will work correctly.

7.3 The For Statement

The For statement is designed to simplify the writing of count-controlled loops. The following statement prints out the integers from 1 through n:

for (count = 1; count <= n; count++)
  cout << count << endl;

This For statement means “Initialize the loop control variable count to 1. While count is less than or equal to n, execute the output statement and increment count by 1. Stop the loop after count has been incremented to n + 1.”

In C++, a For statement is merely a compact notation for a While loop. In fact, the compiler essentially translates a For statement into an equivalent While loop as follows:

images

The syntax template for a For statement is shown here:

images

Expression1 is the While condition. InitStatement can be one of the following: the null statement (just a semicolon), a declaration statement (which always ends in a semicolon), or an expression statement (an expression ending in a semicolon). Therefore, a semicolon always appears before Expression1. (This semicolon isn't shown in the syntax template because InitStatement always ends with its own semicolon.)

Most often, a For statement is written such that InitStatement initializes a loop control variable and Expression2 increments or decrements the loop control variable. Here are two loops that execute the same number of times (50):

images

Just like While loops, Do-While and For loops may be nested. For example, this nested For structure

for (lastNum = 1; lastNum <= 7; lastNum++)
{
  for (numToPrint = 1; numToPrint <= lastNum; numToPrint++)
    cout << numToPrint;
  cout << endl;
}

prints the following triangle of numbers:

images

Although For statements are used primarily to implement count-controlled loops, C++ allows you to write any While loop by using a For statement. To use For loops intelligently, you should know the following facts.

1. In the syntax template, InitStatement can be the null statement, and Expression2 is optional. If Expression2 is omitted, there is no statement for the compiler to insert at the bottom of the loop. As a result, you could write the While loop

while (inputVal != 999)
  cin >> inputVal;

as the equivalent For loop

for ( ; inputVal != 999; )
  cin >> inputVal;

2. According to the syntax template, Expression1—the While condition—is optional. If you omit it, the expression is assumed to be true. The loop

for ( ; ; )
  cout “Hi” << endl;

is equivalent to the While loop

while (true)
  cout << “Hi” << endl;

Both of these structures are infinite loops that print “Hi” endlessly.

3. As highlighted here, the initializing statement, InitStatement, can be a declaration with initialization:

for (int i = 1; i <= 20; i++)
   cout << “Hi” << endl;

Here, the variable i has local scope, even though the statement does not have any braces that explicitly create a block. The scope of i extends only to the end of the For statement. Like any local variable, i is inaccessible outside its scope (that is, outside the For statement). Because i is local to the For statement, it's possible to write code like this:

for (int i = 1; i <= 20; i++)
  cout << “Hi” << endl;
for (int i = 1; i <= 100; i++)
  cout << “Ed” << endl;

This code does not generate a compile-time error (such as MULTIPLY DEFINED IDENTIFIER). We have declared two distinct variables named i, each of which is local to its own For statement.

As you have seen by now, the For statement in C++ is a very flexible structure. Its use can range from a simple count-controlled loop to a general-purpose, “anything goes” While loop. Some programmers squeeze a lot of work into the heading (the first line) of a For statement. For example, the program fragment

cin >> ch;
while (h != '.')
  cin >> ch;

can be compressed into the following For loop (the density of the highlighting shows the corresponding parts of the two loops):

for (cin >>; ch; ch != '.'; cin >> ch)
 ;

Because all the work is done in the For heading, there is nothing for the loop body to do. The body is simply the null statement.

With For statements, our advice is to keep things simple. The trickier the code is, the harder it will be for another person (or you!) to understand your code and track down errors. In this book, we use For loops for count-controlled loops only.

The following program contains both a For statement and a Switch statement. It analyzes the first 100 characters read from the standard input device and reports how many of the characters were letters, periods, question marks, and exclamation marks. For the first category (letters), we use the library function isalpha, which returns true if its argument is a letter and false otherwise.

images

Here is the output of a sample run:

images

The totals here look strange: They add up to only 85 characters. Where are the other 15? Well, the numbers are not counted in the totals, so that gives 94. But that still leaves 6 unaccounted for. Careful examination of the data shows that reading must end partway through the seventh line. Aha! The end-of-line characters on the first 6 lines of input account for the other missing characters!

Now we see that the code is actually correct—it was just our expectations of the results that were off. As you can see, sometimes the expected output in a test plan fails to take everything into consideration. Testing isn't always a one-way street where we have all the answers ahead of time and simply verify the code. In some cases, we find errors in the test plan, and need to fix it instead.

images

images

7.4 The Break and Continue Statements

The Break statement, which we introduced with the Switch statement, is also used with loops. A Break statement causes an immediate exit from the innermost Switch, While, Do-While, or For statement in which it appears. Notice the word innermost. If break is found in a loop that is nested inside another loop, control exits the inner loop but not the outer loop.

One of the more common ways of using break with loops is to set up an infinite loop and use If tests to exit the loop. Suppose we want to input ten pairs of integers, performing data validation and computing the square root of the sum of each pair. For data validation, assume that the first number of each pair must be less than 100 and the second must be greater than 50. Also, after each input, we want to test the state of the stream for EOF. Here's a loop using Break statements to accomplish the task:

loopCount = 1;
while (true)
{
  cin >> num1;
  if ( !cin || num1 >= 100)
    break;
  cin >> num2;
  if (!cin || num2 <= 50)
    break;
  cout << sqrt(float(num1 + num2)) << endl;
  loopCount++;
  if (loopCount > 10)
    break;
}

Note that we could have used a For loop to count from 1 to 10, breaking out of it as necessary. However, this loop is both count controlled and event controlled, so we prefer to use a While loop. When someone is reading code and sees a For loop, that person expects it to be purely count controlled.

The preceding loop contains three distinct exit points. Many programmers adhere to the single-entry, single-exit approach to writing a loop. With this philosophy, control enters a loop at one point only (the first executable statement) and exits at one point only (the end of the body). These programmers argue that multiple exits from a loop make the program logic hard to follow and difficult to debug. Other programmers take the position that it is acceptable to use break within a loop when the logic is clear. If we did not use break in this loop, it would contain multiple nested If statements with compound Boolean expressions that would obscure the actual computation.

Our advice is to use break sparingly; overuse can lead to confusing code. A good rule of thumb is this: Use break within loops only as a last resort. Specifically, use it only to avoid baffling combinations of compound Boolean expressions and nested Ifs.

Another statement that alters the flow of control in a C++ program is the Continue statement. This statement, which is valid only in loops, terminates the current loop iteration (but not the entire loop). It causes an immediate branch to the bottom of the loop—skipping the rest of the statements in the loop body—in preparation for the next iteration. Here is an example of a reading loop in which we want to process only the positive numbers in an input file:

for (dataCount = 1; dataCount <= 500; dataCount++)
{
  dataFile >> inputVal;
  if (inputVal <= 0)
    continue;
  cout << inputVal;
  .
  .
  .
}

If inputVal is less than or equal to 0, control branches to the bottom of the loop. Then, as with any For loop, the computer increments dataCount and performs the loop test before going on to the next iteration.

The Continue statement is not used often, but we present it for completeness (and because you may run across it in other people's programs). Its primary purpose is to avoid obscuring the main process of the loop that would result from indenting the process within an If statement. For example, the previously given code would be written without a Continue statement as follows:

for (dataCount = 1; dataCount <= 500; dataCount++)
{
  dataFile >> inputVal;
  if (inputVal > 0)
  {
    cout << inputVal;
     .
     .
     .
  }
}

Be sure to note the difference between continue and break. The Continue statement means “Abandon the current iteration of the loop, and go on to the next iteration.” The Break statement means “Exit the entire loop immediately.”

7.5 Guidelines for Choosing a Looping Statement

Here are some guidelines to help you decide when to use each of the three looping statements (While, Do-While, and For):

1. If the loop is a simple count-controlled loop, the For statement is a natural. Collecting the three loop control actions—initialize, test, and increment/decrement—into one location (the heading of the For statement) reduces the chances of forgetting to include one of them.

2. If the loop is an event-controlled loop whose body should execute at least once, a Do-While statement is appropriate.

3. If the loop is an event-controlled loop and nothing is known about the first execution, use a While statement.

4. When in doubt, use a While statement.

5. An infinite loop with Break statements sometimes clarifies the code but more often reflects an undisciplined loop design. Use it only after careful consideration of While, Do-While, and For.

Bear in mind that most programmers, when encountering a For statement in the code, expect the loop to be purely count controlled. Using a For statement to implement an eventcontrolled loop leads to tricky code and is to be avoided.

7.6 Additional C++ Operators

C++ has a rich, sometimes bewildering, variety of operators that allow you to manipulate values of the simple data types. Operators you have learned about so far include the assignment operator (=), the arithmetic operators (+, -, *, /, %), the increment and decrement operators (++, --), the relational operators (==, !=, <, <=, >, >=), and the logical operators (!, &&, ||). In certain cases, a pair of parentheses is also considered to be an operator—namely, the function call operator,

ComputeSum(x, y);

and the type cast operator,

y = float(someInt);

C++ also has many specialized operators that are seldom found in other programming languages. Here is a table of these additional operators. As you inspect the table, don't panic—a quick scan will do.

images

The operators in this table, along with those you already know, account for most—but not all—of the C++ operators. We introduce a few more operators in later chapters as the need arises.

Assignment Operators and Assignment Expressions

C++ has several assignment operators. The equal sign (=) is the basic assignment operator. When combined with its two operands, it forms an assignment expression (not an assignment statement). Every assignment expression has a value and a side effect—namely, that the value is stored into the object denoted by the lefthand side. For example, the expression

delta = 2 * 12

has the value 24 and the side effect of storing this value into delta.

In C++, any expression becomes an expression statement when it is terminated by a semicolon. All three of the following are valid C++ statements, although the first two have no effect at run time:

23;
2 * (alpha + beta);
delta = 2 * 12;

The third expression statement is useful because of its side effect of storing 24 into delta.

Because an assignment is an expression, not a statement, you can use it anywhere an expression is allowed. Here is a statement that stores the value 20 into firstInt, the value 30 into secondInt, and the value 35 into thirdInt:

thirdInt = (secondInt = (firstInt = 20) + 10) + 5;

Some C++ programmers use this style of coding because they think it is clever, but most find it hard to read and error-prone.

In Chapter 5, we cautioned against the mistake of using the = operator in place of the == operator:

images

The condition in the If statement is an assignment expression, not a relational expression. The value of the expression is 12 (interpreted in the If condition as true), so the else-clause is never executed. Worse yet, the side effect of the assignment expression is to store 12 into alpha, destroying its previous contents.

In addition to the = operator, C++ has several combined assignment operators (+=, *=, and the others listed in our table of operators). The combined assignment operators are another example of “ice cream and cake.” They are sometimes convenient for writing a line of code more compactly, but you can do just fine without them.

Increment and Decrement Operators

The increment and decrement operators (++ and --) operate only on variables, not on constants or arbitrary expressions. Suppose a variable someInt contains the value 3. The expression ++someInt denotes pre-incrementation. The side effect of incrementing someInt occurs first, so the resulting value of the expression is 4. In contrast, the expression someInt++ denotes post-incrementation. The value of the expression is 3, and then the side effect of incrementing someInt takes place. The following code illustrates the difference between pre- and post-incrementation:

int1 = 14;
int2 = ++int1;    // int1 == 15 && int2 == 15
int1 = 14;
int2 = int1++;    // int1 == 15 && int2 == 14

Using side effects in the middle of larger expressions is always a bit dangerous. It's easy to make semantic errors, and the code may be confusing to read. Look at this example:

a = (b = c++) * --d / (e += f++);

Some people make a game of seeing how much they can do in the fewest keystrokes possible. But they should remember that serious software development requires writing code that other programmers can read and understand. Overuse of side effects hinders this goal. By far the most common use of ++ and -- is to do the incrementation or decrementation as a separate expression statement:

count++;

Here, the value of the expression is unused, but we get the desired side effect of incrementing count. In this example, it doesn't matter whether you use pre-incrementation or post-incrementation. The choice is up to you.

Bitwise Operators

The bitwise operators listed in the operator table (<<, >>, &, |, and so forth) are used for manipulating individual bits within a memory cell. This book does not explore the use of these operators; the topic of bit-level operations is most often covered in a course on computer organization and assembly language programming. However, we point out two things about the bitwise operators.

First, the built-in operators << and >> are the left shift and right shift operators, respectively. Their purpose is to take the bits within a memory cell and shift them to the left or right. Of course, we have been using these operators all along, but in an entirely different context—stream input and output. The header file iostream uses an advanced C++ technique called operator overloading to give additional meanings to these two operators. An overloaded operator is one that has multiple meanings, depending on the data types of its operands. When looking at the << operator, the compiler determines by context whether a left shift operation or an output operation is desired. Specifically, if the first (left-hand) operand denotes an output stream, then it is an output operation. If the first operand is an integer variable, it is a left shift operation.

Second, we repeat our caution from Chapter 5: Do not confuse the && and || operators with the & and | operators. The statement

if (i == 3 & j == 4)    // Wrong
  k = 20;

is syntactically correct because & is a valid operator (the bitwise AND operator). The program containing this statement compiles correctly but executes incorrectly. Although we do not examine what the bitwise AND and OR operators do, just be careful to use the relational operators && and || in your logical expressions.

The Cast Operation

You have seen that C++ is very liberal about letting the programmer mix data types in expressions, assignment operations, argument passing, and returning a function value. However, implicit type coercion takes place when values of different data types are mixed together. Instead of relying on implicit type coercion in a statement such as

intVar = floatVar;

we have recommended using an explicit type cast to show that the type conversion is intentional:

intVar = int(floatVar);

In C++, the cast operation comes in three forms:

intVar = int(floatVar);                   // Functional notation
intVar = (int)floatVar;                   // Prefix notation
intVar = static_cast<int>(floatVar);      // Keyword notation

The first form is called functional notation because it looks like a function call. It isn't really a function call (there is no user-defined or predefined subprogram named int), but it has the syntax and visual appearance of a function call. The second form, prefix notation, doesn't look like any familiar language feature in C++. In this notation, the parentheses surround the name of the data type, not the expression being converted. The third form uses a keyword and angle brackets to explicitly document that a cast operation is to be performed. Prefix notation is the only form available in the C language; the original version of C++ added functional notation and, with the definition of the C++ standard, keyword notation was included.

Functional notation has one restriction on its use: The data type name must be a single identifier. If the type name consists of more than one identifier, you must use prefix notation or keyword notation. For example,

myVar = unsigned int(someFloat);               // Not allowed
myVar = (unsigned int) someFloat;              // Yes
myVar = static_cast<unsigned int> someFloat;   // Yes

Most software engineers now recommend the use of the keyword cast. From the viewpoint of documentation, it is much easier to find these keywords within a large program. And although static_cast is very similar to the other two casts for values of the simple data types, there are more advanced aspects of C++, involving object-oriented programming, where it causes the compiler to perform additional checks that help to spot errors.

C++ defines three additional forms of the keyword cast that have special purposes. We do not explain them further in this book, but we list them here so that you will be aware of their existence: const_cast, dynamic_cast, and reinterpret_cast.

The sizeof Operator

The sizeof operator is a unary operator that yields the size, in bytes, of its operand. The operand can be a variable name, as in

sizeof someInt

Alternatively, the operand can be the name of a data type, enclosed in parentheses:

sizeof(float)

You could find out the sizes of various data types on your machine by using code like this:

cout << “Size of a short is “ << sizeof(short) << endl;
cout << “Size of an int is “ << sizeof(int) << endl;
cout << “Size of a long is “ << sizeof(long) << endl;

The ?: Operator

The last operator in our operator table is the ?: operator, sometimes called the conditional operator. It is a ternary (three-operand) operator with the following syntax:

images

Here's how it works. First, the computer evaluates Expression1. If the value is true, then the value of the entire expression is Expression2; otherwise, the value of the entire expression is Expression3. (Only one of Expression2 and Expression3 is evaluated, but both of them must evaluate to the same data type.) A classic example of the ?: operator's use is to set a variable max equal to the larger of two variables a and b. Using an If statement, we would do it this way:

if (a > b)
  max = a;
else
  max = b;

With the ?: operator, we can use the following assignment statement:

max = (a > b) ? a : b;

Here is another example. The absolute value of a number x is defined as

images

To compute the absolute value of a variable x and store it into y, you could use the ?: operator as follows:

y = (x >= 0) ? x : -x;

In both the max and the absolute value examples, we placed parentheses around the expression being tested. These parentheses are unnecessary because, as we'll see shortly, the conditional operator has very low precedence. Even so, it is customary to include the parentheses for clarity.

Operator Precedence

The following table summarizes operator precedence for the C++ operators we have encountered so far, excluding the bitwise operators. (Appendix B contains the complete list.) In the table, the operators are grouped by precedence level, and a horizontal line separates each precedence level from the next-lower level.

Operator Associativity Remarks
()++ -- Left to rightRight to left Function call and function-style cast ++ and -- as postfix operators
++ -- ! Unary + Unary - (cast) sizeof Right to leftRight to left ++ and -- as prefix operators
* / % Left to right
+ - Left to right
< <= > >= Left to right
== != Left to right
&& Left to right
|| Left to right
?: Right to left
= += -= *= /= Right to left

The column labeled Associativity describes grouping order. Within a precedence level, most operators group from left to right. For example,

a - b + c

means

(a - b) + c

and not

a - (b + c)

Certain operators, though, group from right to left-specifically, the unary operators, the assignment operators, and the ?: operator. Look at the assignment operators, for example.

The expression

sum = count = 0

means

sum = (count = 0)

This associativity makes sense because the assignment operation is naturally a right-to-left operation.

A word of caution: Although operator precedence and associativity dictate the grouping of operators with their operands, C++ does not define the order in which subexpressions are evaluated. Therefore, using side effects in expressions requires extra care. For example, if i currently contains 5, the statement

j = ++i + i;

stores either 11 or 12 into j, depending on the particular compiler being used. Let's see why. There are three operators in this expression statement: =, ++, and +. The ++ operator has the highest precedence, so it operates just on i, not the expression i + i. The addition operator has higher precedence than the assignment operator, giving implicit parentheses as follows:

j = (++i + i);

So far, so good. But now we ask this question: In the addition operation, is the left operand or the right operand evaluated first? The C++ language doesn't dictate the order. If a compiler generates code to evaluate the left operand first, the result is 6 + 6, or 12. Another compiler might generate code to evaluate the right operand first, yielding 6 + 5, or 11. To be assured of left-to-right evaluation in this example, you should force the ordering with two separate statements:

++i;
j = i + i;

The moral here is that if you use multiple side effects in expressions, you increase the risk of unexpected or inconsistent results. For the newcomer to C++, it's better to avoid unnecessary side effects altogether.

Type Coercion in Arithmetic and Relational Expressions

Suppose that an arithmetic expression consists of one operator and two operands-for example, 3.4*sum or var1/var2. If the two operands are of different data types, then one of them is temporarily promoted (or widened) to match the data type of the other. To understand exactly what promotion means, let's look at the rule for type coercion in an arithmetic expression.1

Step 1: Each char, short, bool, or enumeration value is promoted (widened) to int. If both operands are now int, the result is an int expression.

Step 2: If Step 1 still leaves a mixed type expression, the following precedence of types is used:

images

The value of the operand of the “lower” type is promoted to that of the “higher” type, and the result is an expression of that type.

A simple example is the expression someFloat+2. This expression has no char, short, bool, or enumeration values in it, so Step 1 leaves a mixed type expression. In Step 2, int is a “lower” type than float, so the value 2 is coerced temporarily to the float value-say, 2.0. Then the addition takes place, and the type of the entire expression is float.

This description of type coercion also holds for relational expressions such as

someInt <= someFloat

The value of someInt is temporarily coerced to floating-point representation before the comparison takes place. The only difference between arithmetic expressions and relational expressions is that the resulting type of a relational expression is always bool-the value true or false.

Here is a table that describes the result of promoting a value from one simple type to another in C++:

From To Result of Promotion
double long double Same value, occupying more memory space
float double Same value, occupying more memory space
Integral type Floating-point type Floating-point equivalent of the integer value; fractional part is zero
Integral type Its unsigned counterpart Same value, if original number is nonnegative; a radically different positive number, if original number is negative
Signed integral type Longer signed integral type Same value, occupying more memory space
Unsigned integral type Longer integral type (either signed or unsigned) Same nonnegative value, occupying more memory space

Note: The result of promoting a char to an int is compiler dependent. Some compilers treat char as unsigned char, so promotion always yields a nonnegative integer. With other compilers, char means signed char, so promotion of a negative value yields a negative integer.
 

The note at the bottom of the table suggests a potential problem if you are trying to write a portable C++ program. If you use the char type only to store character data, there is no problem. C++ guarantees that each character in a machine's character set (such as ASCII) is represented as a non-negative value. Using character data, promotion from char to int gives the same result on any machine with any compiler.

If you try to save memory by using the char type for manipulating small signed integers, however, then promotion of these values to the int type can produce different results on different machines! That is, one machine may promote negative char values to negative int values, whereas the same program on another machine might promote negative char values to positive int values. The moral is this: Unless you are squeezed to the limit for memory space, do not use char to manipulate small signed numbers; use char only to store character data.
 

The Rich Uncle

PROBLEM: Your rich uncle has just died, and in his desk you find two wills. One of them, dated several months ago, leaves you and your relatives a substantial part of his fortune; the other, dated last week, gives everything to his next-door neighbor. Being suspicious that the second will is a forgery, you decide to write a program to analyze writing style and compare the wills. The program reads and categorizes each character. When the entire file has been read, it prints a summary table showing the percentage of uppercase letters, lowercase letters, decimal digits, blanks, and end-of-sentence punctuation marks in the data file. The names of the input and output files are read from the keyboard. The name of the input file should be printed on the output.

INPUT: Text on a file whose name is read from the keyboard.

OUTPUT: A table giving the name of each category and the percentage of the total that the category represents on the file whose name is read from the keyboard.

DISCUSSION: Doing this task by hand would be tedious but quite straightforward. You would set up five places to make hash marks, one for each of the categories of symbols to be counted. You would then read the text character by character, determine in which category to put each character, and make a hash mark in the appropriate place.

You can look at a character and tell immediately which category to mark. You can simulate “looking” with an If statement with branches for the uppercase letters, the lowercase letters, the digits, a blank, and end-of-sentence punctuation marks. An uppercase letter is defined as one between 'A' and 'Z' inclusive; a lowercase letter is defined as one between 'a' and 'z' inclusive. But wait: Before you start writing algorithms to recognize these categories, shouldn't you look in the library to see if they already exist? Sure enough, header file <cctype> contains functions to recognize uppercase letters, lowercase letters, and digits.

Another function recognizes whitespace. Will that do for counting blanks? Function isspace returns true if the character is a blank, newline, tab, carriage return, or form feed. The problem specifies a count of the number of blanks. A newline might function as the end of a word like a blank, but the problem doesn't ask for the number of words; it asks for the number of blanks.

There is a function to recognize punctuation, but not end-of-sentence punctuation. The problem doesn't state what end-of-sentence marks are. What kinds of sentences are there? Regular sentences, ending in a period; questions, ending in a question mark; and exclamations, ending in an exclamation mark.

Here is a summary of the function in <cctype> that we will use:

isupper() Returns 1 if its argument is an uppercase letter

islower() Returns 1 if its argument is a lowercase letter

isdigit() Returns 1 if its argument is a numeric digit

These functions don't return Boolean values. How can they be used in a Boolean expression? They return an int value that is nonzero (coerced to true in an If or While condition) or 0 (coerced to false in an If or While condition). We have more to say about these functions in Chapter 9.

ASSUMPTIONS: File is not empty.

Main Level 0
Open files for processing
IF files not opened okay
   Write error message
   return 1
Get a character

 

DO
  Increment appropriate character count
  Get a character
WHILE (more data)
Print table

Several of the counters are incremented based on the results of a function; others are based on the character itself. We must use If statements for the group characters, but we can use a Switch statement for the individual characters.

Increment Counters Level 1
IF (isupper(character))
   Increment uppercaseCounter
ELSE IF (islower(character))
  Increment lowercaseCounter
ELSE IF (isdigit(character))
  Increment digitCounter
SWITCH (character)
  Case ' ' : Increment blankCounter
  Case '.' :
  Case '!' :
  Case '? ' : Increment punctuationCounter

At this point you realize that the instructions do not indicate whether the percentages are to be taken of the total number of characters read, including those that do not fit any of the categories, or of the total number of characters that fall into the five categories. You decide to assume that all characters should be counted. Thus you add an Else branch to this module that increments a counter (called allElseCounter) for all characters that do not fall into one of the five categories. This assumption needs to be added to the program. This count can be placed in the default option of the Switch statement.

Calculate and Print Percentages
Set Total to sum of 6 counters
Print “Percentage of uppercase letters:”, uppercaseCounter / Total * 100
Print “Percentage of lowercase letters:”, lowercaseCounter / Total * 100
Print “Percentage of decimal digits:”, digitCounter / Total * 100
Print “Percentage of blanks:”, blankCounter / Total * 100
Print “Percentage of end-of-sentence punctuation:”, punctuationCounter / Total * 100

MODULE STRUCTURE CHART

images

images

images

TESTING: To be tested thoroughly, the RichUncle program must be run with all possible combinations of the categories of characters being counted. Following is the minimum set of cases that must be tested:

1. All the categories of characters are present.

2. Four of the categories are present; one is not. (This alone will require five test runs.)

3. Only characters that fall into one of the five categories are present.

4. Other characters are present.

The output listed below was run on a file containing more than 4000 characters. (It isn't a will, but it is sufficient for testing.)

images

Testing and Debugging

The same testing techniques we used with While loops apply to Do-While and For loops. There are, however, a few additional considerations with these loops.

The body of a Do-While loop always executes at least once. Thus you should try data sets that show the result of executing a Do-While loop for the minimal number of times.

With a data-dependent For loop, it is important to test for proper results when the loop executes zero times. This occurs when the starting value is greater than the ending value (or less than the ending value if the loop control variable is being decremented).

When a program contains a Switch statement, you should test it with enough different data sets to ensure that each branch is selected and executed correctly. You should also test the program with a switch expression whose value is not found in any of the case labels.

Testing and Debugging Hints

1. In a Switch statement, make sure there is a Break statement at the end of each case alternative. Otherwise, control “falls through” to the code in the next case alternative.

2. Case labels in a Switch statement are made up of values, not variables. They may, however, include named constants and expressions involving only constants.

3. A switch expression cannot be a floating-point or string expression, and a case constant cannot be a floating-point or string constant.

4. If there is a possibility that the value of the switch expression might not match one of the case constants, you should provide a default alternative. In fact, it is a good practice to always include a default alternative.

5. Double-check long Switch statements to make sure that you haven't omitted any branches.

6. The Do-While loop is a posttest loop. If there is a possibility that the loop body should be skipped entirely, use a While statement or a For statement.

7. The For statement heading (the first line) always has three pieces within the parentheses. Most often, the first piece initializes a loop control variable, the second piece tests the variable, and the third piece increments or decrements the variable. The three pieces must be separated by semicolons. Any of these pieces can be omitted, but the semicolons must still be present.

8. With nested control structures, the Break statement can exit from only one level of nesting—the innermost Switch or loop in which the break keyword is located.

images Summary

The Switch statement is a multiway selection statement. It allows the program to choose among a set of branches. A Switch containing Break statements can always be simulated by an If-Then-Else-If structure. If a Switch can be used, however, it often makes the code easier to read and understand. A Switch statement cannot be used with floating-point or string values in the case labels.

The Do-While is a general-purpose looping statement. It is like the While loop except that its test occurs at the end of the loop, guaranteeing at least one execution of the loop body. As with a While loop, a Do-While continues as long as the loop condition is true. A Do-While is convenient for loops that test input values and repeat if the input is not correct.

The For statement is also a general-purpose looping statement, but its most common use is to implement count-controlled loops. The initialization, testing, and incrementation (or decrementation) of the loop control variable are centralized in one location, the first line of the For statement.

C++ provides a wealth of additional operators. Many of these—such as the extra assignment operations, increment, and decrement—involve a combination of a side effect the return of a value. Although use of side effects can save a few keystrokes, it usually results in code that is harder to understand.

The For, Do-While, and Switch statements are the “ice cream and cake” of C++. We can live without them if we absolutely must, but they are very nice to have.

images Quick Check

1. If a problem calls for a pure count-controlled loop, would you use a While, a Do-While, or a For statement to implement the loop? (pp. 308–310)

2. When it is executed within a loop, to where does a Break statement transfer control? Where does control proceed from a Continue statement? (pp. 314–316)

3. In converting an If-Then-Else-If multiway branching structure to a Switch statement, which part of the Switch corresponds to the final Else branch? (pp. 298–301)

4. Which looping statement always executes its body at least once? (pp. 304–306)

5. Write a For statement that counts from -10 to 10. (pp. 308–310)

6. What is the difference between the && operator and the & operator? (pp. 319–320)

Answers

1. A For statement. 2. The Break statement immediately exits the loop; the Continue statement sends control to the end of the loop. 3. The default branch. 4. Do-While. 5. for (int count = -10; count <= 10; count++) 6. && is a logical AND of a pair of bool values; & is a bitwise AND of a pair of integral values.

images Exam Preparation Exercises

1. A switch expression may be of type bool, char, int, or long, but not of type float. True or false?

2. A variable declared in the initialization statement of a For loop has global scope. True of false?

3. Any While loop can be directly rewritten as a Do-While merely by changing the statement syntax and moving the exit condition to the end of the loop. True or false?

4. A Break statement is not allowed in a For loop, but a Continue statement is. True or false?

5. Which of the looping statements in C++ are pretest loops and which are posttest loops?

6. What happens when you forget to include the Break statements in a Switch statement?

7. If you omit all of the clauses within a For loop (for ( ; ; ) ), what would be the condition in an equivalent While loop?

8.How many times is the inner loop body executed in the following nested loop?

for (int x = 1; x <= 10; x++)
  for (int y = 1; y <= 10; y++)
    for (int z = 1; z <= 10; z++)
      cout << x + y + z;

9. Which looping statement would you choose for a problem in which the decision to repeat a process depends on an event, and the event cannot occur until the process is executed at least once?

10. Which looping statement would you choose for a problem in which the decision to repeat the process depends on an iteration counter and on the state of an input file, and the process may be skipped if the file is empty?

11. What is output by the following code segment if wood contains 'O'?

switch (wood)
{
  case 'P' : cout << “Pine”;
  case 'F' : cout << “Fir”;
  case 'C' : cout << “Cedar”;
  case 'O' : cout << “Oak”;
  case 'M' : cout << “Maple”;
  default : cout << “Error”;
}

12. What is output by the following code segment if month contains 8?

switch (month)
{
  case 1 : cout << “January”; break;
  case 2 : cout << “February”; break;
  case 3 : cout << “March”; break;
  case 4 : cout << “April”; break;
  case 5 : cout << “May”; break;
  case 6 : cout << “June”; break;
  case 7 : cout << “July”; break;
  case 8 : cout << “August”; break;
  case 9 : cout << “September”; break;
  case 10 : cout << “October”; break;
  case 11 : cout << “November”; break;
  case 12 : cout << “December”; break;
  default : cout << “Error”;
}

13. What is output by the following code segment?

outCount = -1;
do
{
  inCount = 3;
  do
  {
    cout << outCount + inCount << endl;
    inCount--;
  } while (inCount > 0);
  outCount++;
} while (outCount < 2);

14. What is output by the following code segment?

for (int outCount = -1; outCount < 2; outCount++)
  for (int inCount = 3; inCount > 0; inCount--)
    cout << outCount + inCount << endl;

15. Rewrite the code segment in Exercise 14 using While loops.

16. What is printed by the following code segment?

for (int count = 1; count <= 4; count++)
switch (count)
{
  case 4 : cout << “ cow?”; break;
  case 2 : cout << “ now”; break;
  case 1 : cout << “How”; break;
  case 3 : cout << “ brown”; break;
}

17. Write a single simple statement that has the same effect on cout as the code segment in Exercise 16.

18. What does the following code segment (which is written in poor style) output?

count = 1;
for ( ; ; count++)
  if (count < 3)
    cout << count;
  else
    break;

19. The difference between an assignment expression and an assignment statement is a semicolon. True or false?

20. The sizeof operator can be used to determine whether a machine's int type is 32 or 64 bits long. True or false?

21. Which group of C++ operators has the lowest precedence of all?

22. What is the difference in effect of writing count++ versus ++count?

23. In what situation is it necessary to use prefix notation for the cast operation instead of functional notation?

images Programming Warm-Up Exercises

1. Write a Switch statement that outputs the day of the week to cout according to the int value in day that ranges from 0 to 6, with 0 being Sunday.

2. Extend the Switch statement in Exercise 1 so that it outputs “Error” if the value in day is not in the range of 0 through 6.

3. Write a For loop that outputs the days of the week, each on a separate line, starting from Saturday and working backward to Sunday.

4. Change the For loop in Exercise 4 so that it outputs the days of the week in forward order, starting on Wednesday and going through Tuesday.

5. Write a Do-While loop that prompts for and inputs a user entry of “Y” or “N”. If the user fails to enter a correct value, the loop outputs an error message and then repeats the request for the user to enter the value.

6. Write a code segment that adds up the positive integers, starting at 1, until the sum equals or exceeds a value read from the keyboard, and then prints the last integer added to the sum. Use a Do-While loop to do the summing.

7. Write a nested For loop that prints out a multiplication table for the integers 1 through 10.

8. Write a nested For loop that prints a filled right triangle of stars, one star on the first line, two on the next, and so on, up to the tenth line having ten stars.

9. Rewrite the nested loop in Exercise 9 with Do-While loops.

10. Rewrite the nested loop in Exercise 9 with While loops.

11. Write a code segment, using For loops, that prints a hollow rectangle of stars whose width and height are specified by two values read from the keyboard. The top and bottom of the rectangle is a solid row of stars, each row between the top and bottom consists of a star, then width -2 spaces, and another star.

12. Write a nested For loop that outputs the hours and minutes in the period from 3:15 to 7:30.

13. Extend the loop in Exercise 13 to also output seconds.

14. How many lines do the loops in Exercises 12 and 13 output?

15. Write assignment expression statements that do the following:

a. Add 7 to the variable days

b. Multiply the value in variable radius by 6.2831853

c. Subtract 40 from variable workHours

d. Divide variable average by variable count

16. Write an expression whose result is the number of bits in a value of type long.

17. Use the C++ precedence rules to remove any unnecessary parentheses from the following expressions:

a. ((a * b)) + (c * d))

b. ((a * b) / (c * d))

c. ((a + b) + ((c / (d + e)) * f))

d. (((a + b) / (c + d)) * (e + f))

e. ((-a + b) <= (c * d)) && ((a + b) >= (c - d))

images Programming Problems

1. Programming Problem 1 in Chapter 5 asked you to write a program that takes a letter as input and outputs the corresponding word in the International Civil Aviation Organization Alphabet. Extend the program so that it inputs a string and outputs the series of ICAO words that would be used to spell it out. For example:

Enter string: program
Phonetic version is: Papa Romeo Oscar Golf Romeo Alpha Mike

Write the program so that it determines the word corresponding to a specified letter using a Switch statement instead of an If structure. For ease of reference, the ICAO alphabet is repeated here:

A Alpha
B Bravo
C Charlie
D Delta
E Echo
F Foxtrot
G Golf
H Hotel
I India
J Juliet
K Kilo
L Lima
M Mike
N November
O Oscar
P Papa
Q Quebec
R Romeo
S Sierra
T Tango
U Uniform
V Victor
W Whiskey
X X-ray
Y Yankee
Z Zulu

Be sure to use proper formatting and appropriate comments in your code. Provide appropriate prompts to the user. The output should be clearly labeled and neatly formatted.

2. Programming Problem 2 in Chapter 5 asked you write a C++ program that asks the user to enter his or her weight and the name of a planet. The program was then to output how much the user would weigh on that planet. Rewrite that program so that the selection of the factor to use in computing the weight is made with a Switch statement instead of an If structure.

    For ease of reference, the information for the original problem is repeated here. The following table gives the factor by which the weight must be multiplied for each planet. The program should output an error message if the user doesn't type a correct planet name. The prompt and the error message should make it clear to the user how a planet name must be entered. Be sure to use proper formatting and appropriate comments in your code. The output should be clearly labeled and neatly formatted.

Mercury 0.4155
Venus 0.8975
Earth 1.0
Moon 0.166
Mars 0.3507
Jupiter 2.5374
Saturn 1.0677
Uranus 0.8947
Neptune 1.1794
Pluto 0.0899

3. You are working for a company that has traveling salespeople. The salespeople call in their sales to a fulfillment desk, where the sales are all entered into a file. Each sale is recorded as one line on the file sales.dat as a salesperson ID number, an item number, and a quantity, with all three items separated by blanks. There are 10 salespeople, with IDs of 1 through 10. The company sells eight different products, with IDs of 7 through 14 (some older products have been discontinued). The unit prices of the products are given here:

Product Number Unit Price
7 345.00
8 853.00
9 471.00
10 933.00
11 721.00
12 663.00
13 507.00
14 259.00

You have been asked to write a program that reads in the sales file, and generates a separate file for each salesperson containing just that individual's sales. Each line from the sales file is copied to the appropriate salesperson file (salespers1.dat through salespers10.dat), with the salesperson ID omitted. The total for the sale (quantity times unit price) is appended to the record. At the end of processing, the total sales for each salesperson should be output with informative labels to cout. Use functional decomposition to design the program. Be sure that the program handles invalid ID numbers. If a salesperson ID is invalid, write an error message to cout. If a product number is invalid, write the error message to the salesperson's file and don't compute a total for that sale. There should be ample opportunity to use Switch statements and value-returning functions in this application.

4. Write a number-guessing game in which the computer selects a random number in the range of 0 to 100, and users get a maximum of 20 attempts to guess it. At the end of each game, users should be told whether they won or lost, and then be asked whether they want to play again. When the user quits, the program should output the total number of wins and losses. To make the game more interesting, the program should vary the wording of the messages that it outputs for winning, for losing, and for asking for another game. Create as many as 10 different messages for each of these cases, and use random numbers to choose among them. See Appendix C.7 for information on the C++ random-number generator functions. This application should provide a good opportunity for you to use a Do-While statement and Switch statements. Use functional decomposition to solve the problem, write your C++ code using good style and documenting comments, and have fun thinking up some messages that will surprise the user.

5. Write a functional decomposition and a C++ program that reads a time in numeric form and prints it in English. The time is input as hours and minutes, separated by a space. Hours are specified in 24-hour time (15 is 3 p.m.), but the output should be in 12-hour a.m./p.m. form. Note that noon and midnight are special cases. Here are some examples:

Enter time: 12 00
Noon
Enter time: 0 00
Midnight
Enter time: 6 44
Six forty four AM
Enter time: 18 11
Six eleven PM

Write your C++ code using good style and documenting comments. This application should provide you with ample opportunity to use Switch statements.

6. Extend the program in Problem 5 so that it asks the user if he or she wants to enter another time, and then repeats the process until the response to the question is “no.” You should be able to code this easily using a Do-While statement.

images Case Study Follow-Up

1. Which changes would be necessary in the RichUncle program if uppercase letters and lowercase letters were to be counted as members of the same category?

2. You decide that you are interested in counting the number of words in the text. How might you estimate this number?

3. Given that you can calculate the number of words in the text, how can you calculate the average word length?

4. Can you think of any other characteristics of a person's writing style that you might be able to measure?


1 The rule we give for type coercion is a simplified version of the rule found in the C++ language definition. The complete rule has more to say about unsigned types, which we rarely use in this book.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.161.165