본문으로 건너뛰기

Statements

Statement: A unit of execution in a C++ program.

Sequential Execution: Executes statements in order.

Flow-of-Control Statement: Changes the normal sequential execution path.

int a = 10;
int b = 20;

int sum = a + b;

std::cout << sum << '\n';

Normally, each statement executes in sequence.

Control statements such as if, for, and while change that execution path.


5.1 Simple Statements

Expression Statements

Expression Statement: An expression followed by a semicolon.

The expression is evaluated and its result is discarded.

value + 5;

The calculation occurs, but its result is unused.

Expression statements are more useful when the expression has a side effect.

value = 10;

std::cout << value << '\n';

++value;

Side Effect: A change caused by evaluating an expression.

Common side effects include:

  • changing a variable
  • reading input
  • writing output
  • modifying an object

Null Statements

Null Statement: An empty statement consisting only of a semicolon.

;

A null statement can be used where C++ requires a statement but no action is needed.

std::string input;
std::string sought = "stop";

while (std::cin >> input && input != sought)
;

The loop performs all of its work in the condition.

The body is intentionally empty.

When using a null statement intentionally, a comment improves readability.

while (std::cin >> input && input != sought)
; // intentionally empty

Beware of Extraneous Semicolons

An accidental semicolon can completely change a control statement.

Wrong:

while (value < 10);
{
++value;
}

The actual loop is:

while (value < 10)
;

The block is not part of the loop.

If value < 10 remains true, the program loops forever.

Correct:

while (value < 10)
{
++value;
}

Compound Statements

Compound Statement: A sequence of statements and declarations enclosed in {}.

Also called a block.

{
int value = 10;

++value;

std::cout << value << '\n';
}

A block acts as a single statement.

Therefore it can be used as the body of an if or loop.

if (value > 0)
{
std::cout << "positive\n";
--value;
}

Without the braces, only one statement belongs to the if.


Block Scope

Names declared inside a block exist only within that block and nested blocks.

{
int value = 10;

std::cout << value;
}

// value is not visible here

An empty block is also valid.

while (condition)
{
}

Unlike many individual statements, a block itself is not followed by a semicolon.


5.2 Statement Scope

Variables declared in the control part of an if, switch, while, or for have scope limited to that statement.

if (int value = get_value())
{
std::cout << value << '\n';
}

value can be used inside the if.

It cannot be used afterward.

// Error:
// std::cout << value;

If the value must remain available after the statement, define it beforehand.

int value = get_value();

if (value)
{
std::cout << value << '\n';
}

std::cout << value << '\n';

for Scope

A variable declared in a for header belongs to the loop's scope.

for (int i = 0; i < 10; ++i)
{
std::cout << i << '\n';
}

// i is no longer visible

5.3 Conditional Statements

Conditional statements choose which code to execute.

C++ provides two major forms:

if
switch

Use if for general Boolean conditions.

Use switch when choosing among several values of one integral expression.


1) The if Statement

if Statement: Executes a statement when a condition evaluates to true.

Basic form:

if (condition)
statement;

Example:

int value = 10;

if (value > 0)
{
std::cout << "positive\n";
}

The condition must be convertible to bool.


if else

An else provides an alternative execution path.

int value = -5;

if (value >= 0)
{
std::cout << "nonnegative\n";
}
else
{
std::cout << "negative\n";
}

Exactly one branch executes.


Multiple Conditions

Multiple cases can be represented with else if.

int score = 85;

if (score >= 90)
{
std::cout << "A\n";
}
else if (score >= 80)
{
std::cout << "B\n";
}
else if (score >= 70)
{
std::cout << "C\n";
}
else
{
std::cout << "F\n";
}

The conditions are tested from top to bottom.

Once a true condition is found, the remaining branches are skipped.


Nested if

An if can contain another if.

if (value >= 0)
{
if (value == 0)
{
std::cout << "zero\n";
}
else
{
std::cout << "positive\n";
}
}

Watch Your Braces

Without braces, an if controls only the next statement.

if (value > 0)
std::cout << "positive\n";

std::cout << "done\n";

"done" is always printed.

To control both statements:

if (value > 0)
{
std::cout << "positive\n";
std::cout << "done\n";
}

