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:

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:

  1. We can modify the string “in-place”, changing each character into something else.

  2. 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:

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:

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:

Nested loops are often used when we have 2- (or more) dimensional data.

Some examples:

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!