Review of last time: while
and do-while
Review of last time (loops)
Loops are the first kind statement we look at that make use of
bool
values in determining the flow of control. To review:
while
loops repeatedly execute some code (the loop body, as long as a condition evaluates to true. The condition is evaluated before the loop body is run, so it’s possible that the loop body won’t run at all (if the condition is false from the start).Example:
int i = 0; while(i < 10) { cout << i << endl; i++; }
will print the numbers from 0 to 9.
do-while
loops repeatedly execute the loop body as long as a condition is true, however, the condition is tested after the loop body, so the loop body will always be run at least once.int i = 0; do { cout << i << endl; i++; } while(i < 10);
Skipping ahead and breaking out
There are two special statements that let us change the way any of the above loops normally work.
Consider the infinite-while loop
while(true) {
// Stuff
}
We can break out of this or any loop prematurely using the keyword (statement)
break
:
while(true) {
cout << "Hello ";
break;
cout << "Goodbye ";
}
This will print Hello
. The break
aborts the loop and jumps to the first
statement outside the loop body.
Usually, you use break
with a conditional statement (we’ll see those in a
bit) if there is some point in the body where you want to escape early.
Sometimes you want to skip the remainder of the loop body, and jump back up to
the top for the next repetition. continue
does this:
int i = 1;
while(i <= 5) {
cout << i;
continue;
cout << endl;
}
This will print 12345
; the line that prints the endl
will never run.
Instead, when we hit continue
, we jump back to the beginning of the loop,
exactly as if we had finished the body.
break
and continue
both give us more control over the flow of
a loop. break
stops a loop
in its tracks, exiting immediately. We can use it with an if
statement to
make an early escape from a loop:
int i = 0;
while(i < 100) {
cout << i;
if(i > 10)
break;
}
This example is kind of silly, as it would be better to just adjust the
condition on the loop to make it clear when it ends. break
is better used
when the loop might end unpredictably, based on some condition that develops
inside the loop.
break
is particularly useful for loops that allow for some kind of
early exit. For example, suppose we want the user to enter some integers, and
we’re going to print out the product. A first stab might look like this:
int x, prod = 1;
while(cin >> x) {
prod *= x;
}
cout << prod;
However, if you think about it a bit, you’ll realize that if the user ever enters 0, then it doesn’t matter what they enter after that, the product is always going to be 0. So we can break out of the loop as soon as the user enters a 0:
int x, prod = 1;
while(cin >> x) {
prod *= x;
if(x == 0)
break;
}
cout << prod;
Another use for break
is in while(true)
loops. Usually we don’t want a
loop that really runs forever, but the condition may be complicated, or
might need to be tested in the middle of the loop (as opposed to the
beginning – while
– or end – do-while
). In these cases we can use
a break
inside the loop, probably with an if
as above, to specify
exactly when the loop should end.
continue
skips everything after it in the body of the loop and just jumps
immediately to the next time through the loop. It’s normally used when
a loop has a lot of work to do inside its body, but some of that work can be
skipped some time:
while(condition) {
// Do some work
if(whatever)
continue; // Skip remaining work
// Remaining work
}
An easy example would be if the user entered 1 in our product-loop: 1 times anything is that thing itself, so we can skip the multiplication step:
int x, prod = 1;
while(cin >> x) {
if(x == 1)
continue;
prod *= x;
if(x == 0)
break;
}
cout << prod;
Loops, continued
The For loop
Consider the following while
loop:
int i = 0;
while(i < 10) {
cout << i;
++i;
}
A lot of loops follow this pattern: declare a variable, check a condition
(which often depends on the variable), run the loop body, and then finally
update the variable with a new value for the next time through the loop.
Because this pattern is so common, C++ has a special kind of loop just for
it: the for
loop. The structure of a for
loop looks like this:
for(initialization; condition; update)
body;
It is rearranged and run as if you wrote:
{
initialization;
while(condition) {
body;
update;
}
}
(Note that the semicolons between the initialization, condition, and update )
For example we can rewrite the previous while
loop into
for(int i = 0; i < 10; ++i)
cout << i;
Note that the initialization includes the declaration of the variable i. This kind of loop is incredibly common when we need to make a variable count up (or down) between a range of numbers. To count up from a to b we just write
for(int i = a; i <= b; ++i)
body;
(To count down, just change the increment to a decrement and swap the direction of the comparison.)
All of the while
loops we wrote for working with numbers can be converted to
for
loops, making the starting and ending points clearer as we do so:
// 0 to 10
for(int i = 0; i <= 10; ++i)
cout << i;
// 20 to 0, by 2s
for(int j = 20; j >= 0; j -= 2)
cout << j;
// 1 to 256, doubling each time
for(int i = 1; i <= 256; i *= 2)
cout << i;
// *Both* i and j counting, in opposite directions
for(int i = 1, j = 10; i <= 10; i++, j--)
cout << i << "," << j << endl;
Even number-like loops can be converted:
for(char c = 'a'; c <= 'z'; c++)
cout << c;
But we can do more than work with numbers. E.g., consider reading from the user:
int sum = 0
for(int x; cin >> x; /* no update! */ )
sum += x;
Here, the condition is doing double-duty, it serves as both the condition and the update. The update itself is empty. You can, in fact, leave all three parts of a for loop empty, giving a loop that never stops:
for(;;)
// loops forever
If you were to write
for(;condition;)
// body
then this would be exactly the same as a while
loop with the same condition.
Let’s look at some for
loops and you tell me what they do:
// Prints 0 through 9, inclusive
for(int i = 0; i < 10; ++i)
cout << i << endl;
// Write a loop that prints out 1 4 9 16 25 36
// Prints 0, -1, -2, etc and never stops!
for(int i = 0; i < 10; --i)
cout << i << endl;
// Write a loop that prints out 0 1 2 0 1 2 0 1 2
// Prints nothing at all (because the condition immediately fails)
for(int i = 0; i > 0; ++i)
cout << i << endl;
// Write a loop that prints out 64, 32, 16, 8, 4, 2
// Prints 10, 9, ..., 1
for(int i = 10; i > 0; --i)
cout << i << endl;
// Write a loop that prints out ACEGIKMOQSUWY (every other letter)
// Prints out 0,2,4,...,12
for(int i = 0; i < 13; i += 2)
cout << i << endl;
// Write a loop that prints out
// *
// ***
// *****
// *******
// *********
// Prints out powers of 2: 1,2,4,8,...,128
for(int i = 1; i < 256; i *= 2)
cout << i << endl;
// Will print out
// #####
// ####
// ###
// ##
// #
// pop_back removes the last character in a string.
for(string s = "#####"; s.length() > 0; s.pop_back())
cout << s << endl;
// Prints abcdefg...xyz
for(char c = 'a'; c <= 'z'; ++c)
cout << c;
// Write a loop that prints out
// HELLO! HELLO! HELLO! HELLO! HELLO!
// Prints 8 spaces. This could be used to indent lines a specific number of
// spaces.
for(int i = 0; i < 8; ++i)
cout << " ";
string name = "Andy Clifton";
// Write a loop that prints out 'A' 'D' ' ' 'l' 'f' 'o' (every other letter)
// Start by writing a loop that prints every letter, and then modify it.
Plotting a graph with a for
loop.
Let’s plot a function using a for loop. If we do
#include <cmath>
it will bring in a bunch of math functions for our use, most importantly, the
trig. functions sin
, cos
, etc. Let’s plot the sin function. We’ll plot
it vertically, as in \(x = \cos(y)\), so that we can print successive
rows to the screen.
A single row will look something like this:
############################
0 is in the middle, -1 is at the far left (with no #s printed) and 1 is at the far right with 50 #s drawn. A good place to start would be with drawing a single row:
// draw a row
float x = ... ; // x is between -1 and 1
for(int i = 0; i < ???; i++)
cout << "#";
cout << endl;
What should the upper bound on i
be? It has to depend on x
, and it should
be 0 when \(x = -1\), and 51 when \(x = +1\). We need to remap the range
-1…1 to 0..51. An easy way to do this is to first map it to 0..1, and then
multiply by 51. To map -1…1 to 0..1, just add 1 and then divide by 2:
// draw a row
float x = ...;
x = (x + 1.0) / 2.0;
x *= 51;
for(int i = 0; i < x; i++)
cout << "#";
cout << endl;
In order to print an entire period of the sin function, we need a loop around this to print out a bunch of rows:
for(float y = 0; y < 2*3.14159; y += ???) {
// draw a row
float x = sin(y);
x = (x + 1.0) / 2.0;
x *= 51;
for(int i = 0; i < x; i++)
cout << "#";
cout << endl;
}
The only question is what do we increment y by? This determines – indirectly – the number of rows printed, but also the resolution (frequency of sample points) of our plot. We can just pick a value that seems good, so 0.1 should work:
for(float y = 0; y < 2*3.14159; y += 0.1) {
// draw a row
float x = sin(y);
x = (x + 1.0) / 2.0;
x *= 51;
for(int i = 0; i < x; i++)
cout << "#";
cout << endl;
}
Loops over strings
To build a loop that runs over the characters in a string, from beginning to end,
we’re going to start by building a loop over numbers, specifically the character
positions in the string. These positions run from 0 (the first character) to
.length() - 1
, so a loop for them looks like this:
string text = ...;
for(int i = 0; i < text.length(); ++i) {
...
}
For each character position, we can use .at
to look up the associated character:
string text = ...;
for(int i = 0; i < text.length(); ++i) {
char c = text.at(i);
// Do something with c
}
Note that you can also assign to .at
, because each character position is
its own box:
string text = ...;
for(int i = 0; i < text.length(); ++i) {
text.at(i) = // whatever
}
There are two general ways we can process a string:
We can modify the string “in-place”, changing each character into something else.
We can leave the original string untouched, and construct an entirely new string containing the modified characters.
(1) is fairly straightforward, we can use .at
(in both ways) to get and
change each character:
// Convert all 'a' to 'z'
for(int i = 0; i < text.length; ++i)
if(text.at(i) == 'a')
text.at(i) = 'z';
(2) is a little more interesting. The easiest way to think about this is to first write a loop that creates a copy of a string, without changing anything. After that, we can adjust it to change the characters. To copy a string, one character at a time, we need to create a new string, initially empty, and then add the characters from the original string to it, one at a time:
string orig = ...;
string copy; // Empty
for(int i = 0; i < orig.length(); ++i) {
char c = orig.at(i);
copy = copy + c; // Or is it the other way 'round?
}
Do we want to add the characters to the end of the copy, or two the
beginning (copy = orig.at(i) + copy;
)? Let’s try it both ways.
(Demonstrate)
We want to add it to the end. This should make sense, because we are reading the characters from start to finish in the original string, so adding them onto the end will put them back into the proper locations. (As a bonus, we also know how to reverse a string, should we ever need that!)
As it turns out, there’s a shortcut for adding a character to the end of a
string: copy.push_back(c)
:
string orig = ...;
string copy; // Empty
for(int i = 0; i < orig.length(); ++i) {
char c = orig.at(i);
// Modify c in some way...
copy.push_back(c);
}
Variable Scoping and Loops
Suppose we want to read in many lines of input from the user (using getline
)
and merge them all together into a single long string, containing all the text.
Will this work:
string line;
while(getline(cin,line)) {
string text;
text.append(line);
}
No, for two reasons:
First, the scope of
text
ends at the curly brace that closes the body of the loop. So we have no way of getting the accumulated text out of the loop.More critically, every time the loop body starts,
text
is created anew. Likewise, at the end of every loop body,text
‘s scope ends and it is destroyed. So we aren’t “accumulating” the input the user is giving us, we’re just throwing it away.
If we want to accumulate results inside a loop, or we want to use the results calculated inside some loop, outside it, after it’s done, we have to declare the variable(s) outside the loop, before it:
string line, text;
while(getline(cin,line)) {
text.append(line);
}
// Now text contains all the lines
The rules for variable scoping apply to loops with block bodies as follows:
For
while
loops, there are no special rules. Variables declared outside the while loop’s block body are still in scope inside, unless shadowed. Variables declared inside the body are not visible after it.For
do
-while
loops, there are no special rules, but there is one thing worth pointing out: variables declared inside the body are not in scope in thewhile
condition. In particular, this will not work:do { int i; cin >> i; } while(i < 0);
You will have to move the declaration of i above the loop.
For a
for
loop, you can declare a variable in the initialization, and that variable is in scope in the body, but not afterwards:for(int i = 0; i < 10; ++i) { cout << i; // i is still in scope here } // i is NOT in scope out here
In general, if you plan to use some variable inside the loop after the loop, then you should move that variable’s declaration outside the loop, just before it.
Parallel loops
Nothing says that our loops (of whatever kind) can only involve one variable. For example, we can do this:
for(int i = 0, j = 10; i < 10; i++, j--) {
cout << i << "," << j << endl;
}
This loop updates both i
and j
every time it loops. i
counts up, while
j
counts down.
Multi-dimensional loops
We’ve done some of this above, but let’s take a closer look at it.
Suppose we wanted to print out an m by n square of #
characters. That is,
if \(m = 5\) and \(n = 3\) we have
#####
#####
#####
Five columns and three rows. How can we do this? Let’s build a loop that will
print out m #
s in a row:
for(int x = 0; x < m; ++x)
cout << "#";
cout << endl;
(After the loop completes, we print an endl
, just to be polite.)
Now we need to repeat this n times. What do we use to repeat things? Another loop, surrounding the first one. This is called a nested loop:
for(int y = 0; y < n; ++y) {
for(int x = 0; x < m; ++x)
cout << "#";
cout << endl;
}
It’s worth examining this to see how many times the innermost loop body will be executed:
The inner loop runs its body m times.
The outer loop runs the inner loop n times.
So the inner loop’s body will be run \(m \times n\) times in total!
Nested loops are often used when we have 2- (or more) dimensional data.
Some examples:
All possible pairings of the numbers 1..6 with 1..4:
for(int i = 1; i <= 6; i++) for(int j = 1; j <= 6; j++) cout << i << "," << j << endl;
How many things will this print out?
All possible rolls of two dice, where the order does not matter. By this I mean that the roll (1,2) counts the same as (2,1), so we should only ever print one or the other, not both. How can we do this? Hint: we can do it without keeping track of all the rolls we’ve printed so far, by just making sure we print the rolls in some predictable order. That way, the question of “have we printed this roll before” is easy to answer.
The order we’ll choose is that the second roll grows quickly, while the first row grows slowly:
1,1 1,2 1,3 ... 1,6 2,2 2,3 ...
Another way to put this is that the first roll is always less than or equal to the second. Note that we can skip the 2,1 roll above, because the first roll is not less than or equal to the second: this tells us that we have already covered this roll!
for(int i = 1; i <= 6; i++) for(int j = 1; j <= 6; j++) if(i <= j) cout << i << "," << j << endl;
The way we do this is by “pre-sorting” the dice rolls, so that the low roll always comes first.
The signs of all eight octants in 3D space:
for(int x = -1; x <= 1; x += 2) for(int y = -1; y <= 1; y += 2) for(int z = -1; z <= 1; z += 2) cout << "(" << x << "," << y << "," << z << ")" << endl;
The range-based for
loop
The fourth kind of loop is the “range based” for
loop,
a new feature in C++ (added in 2011), so some
very old compilers may not support it. But it’s really useful, so I’m going
to show it to you. For now, we’ll use it to deal with the characters in
strings, but you should bear in mind that it works with “containers” of
any type, so when we see arrays and vectors later on, know that you can use
the range-based for
loop with them, also.
The structure of the ranged-for loop is
for(variable : container)
body
The variable can be either the name of an existing variable, or a declaration
of a new one; in either case, the type of the variable must be the same as the
contents of the container (e.g., above we used int for a vector<int>
).
The range-based for
works on vectors (above), but it also works on arrays:
int values[] = {...};
for(int v : values)
cout << v << endl;
It even works on strings:
// Will print
// A
// n
// d
// y
string name = "Andy";
for(char c : name)
cout << c << endl;
The range-based for loop is a special-purpose tool. If you wanted to do
something with every other element of a vector, or you wanted to process the
characters in a string in reverse order, you’d have to use the
more general for
loop. But if you want to just do something for every element
in a container, from start to end, the range-based for loop expresses what
you’re doing clearly and concisely.
Some examples:
string name = "Andy Clifton";
string text = "When, in the course of human events...";
// Prints AAnnddyy CClliiffttoonn
for(char c : name)
cout << c << c;
// Prints
// Andy Clifton
// ------------
cout << name << endl;
for(char c : name)
cout << "-";
// Count the number of upper-case characters
int uppers = 0;
for(char c : name)
uppers += (c >= 'A' && c <= 'Z' ? 1 : 0); // How can I simplify this line?
// Check if every alphabetic character in a string is upper-case:
bool all_upper = true;
for(char c : text)
if(c >= 'a' && c <= 'z') {
all_upper = false;
break;
}
We can use a range-based for loop to simplify the process of transforming a string by making a copy: remember that the basics of string transformation is to create a new empty string, and then add each character in the original to the end of the copy, after transforming it in some way. We can do this more simply with a range-based loop:
// Copy a string into another
string copy;
for(char c : text)
copy.push_back(c);
// Replace lowercase-letters with #s
for(char c : text) {
if(c >= 'a' && c <= 'z')
copy.push_back('#');
else
copy.push_back(c);
}
// Reverse a string
for(char c : text)
copy = c + copy; // Add to front
We can’t yet use a range-based for
to transform a string in-place, without
copying it, but we’ll see later that it can actually do that, too!