Statements define and control a program's work. This chapter describes the syntax and rules for statements. The syntax rules apply recursively, and any place that calls for a statement, you can substitute (almost) any of the statements in this chapter, especially a compound statement, which is how you can have a conditional or loop with a multi-statement body.
An expression statement is simply an expression followed by a semicolon. The expression is evaluated, and the result is discarded.
;
;
42; // valid but pointless cout << 42; // more typical x = y * z; // remember that assignment is an expression ; // null statement
A declaration can appear anywhere a statement appears, and certain statements permit additional declarations within the statement.
Declarations made in a substatement (of conditional and loop statements) are limited in scope to the substatement, even if the substatement is not a compound statement. For example, the following statement
while ( test() ) int x = init();
is equivalent to:
while ( test() ) { int x = init(); }
A simple declaration can appear anywhere a statement can be used. You can declare an object, a type, or a namespace alias. You can also use a using
declaration or using
directive. You can declare a function, but not define a function. You cannot define a namespace or declare a template.
In traditional C programming, declarations appear at the start of each block or compound statement. In C++ (and in the C99 standard), declarations can appear anywhere a statement can, which means you can declare variables close to where they are used. Example 5-1 shows examples of how declarations can be mixed with statements.
Example 5-1: Mixing declarations and statements.
#include <cctype> #include <iomanip> #include <iostream> #include <ostream> #include <string> // Count lines, words, and characters in the standard input. int main() { unsigned long num_lines, num_words, num_chars; num_lines = num_words = num_chars = 0; using namespace std; string line; while (getline(cin, line)) { ++num_lines; num_chars += line.length() + 1; bool in_word = false; for (size_t i = 0; char c = line[i]; ++i) if (isspace(static_cast<unsigned char>(c))) { if (in_word) ++num_words; in_word = false; } else if (! in_word) in_word = true; if (in_word) ++num_words; } cout << right << setw(10) << num_lines << setw(10) << num_words << setw(10) << num_chars << '\n'; }
Sometimes a construct can look like an expression statement or a declaration. These ambiguities are resolved in favor of declarations. Example 5-2 shows some declarations that look like they might be expressions.
Example 5-2: Declarations that seem like expressions.
class cls { public: cls(); cls(int x); }; int x; int* y = &x; int main() { // The following are unambiguously expressions: // constructing instances of cls. cls(int(x)); cls(*static_cast<int*>(y)); // Without the redundant casts, though, they would look // like declarations, not expressions. cls(x); // declares a variable x cls(*y); // declares a pointer y }
The for
, if
, switch
, and while
statements permit a declaration within the statement's condition. For example,
if (int x = test_this(a, b)) { cout << x; }
If the condition contains a declaration, the scope of the declared name extends to the end of the entire statement. In particular, a name declared in the condition of an if
statement is visible in the else
part of the statement. The name cannot be redeclared in the immediate substatement (but can be redeclared in a nested statement). The name is not visible in subsequent statements. For example,
if (int x = test_this(a, b)) { cout << x; } else { cout << -x; // same x as above double x; // error: redeclare x if (x < 0) float x; // valid: inner block } cout << x; // invalid: x not in scope
A for
loop permits two special declarations; both are in the same scope. See the description of the for
loop for details.
The syntax for a condition declaration is as follows:
type-specifiers declarator =
expression
See Chapter 3 for information about type specifiers and declarators.
A compound statement is a sequence of zero or more statements in curly braces.
{
statement ... }
{ }
The two conditional statements are if
statements and switch
statements.
An if
statement has one of the following forms:
if (
condition )
statementbool
, and if it is true
, the statement is executed. Otherwise, the statement is skipped and execution continued with the subsequent statement.if (
condition )
statement else
statementbool
, and if it is true
, the first statement is executed. Otherwise, the second statement is executed. Declarations in the first statement are not visible in the second. If if
statements are nested, the else
part binds with the closest if
statement. Example 5-3 shows nested if
statements.Example 5-3: Nesting if statements.
if (c1) if (c2) cout << "c1 and c2 are true\n"; else cout << "c1 is true, but c2 is false\n"; else if (c2) cout << "c1 is false, but c2 is true\n"; else cout << "c1 and c2 are false\n";
A switch
statement chooses one execution path from among many alternatives.
switch (
condition )
statementcase
labels in the statement. If a case
constant matches the condition, execution continues with the statement immediately after the case
label. If no case
matches the condition, execution continues after the default
label, if one is present. If there is no default
label, the switch's statement is skipped and execution continues with the subsequent statement.case
or default
label does not affect control flow. Use the break
statement (described later in this chapter) to exit from a switch
statement.switch
statements. The syntax for case
and default
labels is as follows.case
constant-expression :
statementdefault :
statementswitch
statement, all case
constants must have different values.default
in the switch
statement; it can appear anywhere in the statement. (The default
case does not have to be last, as in some languages.)case
and default
labels appear at the top level of the switch's substatement. They can appear in nested statements, but that makes the statement hard to read.Example 5-4: Switch statements.
enum color { black, red, green, yellow, blue, magenta, cyan, white }; color get_pixel(unsigned r, unsigned c) { ... } void demo() { using std::cout; int r = ... int c = ... switch (get_pixel(r, c)) { cout << "this is never executed, but it is valid\n"; case black: cout << "no color\n"; break; // Don't forget the break statements! case red: case green: case blue: cout << "primary\n"; break; // Omitting break is a common mistake. default: cout << "mixed\n"; switch (get_pixel(r+1, c+1)) case white: cout << " white\n"; // This case is private to the // inner switch statement. if (r > 0) // If the color is yellow, the switch branches // directly to here. For colors other than red, // green, blue, and black, execution jumps to the // default label and gets here if r > 0. case yellow: cout << " yellow or r > 0\n"; break; // A break after the last case is not necessary, // but a good idea in case you add a case later. } }
Two loop statements test at the top of the loop (for
and while
). One (do
) tests at the bottom, thereby ensuring the loop body will be executed at least once. This section describes the loop statements. See the next section for additional statements that affect or control loop execution.
The loop statements can declare variables in the scope of the loop substatement. Every time the loop iterates, it re-enters the substatement scope. That means objects that are declared in the substatement are created and destroyed every loop iteration.
A while
statement repeats a statement while a condition is true.
while (
condition )
statementbool
. If the value is true
, statement is executed, and the loop repeats. If the value is false
, the loop finishes and execution continues with the subsequent statement. Thus, if condition is false the first time it is evaluated, the statement is never executed.while
loop is typically used for unbounded iteration, that is, when you don't know beforehand how many times the loop will iterate. Examples 5-5 shows one common use of the while
loop.Example 5-5: Controlling I/O with a while loop.
#include <algorithm> #include <iostream> #include <iterator> #include <ostream> #include <string> #include <vector> // Sort lines of text. int main() { using namespace std; string line; vector<string> data; while (getline(cin, line)) data.push_back(line); sort(data.begin(), data.end()); copy(data.begin(), data.end(), ostream_iterator<string>(cout, "\n")); }
A for
loop is a generalization of the traditional counted loop that appears in most programming languages.
for (
init ;
condition ;
iterate-expr )
statementfor ( ; ; )
statementfor
statement can be an expression or a simple declaration. The init part offers more flexibility than a condition. Where a condition can declare only one name, the init part can declare multiple names. The syntax is as follows:specifier-list declarator-list
for
loop starts by executing the init part, if present. Then it evaluates the condition (just like a while
loop), and if the condition is true
, executes statement. Then it evaluates iterate-expr and evaluates condition again. When condition is false
, the loop finishes and execution continues with the subsequent statement. Thus, the init part is evaluated exactly once. The condition is evaluated at least once. The statement is executed the same number of times that iterate-expr is evaluated, which might be zero.continue
statement in a for
loop evaluates the iterate-expr and then tests the condition. A break
statement exits the loop without evaluating the iterate-expr.true
is used.for
loop is to count a bounded loop, although its flexibility makes it useful for unbounded loops, too, as you can see in Example 5-6.Example 5-6: Multiple uses of for loops.
// One way to implement the for_each standard algorithm. template<typename InIter, typename Function> Function for_each(InIter first, InIter last, Function f) { for ( ; first != last; ++first) f(*first); return f; } // One way to implement the generate_n standard algorithm. template<typename OutIter, typename Size, typename Generator> void generate_n(OutIter first, Size n, Generator gen) { for (Size i = 0; i < n; ++i, ++first) *first = gen; }
The do
statement is like a while
statement that tests at the end of the loop body.
do
statement while (
expression ) ;
bool
. If the value is true
, the statement is repeated and the expression is checked again. When the expression is false
, the loop finishes and execution continues with the subsequent statement.continue
statement jumps to the end of statement and tests expression.The control statements cause execution to change from its normal sequence. When execution leaves a scope, all automatic objects that were created in that scope are destroyed.
break;
break
statement can be used only in the body of a loop or switch
statement. It terminates the loop or switch
statement and transfers execution to the statement immediately following the loop or switch
.break
applies only to the innermost statement. To break out of multiple loops and switches, you must use a goto
statement, or redesign the block to avoid avoid nested loops and switches (say by factoring the inner statement into a separate function). Example 5-7 shows a simple use of break
.Example 5-7: Using break in the find_if algorithm.
// One way to implement the find_if standard algorithm. template<typename InIter, typename Predicate> InIter find_if(InIter first, InIter last, Predicate pred) { for ( ; first != last; ++first) if (pred(*first)) break; return first; }
continue;
continue
statement can be used only in the body of a loop. It causes the loop to skip the remainder of its body and immediately retest its condition prior to iterating again (if the condition is true
). In a for
loop, the iterate-expr is evaluated before testing the condition.goto
identifier ;
goto
statement transfers control to the statement in the current function that has identifier as a label. Jumping over a declaration is allowed only if the declared object is not initialized (that is, it must have POD type and not have an initializer; see Chapter 7 for information about POD types).return ;
return
expr ;
return
statement transfers execution out of a function to the caller. The first form does not return a value, so it should be used only in functions of type void
, constructors, and destructors. The latter form can be used only in functions that return values and not in constructors and destructors.return
statement, an implicit return;
is assumed. If the function has a non-void
return type, the behavior is undefined.main
function is special. If it ends without a return
statement, return
0;
is assumed. :
statementgoto
statement. Label identifiers must be unique within a function; the scope of a label is the function where it is declared. Label identifiers do not conflict with any other identifiers; they have a separate namespace.case
and default
labels.The try
statement catches exceptions. See also Chapter 6 for information about try
function bodies. See Chapter 4 for information about throwing exceptions.
try
compound-statement handlers...try
requires a compound statement, after which is a list of one or more handlers. The compound-statement is executed, and if an exception is raised, the handlers are tested to see if any handler can handle the exception. If so, the handler's statement is executed. Otherwise, control passes to the try
statement that was entered most recently. If no more try
statements were entered, terminate()
is called. When a handler is found, all automatic and temporary objects that were created in the try
statement are destroyed. If any functions were called, those functions are terminated, and automatic and temporary objects are destroyed.catch
clauses, where each catch clause has the following syntax:catch (
type declarator )
compound-statementcatch (...)
compound-statementcatch
handler with a matching type. The compound-statement is executed, and when it finishes, execution continues with the statement that follows the last catch
clause of the try
statement (if no other exceptions are thrown). If a plain throw
is evaluated (with no argument), the current exception object is rethrown; otherwise the exception object is destroyed when the handler finishes.&
(not considering cv qualifiers), the handler matches.void*
, the handler matches.catch
handler matches all exceptions. Handlers are examined in order, so catch (...)
should always be last.try
statements.Example 5-8: Throwing and catching exceptions.
#include <cstdlib> #include <fstream> #include <iostream> #include <numeric> #include <ostream> #include <sstream> #include <stdexcept> #include <string> #include <vector> class bad_data : public std::out_of_range { public: bad_data(int value, int min, int max) : std::out_of_range(make_what(value, min, max)) {} private: std::string make_what(int value, int min, int max); }; std::string bad_data::make_what(int value, int min, int max) { std::ostringstream out; out << "Invalid datum, " << value << ", must be in [" << min << ", " << max << "]"; return out.str(); } // Read a set of numbers from an input stream. // Verify that all data are within the defined boundaries. // Throw bad_data if any datum is invalid. If an exception // is thrown, tmp's destructor is automatically called // (if it has a destructor). template<typename T, typename charT, typename traits> void getdata(std::basic_istream<charT,traits>& in, std::vector<T>& data, T min, T max) { T tmp; while (in >> tmp) { if (tmp < min) throw bad_data(tmp, min, max); else if (tmp > max) throw bad_data(tmp, min, max); else data.push_back(tmp); } } // arbitrary precision integer class bigint { public: bigint(); ~bigint(); ... }; int main(int argc, char** argv) { using namespace std; if (argc < 2) { cerr << "usage: " << argv[0] << " FILE\n"; return EXIT_FAILURE; } vector<bigint> data; ifstream in(argv[1]); if (! in) { perror(argv[1]); return 2; } try { getdata(in, data, 0, 100); } catch (const bad_data& ex) { cerr << argv[1] << ": " << ex.what() << '\n'; return 3; } if (data.size() == 0) cout << "no data\n"; else { bigint sum(accumulate(data.begin(),data.end(),bigint()); std::cout << "avg=" << sum / data.size() << '\n'; } }