Using braces consistently reduces mistakes when code is later modified.


Dangling else

An else is matched with the closest preceding unmatched if.

if (a)
if (b)
std::cout << "A\n";
else
std::cout << "B\n";

The else belongs to:

if (b)

not:

if (a)

Use braces when the intended structure may be unclear.

if (a)
{
if (b)
{
std::cout << "A\n";
}
}
else
{
std::cout << "B\n";
}

2) The switch Statement

switch Statement: Chooses among several execution paths based on the value of an integral expression.

char grade = 'B';

switch (grade)
{
case 'A':
std::cout << "excellent\n";
break;

case 'B':
std::cout << "good\n";
break;

case 'C':
std::cout << "pass\n";
break;

default:
std::cout << "other\n";
break;
}

The value of grade is compared against each case label.


case Labels

A case label must use an integral constant expression.

constexpr int start = 1;

int command = 1;

switch (command)
{
case start:
std::cout << "start\n";
break;
}

Two case labels in the same switch cannot have the same value.

Invalid:

switch (value)
{
case 1:
break;

// Error: duplicate value
// case 1:
// break;
}

break

break normally exits the switch.

switch (command)
{
case 1:
std::cout << "open\n";
break;

case 2:
std::cout << "close\n";
break;
}

After break, execution continues after the switch.


Fallthrough

If a case does not end with break, execution continues into the next case.

int value = 1;

switch (value)
{
case 1:
std::cout << "one\n";

case 2:
std::cout << "two\n";
break;
}

Output:

one
two

Execution begins at case 1 and continues until break.

This behavior is called fallthrough.


Stacked case Labels

Fallthrough can intentionally allow several values to share one action.

char vowel = 'e';

switch (vowel)
{
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
std::cout << "vowel\n";
break;

default:
std::cout << "not vowel\n";
break;
}

All five cases share the same statements.


The default Label

default executes when no case value matches.

switch (command)
{
case 1:
std::cout << "start\n";
break;

case 2:
std::cout << "stop\n";
break;

default:
std::cout << "unknown command\n";
break;
}

Although default is not always required, it is useful for handling unexpected values.


Variables inside switch

Control cannot jump across the initialization of a variable that would remain in scope.

Avoid:

switch (command)
{
case 1:
// int value = 10;

case 2:
// value would be in scope here even though
// its initialization might have been skipped
break;
}

Use a block to limit the variable's scope.

switch (command)
{
case 1:
{
int value = 10;

std::cout << value << '\n';

break;
}

case 2:
{
int value = 20;

std::cout << value << '\n';

break;
}
}

Each variable belongs only to its own case block.


5.4 Iterative Statements

Iterative Statement: Repeats a statement or block.

C++ provides:

while
for
range for
do while

while and traditional for test before executing the body.

do while tests after executing the body.


1) The while Statement

while Statement: Repeats its body while the condition is true.

Syntax:

while (condition)
{
statements;
}

Example:

int value = 0;

while (value < 5)
{
std::cout << value << '\n';

++value;
}

Output:

0
1
2
3
4

Zero Iterations

The condition is tested before the body.

int value = 10;

while (value < 5)
{
std::cout << value;
}

The body does not execute.


Unknown Number of Iterations

while is useful when the number of iterations is not known beforehand.

int value = 0;

while (std::cin >> value)
{
std::cout << value << '\n';
}

The loop continues until input fails.


Loop Control Variable

A variable defined before the loop remains available afterward.

int value = 0;

while (value < 10)
{
++value;
}

std::cout << value;

Output:

10

2) Traditional for Statement

A traditional for loop combines:

  1. initialization
  2. condition
  3. iteration expression

Syntax:

for (init-statement; condition; expression)
{
statement;
}

Example:

for (int i = 0; i < 5; ++i)
{
std::cout << i << '\n';
}

Execution Order

For:

for (int i = 0; i < 3; ++i)
{
std::cout << i << '\n';
}

the execution order is:

1. int i = 0
2. test i < 3
3. execute body
4. execute ++i
5. repeat from step 2

The initialization runs only once.

The condition is tested before every iteration.

The expression executes after each iteration.


Equivalent while

This:

