- Chapter 5. Statements
- 5.2 Statement Scope
- 5.3 Conditional Statements
- 5.4 Iterative Statements
- 5.5 Jump Statements
- 5.6
try
Blocks and Exception Handling - Chapter Summary
- Defined Terms
Chapter 5. Statements
Like most languages, C++ provides statements for conditional execution, loops that repeatedly execute the same body of code, and jump statements that interrupt the flow of control.
5.1 Simple Statements
Null Statements
The simplest statement is the empty statement, also known as a null statement, which is a single semicolon:
; // null statement
For example,
// read until we hit end-of-file or find an input equal to sought
while (cin >> s && s != sought)
; // null statement
// do something with s
Best Practice: Null statements should be commented. That way anyone reading the code can see that the statement was omitted intentionally.
Beware of Missing or Extraneous Semicolons
ival = v1 + v2;; // ok: second semicolon is a superfluous null statement
Although an unnecessary null statement is often harmless, an extra semicolon following the condition in a while or if can drastically alter the programmer’s intent. For example, the following code will loop indefinitely:
// disaster: extra semicolon: loop body is this null statement
while (iter != svec.end()) ; // the while body is the empty statement
++iter; // increment is not part of the loop
Extraneous null statements are not always harmless.
WARNING: Extraneous null statements are not always harmless.
Compound Statements (Blocks)
A compound statement, usually referred to as a block, is a (possibly empty) sequence of statements and declarations surrounded by a pair of curly braces. A block is a scope (§ 2.2.4, p. 48). Names introduced inside a block are accessible only in that block and in blocks nested inside that block.
By enclosing the statements in curly braces, we made them into a single (compound) statement.
We can also define an empty block which is equivalent to a null statement:
while (cin >> s && s != sought)
{ } // empty block
5.2 Statement Scope
We can define variables inside the control structure of the if
, switch
, while
, and for
statements. Variables defined in the control structure are visible only within that statement and are out of scope after the statement ends:
while (int i = get_num()) // i is created and initialized on each iteration
cout << i << endl;
i = 0; // error: i is not accessible outside the loop
If we need access to the control variable, then that variable must be defined outside the statement:
// find the first negative element
auto beg = v.begin();
while (beg != v.end() && *beg >= 0)
++beg;
if (beg == v.end())
// we know that all elements in v are greater than or equal to zero
5.3 Conditional Statements
C++ provides two statements, if
and switch
, that allow for conditional execution.
5.3.1 The if
Statement
The syntactic form of the simple if
without an else
branch is
if (condition)
statement
An if else
statement has the form
if (condition)
statement
else
statement2
Using an if else
Statement
Nested if
Statements
Watch Your Braces
To avoid the problems caused by missing braces, some coding styles recommend always using braces after an if
or an else
(and also around the bodies of while
and for
statements). Doing so avoids any possible confusion. It also means that the braces are already in place if later modifications of the code require adding statements.
Dangling else
Controlling the Execution Path with Braces
5.3.2 The switch
Statement
The case
keyword and its associated value together are known as the case
label. case
labels must be integral constant expressions (§ 2.4.4, p. 65). It is an error for any two case
labels to have the same value.
// counting vewels
// initialize counters for each vowel
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
char ch;
while (cin >> ch) {
// if ch is a vowel, increment the appropriate counter
switch (ch) {
case 'a':
++aCnt;
break;
case 'e':
++eCnt;
break;
case 'i':
++iCnt;
break;
case 'o':
++oCnt;
break;
case 'u':
++uCnt;
break;
}
}
Control Flow within a switch
After a case
label is matched, execution starts at that label and continues across all the remaining case
s or until the program explicitly interrupts it. To avoid executing code for subsequent case
s, we must explicitly add a break
to tell the compiler to stop execution.
There are also situations that we omit a break
statement to allow the program to fall through multiple case labels.
unsigned vowelCnt = 0;
// ...
switch (ch)
{
// any occurrence of a, e, i, o, or u increments vowelCnt
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
++vowelCnt;
break;
}
Because C++ programs are free-form, case labels need not appear on a new line.
switch (ch)
{
// alternative legal syntax
case 'a': case 'e': case 'i': case 'o': case 'u':
++vowelCnt;
break;
}
Forgetting a break
Is a Common Source of Bugs
Best Practice: Although it is not necessary to include a break
after the last label of a switch
, the safest course is to provide one. That way, if an additional case is added later, the break
is already in place.
The default
Label
The statements following the default
label are executed when no case
label matches the value of the switch
expression.
// if ch is a vowel, increment the appropriate counter
switch (ch) {
case 'a': case 'e': case 'i': case 'o': case 'u':
++vowelCnt;
break;
default:
++otherCnt;
break;
}
}
Best Practice: It can be useful to define a default
label even if there is no work for the default
case. Defining an empty default
section indicates to subsequent readers that the case was considered.
Variable Definitions inside the Body of a switch
It is illegal to jump from a place where a variable with an initializer is out of scope to a place where that variable is in scope:
case true:
// this switch statement is illegal because these initializations might be bypassed
string file_name; // error: control bypasses an implicitly initialized variable
int ival = 0; // error: control bypasses an explicitly initialized variable
int jval; // ok: because jval is not initialized
break;
case false:
// ok: jval is in scope but is uninitialized
jval = next_num(); // ok: assign a value to jval
if (file_name.empty()) // file_name is in scope but wasn't initialized
// ...
5.4 Iterative Statements
C++ provides three iterative statements (commonly called loops): while
, for
and do while
.
5.4.1 The while Statement
while (condition)
statement
Note: Variables defined in a while
condition or while
body are created and destroyed on each iteration.
Using a while
Loop
5.4.2 Traditional for Statement
The syntactic form of the for
statement is:
for (init-statement condition; expression)
statement
or equivalently
for (initializer; condition; expression)
statement
The for
and the part inside the parentheses is often referred to as the for
header.
Execution Flow in a Traditional for
Loop
Executes until the condition is false
:
init-statement -> condition is true
-> statement (for
body) -> expression -> condition is true
-> statement -> expression -> … -> condition is false
.
Multiple Definitions in the for
Header
init-statement can define several objects, but with only a single declaration statement. Therefore, all the variables must have the same base type (§ 2.3, p. 50).
// remember the size of v and stop when we get to the original last element
for (decltype(v.size()) i = 0, sz = v.size(); i != sz; ++i)
v.push_back(v[i]);
In this loop we define both the index, i
, and the loop control, sz
, in init-statement.
Omitting Parts of the for
Header
A for
header can omit any (or all) of init-statement, condition, or expression.
We can use a null statement for init-statement when an initialization is unnecessary. For example, we might rewrite the loop that looked for the first negative number in a vector
so that it uses a for
:
auto beg = v.begin();
for ( /* null */; beg != v.end() && *beg >= 0; ++beg)
; // no work to do
Omitting condition is equivalent to writing true as the condition, so the for
body must contain a statement that exits the loop.
for (int i = 0; /* no condition */ ; ++i) {
// process i; code inside the loop must stop the iteration!
}
We can also omit expression from the for header. In such loops, either the condition or the body must do something to advance the iteration. As an example, we’ll rewrite the while
loop that read input into a vector
of int
s, and we let the condition change the value of i
:
vector<int> v;
for (int i; cin >> i; /* no expression */ )
v.push_back(i);
5.4.3 Range for
Statement
The new standard introduced the range for
statement that can be used to iterate through the elements of a container or other sequence.
for (declaration : expression)
statement
The expression must represent a sequence, such as a braced initializer list (§ 3.3.1, p. 98), an array (§ 3.5, p. 113), or an object of a type such as vector or string that has begin and end members that return iterators (§ 3.4, p. 106).
The declaration defines a variable. It must be possible to convert each element of the sequence to the variable’s type (§ 4.11, p. 159). The easiest way to ensure that the types match is to use the auto
type specifier (§ 2.5.2, p. 68).
An example that doubles the value of each element in a vector
:
vector<int> v = {0,1,2,3,4,5,6,7,8,9};
// range variable must be a reference so we can write to the elements
for (auto &r : v) // for each element in v
r *= 2; // double the value of each element in v
is equivalent to
for (auto beg = v.begin(), end = v.end(); beg != end; ++beg) {
auto &r = *beg; // r must be a reference so we can change the element
r *= 2; // double the value of each element in v
}
Now that we know how a range for works, we can understand why we said in § 3.3.2 (p. 101) that we cannot use a range for
to add elements to a vector
(or other container). In a range for, the value of end()
is cached. If we add elements to (or remove them from) the sequence, the value of end
might be invalidated
(§ 3.4.1, p. 110). We’ll have more to say about these matters in § 9.3.6 (p. 353).
5.4.4 The do while
Statement
The condition is tested after the loop body completes. The loop body is executed at least once.
do
statement
while (condition);
5.5 Jump Statements
Jump statements interrupt the flow of execution. C++ offers four jumps: break
, continue
, goto
, and return
.
5.5.1 The break
Statement
A break
can appear only within an iteration statement or switch
statement.
5.5.2 The continue
Statement
A continue
can appear only inside an iterative statement.
5.5.3 The goto
Statement
goto label;
where label
is an identifier that identifies a labeled statement.
label: ... // labeled statement; may be the target of a goto
5.6 try
Blocks and Exception Handling
Exceptions are run-time anomalies.
In C++, exception handling involves throw expressions, try blocks and a set of exception classes.
5.6.1 A throw
Expression
The detecting part of a program uses a throw
expression to raise an exception.
throw runtime_error("Exception message")
The type runtime_error
is one of the standard library exception types and is defined in the stdexcept
header. We must initialize a runtime_error
by giving it a string
or a C-style character string (§ 3.5.4, p. 122).
5.6.2 The try
Block
try {
program-statements
} catch (exception-declaration) {
handler-statements
} catch (exception-declaration) {
handler-statements
} // ...
Writing a Handler
try {
// ...
} catch (runtime_error err) {
cout << err.what()
// ...
}
err
has type runtime_error
and what
is a member function (§ 1.5.2, p. 23) of the runtime_error
class. Each of the library exception classes defines a member function named what
. These functions take no arguments and return a C-style character string (i.e., a const char*
). The what
member of runtime_error
returns a copy of the string
(i.e. the "Exception message"
) used to initialize the particular object.
Functions Are Exited during the Search for a Handler
If a program has no try
blocks or no appropriate catch
is found, a library function named terminate
is called and the the program is aborted.
5.6.3 Standard Exceptions
The C++ library defines several classes that it uses to report problems encountered in the functions in the standard library. These classes are defined in four headers:
- The
exception
header defines the most general kind of exception class namedexception
. It communicates only that an exception occurred but provides no additional information. - The
stdexcept
header defines several general-purpose exception classes, which are listed in Table 5.1. - The
new
header defines thebad_alloc
exception type, which we cover in § 12.1.2 (p. 458). - The
type_info
header defines thebad_cast
exception type, which we cover in § 19.2 (p. 825).
The exception types define only a single operation named what
.
Chapter Summary
C++ provides a limited number of statements. Most of these affect the flow of control within a program:
while
,for
, anddo while
statements, which provide iterative execution.if
andswitch
, which provide conditional execution.continue
, which stops the current iteration of a loop.break
, which exits a loop or switch statement.goto
, which transfers control to a labeled statement.try
andcatch
, which define atry
block enclosing a sequence of statements that might throw an exception. Thecatch
clause(s) are intended to handle the exception(s) that the enclosed code might throw.throw
expression statements, which exit a block of code, transferring control to an associated catch clause.return
, which stops execution of a function.
In addition, there are expression statements and declaration statements. An expression statement causes the subject expression to be evaluated. Declarations and definitions of variables were described in Chapter 2.
Defined Terms
block Sequence of zero or more statements enclosed in curly braces.
compound statement Synonym for block.
exception safe Term used to describe programs that behave correctly when exceptions are thrown.
flow of control Execution path through a program.
raise Often used as a synonym for throw. C++ programmers speak of “throwing” or “raising” an exception interchangeably.
References
Lippman, Stanley B., Josée Lajoie, and Barbara E. Moo. C++ Primer. Addison-Wesley Professional, 2012.