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:
- initialization
- condition
- 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
breakcontinuereturn
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>
| Exception | Meaning |
|---|---|
runtime_error | Error detectable only at run time |
range_error | Result outside meaningful range |
overflow_error | Arithmetic overflow |
underflow_error | Arithmetic underflow |
logic_error | Error in program logic |
domain_error | Argument outside valid mathematical domain |
invalid_argument | Inappropriate argument |
length_error | Object would exceed maximum size |
out_of_range | Value 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
- Statements normally execute sequentially.
- Flow-of-control statements change the normal execution path.
- An expression followed by
;is an expression statement. - A null statement is a single
;. - An accidental semicolon after a loop or
ifcan change program behavior. - A block
{}groups multiple statements into one statement. - Names declared inside a block have block scope.
- Variables declared in a control statement have scope limited to that statement.
ifexecutes code based on a Boolean condition.elsebelongs to the nearest unmatchedif.- Braces should be used to make nested conditional structure clear.
switchselects among several integral values.caselabels must be integral constant expressions.breaknormally prevents fallthrough in aswitch.- Multiple
caselabels can intentionally share one body. defaulthandles unmatchedswitchvalues.- Use blocks when variables are declared inside individual
casesections. whilechecks its condition before each iteration.- A
whileloop can execute zero times. - Traditional
forcombines initialization, condition, and update. - The
forinitialization runs once. - The
forcondition runs before every iteration. - The
forexpression runs after every iteration. - Range
forprocesses each element of a sequence. - Use
auto&in a rangeforwhen elements must be modified. - Use
const auto&to read larger elements without copying. - Do not change a vector's size while iterating over it with a range
for. do whilealways executes its body at least once.- A
do whilestatement requires a semicolon after the condition. breakexits the nearest loop orswitch.continueskips the rest of the current loop iteration.gotoperforms an unconditional jump inside the same function.gotoshould generally be avoided.throwreports an exceptional run-time condition.- A
tryblock contains code that may throw. - A matching
catchhandles an exception. what()provides the exception's descriptive message.- Exceptions may propagate through several function calls.
- Local objects are destroyed as exception handling exits their scopes.
- An unhandled exception causes program termination.
- Standard exception classes are provided by
<exception>and<stdexcept>. runtime_error,invalid_argument, andout_of_rangeare common standard exceptions.- Exception-safe code keeps objects and resources valid when an exception occurs.