for (int i = 0; i < 5; ++i)
{
std::cout << i << '\n';
}

is conceptually similar to:

int i = 0;

while (i < 5)
{
std::cout << i << '\n';

++i;
}

The for form is convenient when initialization, condition, and update all relate to one loop-control variable.


Multiple Definitions

The initialization can define multiple variables of the same base type.

for (int i = 0, j = 10;
i < j;
++i, --j)
{
std::cout << i << ' ' << j << '\n';
}

Omitting the Initialization

A loop-control variable may already exist.

int i = 0;

for (; i < 5; ++i)
{
std::cout << i << '\n';
}

Omitting the Condition

An omitted condition is treated as true.

for (;;)
{
// infinite loop
}

Such a loop must normally be terminated by something such as break or return.

for (;;)
{
int value = 0;

if (!(std::cin >> value))
{
break;
}
}

Omitting the Expression

The update may instead occur inside the loop body.

int i = 0;

for (; i < 5;)
{
std::cout << i << '\n';

++i;
}

3) Range for Statement

Range for Statement: Processes every element in a sequence.

Syntax:

for (declaration : expression)
{
statement;
}

Example:

std::vector<int> values{10, 20, 30};

for (auto value : values)
{
std::cout << value << '\n';
}

Each iteration initializes value from the next vector element.


Using auto

auto is commonly used so the compiler determines the element type.

std::string text = "hello";

for (auto c : text)
{
std::cout << c << '\n';
}

Modifying Elements

Without a reference, the loop variable is a copy.

std::vector<int> values{1, 2, 3};

for (auto value : values)
{
value *= 2;
}

The vector is unchanged.

To modify the actual elements:

for (auto& value : values)
{
value *= 2;
}

Now:

values = {2, 4, 6}

Read-Only References

For larger objects, a const reference avoids copying.

std::vector<std::string> words{
"hello",
"world"
};

for (const auto& word : words)
{
std::cout << word << '\n';
}

The elements can be read but not modified through word.


Do Not Change Container Size

A range for establishes its iteration range when the loop begins.

Changing the size of a vector during iteration can invalidate the loop's iterators.

Avoid:

std::vector<int> values{1, 2, 3};

for (auto value : values)
{
// Do not do this:
// values.push_back(value);
}

4) The do while Statement

do while Statement: Executes the body first and checks the condition afterward.

Syntax:

do
{
statement;
}
while (condition);

The final semicolon is required.


At Least One Iteration

int value = 10;

do
{
std::cout << value << '\n';
}
while (value < 5);

Output:

10

Even though the condition is false, the body executes once.


Typical Input Validation

int value = 0;

do
{
std::cout << "Enter a positive number: ";

std::cin >> value;
}
while (value <= 0);

The program always asks at least once.


Condition Variable

Variables used by the condition generally need to exist outside the do body.

int value = 0;

do
{
std::cin >> value;
}
while (value != 0);

A do while condition cannot define a variable in the way some other control conditions can.


5.5 Jump Statements

Jump statements interrupt normal control flow.

Important jump statements include:

break
continue
goto
return

return is discussed with functions.


1) The break Statement

break: Terminates the nearest enclosing loop or switch.

for (int i = 0; i < 10; ++i)
{
if (i == 5)
{
break;
}

std::cout << i << '\n';
}

Output:

0
1
2
3
4

When i == 5, the loop immediately ends.


break with Input

std::string word;

while (std::cin >> word)
{
if (word == "quit")
{
break;
}

std::cout << word << '\n';
}

The word "quit" terminates the loop.


Nearest Enclosing Loop

In nested loops, break affects only the nearest loop.

for (int row = 0; row < 3; ++row)
{
for (int column = 0; column < 3; ++column)
{
if (column == 1)
{
break;
}

std::cout << row << ' '
<< column << '\n';
}
}

Only the inner loop is terminated.

The outer loop continues.


2) The continue Statement

continue: Stops the current iteration and begins the next iteration of the nearest loop.

for (int i = 0; i < 10; ++i)
{
if (i % 2 == 0)
{
continue;
}

std::cout << i << '\n';
}

Output:

1
3
5
7
9

Even values skip the output statement.


continue in a while

