Before writing a program to solve a problem, you should have a thorough understanding of the problem and a carefully planned approach to solving it.
A procedure for solving a problem in terms of the actions (p. 105) to execute and the order (p. 105) in which these actions execute is called an algorithm (p. 105).
Specifying the order in which statements (actions) execute in a program is called program control (p. 105). This chapter investigates program control using C++’s control statements (p. 105).
Pseudocode (p. 105) is an informal language similar to everyday English that helps you develop algorithms without having to worry about the strict details of C++ language syntax.
Pseudocode helps you “think out” a program before attempting to write it.
Pseudocode normally describes only statements representing the actions that occur after you convert a program from pseudocode to C++ and the program is run on a computer.
Normally, statements in a program are executed one after the other in the order in which they’re written—this is called sequential execution (p. 106).
Various C++ statements enable you to specify that the next statement to execute is not necessarily the next one in sequence. This is called transfer of control (p. 106).
Bohm and Jacopini’s work demonstrated that all programs could be written in terms of only sequence (p. 106), selection (p. 106) and iteration (p. 106).
The sequence (p. 106) structure is built into C++. Unless directed otherwise, the computer executes C++ statements one after the other in the order in which they’re written.
A UML activity diagram (p. 106) models the workflow (p. 107; also called the activity) of a portion of a software system. Such workflows may include a portion of an algorithm.
Activity diagrams are composed of symbols, such as action-state symbols (p. 107), diamonds (p. 107) and small circles (p. 107). These are connected by transition arrows (p. 107), which represent the flow of the activity—that is, the order in which the actions should occur.
Like pseudocode, activity diagrams help you develop and represent algorithms.
An action state (p. 107) contains an action expression (p. 107) that specifies a particular action to perform.
Transition arrows in the activity diagram represent transitions (p. 107), specifying the order in which the actions represented by the action states occur.
A solid circle (p. 107) at the top of the activity diagram represents the initial state (p. 107)—the beginning of the workflow before the program performs the modeled actions.
A solid circle surrounded by a hollow circle (p. 107) at the bottom of the diagram represents the final state—the end of the workflow after the program performs its actions.
UML notes (p. 107; like comments in C++) are explanatory remarks that describe the purpose of symbols in the diagram. A dotted line (p. 107) connects each note with the element it describes.
C++ has three types of selection statements (p. 108).
The if
single-selection statement (p. 108) performs (selects) an action (or group of actions), if a condition is true, or skips it, if the condition is false.
The if
…else
double-selection statement (p. 108) performs an action (or group of actions) if a condition is true and performs a different action (or group of actions) if the condition is false.
The switch
multiple-selection statement (p. 108) performs one of many different actions (or group of actions), depending on the value of an expression.
C++ provides four iteration statements (p. 108; sometimes called repetition statements or looping statements) that enable programs to perform statements repeatedly as long as a loop-continuation condition (p. 108) remains true.
The iteration statements are the while
, do
…while
, for
and range-based for
statements.
The while
and for
statements perform the action (or group of actions) in their bodies zero or more times—if the loop-continuation condition is initially false, the action (or group of actions) will not execute.
The do
…while
statement performs its body action(s) one or more times.
Each of the words if
, else
, switch
, while
, do
and for
is a C++ keyword. Keywords cannot be used as identifiers, such as variable names, and must be spelled with only lowercase letters.
Every program is formed by combining as many control statements as is appropriate for the algorithm the program implements.
Single-entry/single-exit control statements (p. 109) make it easy to build programs by connecting the exit point of one to the entry point of the next. We call this control-statement stacking (p. 109).
In control-statement nesting (p. 109), one control statement appears inside another.
if
Single-Selection StatementIn C++, a decision can be based on any expression that can be evaluated as zero or nonzero—if the expression evaluates to zero, it’s treated as false; if the expression evaluates to nonzero, it’s treated as true.
C++ provides the data type bool
(p. 110) for Boolean variables that can hold only the values true
(p. 110) and false
(p. 110)—each of these is a C++ keyword.
The UML decision symbol (p. 110; a diamond) indicates that a decision is to be made. The workflow continues along a path determined by the symbol’s associated guard conditions (p. 110), which can be true or false. Each transition arrow emerging from a decision symbol has a guard condition (specified in square brackets next to the arrow). If a guard condition is true, the workflow enters the action state to which the transition arrow points.
if
…else
Double-Selection StatementThe if
…else
double-selection statement (p. 110) allows you to specify an action (or group of actions) to perform when the condition is true and another action (or group of actions) when the condition is false.
if
…else
StatementsA program can test multiple cases by placing if
…else
statements inside other if
…else
statements to create nested if
…else
statements (p. 111).
else
ProblemEnclosing control-statement bodies in braces ({
and }
) avoids a logic error called the “dangling-else
” problem (Exercises 4.23–4.25).
Statements contained in a pair of braces (such as the body of a control statement or function) form a block (p. 113). A block can be placed anywhere in a function that a single statement can be placed.
Syntax errors are caught by the compiler.
A logic error (p. 113) has its effect at execution time.
A fatal logic error (p. 114) causes a program to fail and terminate prematurely.
A nonfatal logic error (p. 114) allows a program to continue executing but causes it to produce incorrect results.
Just as a block can be placed anywhere a single statement can be placed, it’s also possible to have an empty statement, ;
(p. 114), where a statement would normally be.
?:
)The conditional operator , ?:
(p. 114)
is C++’s only ternary operator (p. 114). Together, the operands and the ?:
symbol form a conditional expression (p. 114).
The first operand (to the left of the ?
) is a condition, the second operand (between the ?
and :
) is the value of the conditional expression if the condition is true and the third operand (to the right of the :
) is the value of the conditional expression if the condition is false.
while
Iteration StatementA while
iteration statement (p. 118) allows you to specify that a program should repeat an action (or group of actions) while some condition remains true.
UML’s merge symbol (p. 118; a diamond) joins two flows of activity into one.
A decision symbol has one transition arrow pointing to the diamond and two or more pointing out from it to indicate possible transitions from that point. In addition, each transition arrow pointing out of a decision symbol has a guard condition next to it.
A merge symbol has two or more transition arrows pointing to the diamond and only one pointing from the diamond, to indicate multiple activity flows merging to continue the activity. None of the transition arrows associated with a merge symbol has a guard condition.
An unsigned int
variable can assume only nonnegative values. Variables of unsigned
integer types can represent values from 0 to approximately twice the positive range of the corresponding signed integer types.
You can determine your platform’s maximum unsigned int
value with the constant UINT_MAX
from <climits>
.
A variable declared in a function body is a local variable and can be used only from the line of its declaration to the closing right brace of the block in which the variable is declared.
A local variable’s declaration must appear before the variable is used; otherwise, a compilation error occurs.
Integer division yields an integer result—any fractional part of the calculation is truncated.
Adding two integers could result in a value that’s too large to store in an int
variable. This is known as arithmetic overflow (p. 122) and causes undefined behavior, which can lead to unintended results and security problems.
The maximum and minimum values that can be stored in an int
variable are represented by the constants INT_MAX
and INT_MIN
, respectively, which are defined in the header <climits>
. There are similar constants for the other integral types and for floating-point types.
To see these constants’ values on your platform, open the headers <climits>
and <cfloat>
in a text editor (you can search your file system for these files).
A program uses range checking (p. 123) to ensure that values are within a specific range.
In sentinel-controlled iteration (p. 124), a special value called a sentinel value (p. 123; also called a signal value, a dummy value or a flag value) is used to indicate “end of data entry.”
Sentinel-controlled iteration is often called indefinite iteration (p. 124) because the number of iterations is not known before the loop begins executing.
Top-down, stepwise refinement (p. 124) is essential to the development of well-structured programs.
Begin with a pseudocode representation of the top (p. 124)—a single statement that, in effect, is a complete representation of a program.
The top rarely conveys sufficient detail from which to write a C++ program. So in the first refinement (p. 124), we refine the top into a series of smaller tasks and list these in the order in which they’ll be performed. This refinement uses only tasks in sequence.
In the second refinement (p. 124), we commit to specific variables and logic.
A number with a decimal point is called a real number or floating-point number (p. 126; e.g., 7.33, 0.0975 or 1000.12345).
C++ provides types float
(p. 126) and double
(p. 126) to store floating-point numbers in memory.
double
variables can typically store numbers with larger magnitude and finer detail (i.e., more precision; p. 126).
C++ also supports type long double
(p. 126) for floating-point values with larger magnitude and more precision than double
.
The static_cast
operator (p. 129) converts a temporary copy of its operand in parentheses to the type in angle brackets (double
). Using a cast operator in this manner is called explicit conversion (p. 129).
For arithmeitc, the compiler knows how to evaluate only expressions in which the operand types are identical. To ensure this, the compiler performs an operation called promotion (p. 129; also called implicit conversion) on selected operands.
In an expression containing values of data types int
and double
, C++ promotes (p. 129) int
operands to double
values.
static_cast
is a unary operator (p. 129)—it has only one operand.
C++ also supports unary versions of the plus (+
) and minus (-
) operators, so that you can write such expressions as -7
or +5
.
setprecision (p. 130) is a parameterized stream manipulator (p. 130) that specifies the number of digits of precision to the right of the decimal point when a floating-point number is output.
Programs that use parameterized stream manipulators must include the header <iomanip>
(p. 130).
The manipulator endl
(from the header <iostream>
) is a nonparameterized stream manipulator (p. 130), because it does not require an argument.
By default, floating-point values are output with six digits of precision.
The stream manipulator fixed
(p. 130) indicates that floating-point values should be output in fixed-point format (p. 130), as opposed to scientific notation (p. 130).
Scientific notation displays a floating-point number between 1.0 and 10.0, multiplied by a power of 10. Scientific notation is useful when displaying very large or very small values.
Fixed-point formatting forces the decimal point and trailing zeros to print, even if the value is a whole number amount.
When the stream manipulators fixed
and setprecision
are used in a program, the printed value is rounded (p. 130) to the number of decimal positions indicated by setprecision
’s argument, although the value in memory remains unaltered.
It’s also possible to force a decimal point to appear by using stream manipulator showpoint
(p. 130). If showpoint
is specified without fixed
, then trailing zeros will not print.
Nonparameterized stream manipulators fixed
and showpoint
require the header <iostream>
.
For fundamental-type variables, list-initialization syntax prevents narrowing conversions (p. 136) that could result in data loss.
The compound assignment operators +=
, -=
, *=
, /=
and %=
(p. 136) abbreviate assignment expressions.
The increment (p. 137; ++
) and decrement (p. 137; --
) operators increment or decrement a variable by 1, respectively. If the operator is prefixed to the variable, the variable is incremented or decremented by 1 first, then its new value is used in the expression in which it appears. If the operator is postfixed to the variable, the variable is first used in the expression in which it appears, then the variable’s value is incremented or decremented by 1.
All variables must have a type.
The fundamental types are not guaranteed to be identical from computer to computer. An int
on one machine might be represented by 16 bits (2 bytes) of memory, on a second machine by 32 bits (4 bytes), and on another machine by 64 bits (8 bytes).