int value = 0;

while (std::cin >> value)
{
if (value < 0)
{
continue;
}

std::cout << value << '\n';
}

Negative values are ignored.


break vs. continue

break:

leave the loop completely

continue:

skip the rest of this iteration
and continue looping

Example:

for (int i = 0; i < 10; ++i)
{
if (i == 3)
{
continue;
}

if (i == 7)
{
break;
}

std::cout << i << ' ';
}

Output:

0 1 2 4 5 6

3 is skipped.

At 7, the loop ends.


3) The goto Statement

goto: Transfers control unconditionally to a labeled statement in the same function.

Syntax:

goto label;

// ...

label:
statement;

Example:

int value = 0;

if (value == 0)
{
goto done;
}

std::cout << "processing\n";

done:
std::cout << "finished\n";

Initialization Restrictions

A goto cannot jump forward across the initialization of a variable that would then be in scope.

Invalid conceptually:

goto end;

// initialization would be skipped
int value = 10;

end:
// value would be in scope here

The language prevents such control flow.


Avoid goto

Most control flow is clearer using:

  • loops
  • functions
  • break
  • continue
  • return

Prefer:

while (condition)
{
if (finished)
{
break;
}
}

over constructing equivalent arbitrary jumps with goto.


5.6 try Blocks and Exception Handling

Exception: A run-time problem that interrupts normal execution.

Exception Handling: A mechanism for reporting a problem in one part of a program and handling it elsewhere.

The basic mechanism uses:

throw
try
catch

General flow:

problem detected

throw

search for matching catch

handle exception

1) A throw Expression

throw Expression: Signals that the current code cannot continue normally.

Syntax:

throw expression;

Example using std::runtime_error:

#include <stdexcept>

if (denominator == 0)
{
throw std::runtime_error(
"division by zero"
);
}

The exception object contains information about the problem.


Throwing Based on Program Data

#include <stdexcept>

int divide(int a, int b)
{
if (b == 0)
{
throw std::runtime_error(
"divisor must not be zero"
);
}

return a / b;
}

When b == 0, normal execution of the function stops.


Exception Type

The type of the thrown expression determines which handlers can catch it.

throw std::runtime_error("error");

throws an exception of type:

std::runtime_error

The header is:

#include <stdexcept>

2) The try Block

Code that may throw an exception can be placed inside a try block.

try
{
// code that may throw
}
catch (const std::runtime_error& error)
{
// handle the exception
}

Complete Example

#include <iostream>
#include <stdexcept>

int main()
{
int a = 0;
int b = 0;

std::cin >> a >> b;

try
{
if (b == 0)
{
throw std::runtime_error(
"division by zero"
);
}

std::cout << a / b << '\n';
}
catch (const std::runtime_error& error)
{
std::cerr
<< error.what()
<< '\n';
}
}

Input:

10 0

Possible output:

division by zero

catch Clause

A catch clause handles exceptions of a matching type.

catch (const std::runtime_error& error)
{
std::cerr << error.what() << '\n';
}

The exception is commonly caught by const reference.


what()

Standard exception objects provide the what() member function.

std::cerr << error.what();

what() returns:

const char*

containing a description of the error.


Multiple Handlers

A try block may have several handlers.

try
{
process();
}
catch (const std::invalid_argument& error)
{
std::cerr
<< "invalid argument: "
<< error.what()
<< '\n';
}
catch (const std::runtime_error& error)
{
std::cerr
<< "runtime error: "
<< error.what()
<< '\n';
}

The exception system searches for an appropriate handler.


try Scope

Variables defined inside the try block belong to that block.

try
{
int value = 10;

// value is visible here
}
catch (const std::runtime_error& error)
{
// value is not visible here
}

Define shared variables outside the try if the handler also needs them.


Searching for a Handler

An exception may be thrown inside a function called by another function.

void inner()
{
throw std::runtime_error("error");
}

void outer()
{
inner();
}

The caller can handle the exception.

try
{
outer();
}
catch (const std::runtime_error& error)
{
std::cerr << error.what();
}

Conceptually:

inner()
throws

inner exits

outer exits

matching catch found

This search proceeds outward through the function call chain.


Objects during Exception Handling

When functions are exited because of an exception, local automatic objects are destroyed as their scopes are left.

void process()
{
std::string text = "data";

throw std::runtime_error("error");
}

When process() exits because of the exception, text is destroyed automatically.

This behavior is important for resource-managing C++ objects.


No Matching Handler

If an exception propagates out of the program without finding an appropriate handler, the program terminates.

This is why exceptions intended to be recoverable must eventually be handled.


Exception Safety

Exception Safety: Program objects and resources remain in a valid state even when an exception interrupts normal execution.

Prefer objects that manage resources automatically.

For example:

std::vector<int> values;
std::string text;

Their resources are released automatically when scope is exited.

This idea becomes more important when classes and dynamic memory are studied.


3) Standard Exceptions

The standard library provides exception classes for common error categories.

<exception>

Defines the general exception type:

#include <exception>
std::exception

<stdexcept>

Provides commonly used exception types.

#include <stdexcept>
ExceptionMeaning
runtime_errorError detectable only at run time
range_errorResult outside meaningful range
overflow_errorArithmetic overflow
underflow_errorArithmetic underflow
logic_errorError in program logic
domain_errorArgument outside valid mathematical domain
invalid_argumentInappropriate argument
length_errorObject would exceed maximum size
out_of_rangeValue or index outside valid range

runtime_error

throw std::runtime_error(
"unable to process data"
);

Use when the problem is detected during execution.


invalid_argument

void set_age(int age)
{
if (age < 0)
{
throw std::invalid_argument(
"age must be nonnegative"
);
}
}

out_of_range

A common example is checked container access.

std::vector<int> values{1, 2, 3};

try
{
std::cout << values.at(10);
}
catch (const std::out_of_range& error)
{
std::cerr
<< error.what()
<< '\n';
}

Unlike:

values[10]

at() performs bounds checking and can throw std::out_of_range.


Other Standard Exceptions

<new> defines:

std::bad_alloc

which can report memory-allocation failure.

<typeinfo> defines:

std::bad_cast

which is related to failed run-time type conversions.

These become more relevant in later chapters.


Essential Study Checklist

  1. Statements normally execute sequentially.
  2. Flow-of-control statements change the normal execution path.
  3. An expression followed by ; is an expression statement.
  4. A null statement is a single ;.
  5. An accidental semicolon after a loop or if can change program behavior.
  6. A block {} groups multiple statements into one statement.
  7. Names declared inside a block have block scope.
  8. Variables declared in a control statement have scope limited to that statement.
  9. if executes code based on a Boolean condition.
  10. else belongs to the nearest unmatched if.
  11. Braces should be used to make nested conditional structure clear.
  12. switch selects among several integral values.
  13. case labels must be integral constant expressions.
  14. break normally prevents fallthrough in a switch.
  15. Multiple case labels can intentionally share one body.
  16. default handles unmatched switch values.
  17. Use blocks when variables are declared inside individual case sections.
  18. while checks its condition before each iteration.
  19. A while loop can execute zero times.
  20. Traditional for combines initialization, condition, and update.
  21. The for initialization runs once.
  22. The for condition runs before every iteration.
  23. The for expression runs after every iteration.
  24. Range for processes each element of a sequence.
  25. Use auto& in a range for when elements must be modified.
  26. Use const auto& to read larger elements without copying.
  27. Do not change a vector's size while iterating over it with a range for.
  28. do while always executes its body at least once.
  29. A do while statement requires a semicolon after the condition.
  30. break exits the nearest loop or switch.
  31. continue skips the rest of the current loop iteration.
  32. goto performs an unconditional jump inside the same function.
  33. goto should generally be avoided.
  34. throw reports an exceptional run-time condition.
  35. A try block contains code that may throw.
  36. A matching catch handles an exception.
  37. what() provides the exception's descriptive message.
  38. Exceptions may propagate through several function calls.
  39. Local objects are destroyed as exception handling exits their scopes.
  40. An unhandled exception causes program termination.
  41. Standard exception classes are provided by <exception> and <stdexcept>.
  42. runtime_error, invalid_argument, and out_of_range are common standard exceptions.
  43. Exception-safe code keeps objects and resources valid when an exception occurs.