06: Loops

UC Irvine - Fall ‘22 - ICS 45C

Quick list of things I want to talk about:

  • for
  • while
  • do-while
  • i++ vs. ++i
  • break/continue
  • equivalence
  • scope

Expanded notes:

Now that we have covered branching and how to write conditional expressions, let’s put them to use in loops! C++ gives us three main types of loops: (i) for, (ii) while, and (iii) do-while. These notes aim to explain how to use all of those.

for-loop: counted-repetition

The first loop we’ll take a look at is the for-loop. The usual application of a for-loop is to iterate over a collection of things. In other words, you use it when you know beforehand how many times you want to loop, that’s why we referred to it as counted repetition.

The syntax of a for-loop is as follows:

for (INIT; CONDITION; CHANGE) {
  // instructions to be repeated
}

So before you start the loop, your initialization statement happens once. That could be used to create a variable that will change during the loop.

Then, before each iteration (i.e., one loop run) the condition is checked. If it is true, then the statements inside the loop will execute. This conditions is checked even before the 1st iteration!

Finally, after each iteration, the change statement executes.

Let’s walk through an example: printing all numbers in the range [0, 2].
Using the syntax above, we could do something like this:

for (int i=0; i <= 2; i++) {
  cout << i << ", ";
}

Compiling and running this code should give you:

0, 1, 2, 

So what’s happening?

  1. a variable i is created with a value of 0;
  2. the condition is checked, since 0 <= 2 is true, we run an iteration;
  3. loop iteration (cout statement);
  4. we increase the value of i to 1;
  5. evaluate the condition, still true;
  6. loop iteration (cout statement);
  7. we increase the value of i to 2;
  8. evaluate the condition, still true;
  9. loop iteration (cout statement);
  10. we increase the value of i to 3;
  11. evaluate the condition, now it’s false. So we skip the loop body and go to the statement after the loop.

All parts of the for-loop are optional, so if you have a variable that’s already been initialized, for example, you could simply leave the initialization blank:

int i = 0;
for (/* empty */; i <= 2; i++) {
  cout << i << ", ";
}

Note that you can create comments with /* */, so the /* empty */ over there does the same as if we had not typed anything.

while-loop: conditional-repetition

The second type of loop we will talk about is the while-loop. They are the usual tool when you don’t know how many types you want to repeat something. That’s why we referred to it as conditional-repetition.

The syntax of a while-loop is as follows:

while (CONDITION) {
  // instructions to be repeated
}

So you can imagine this syntax as a subset1 of the for-loop. Before each loop iteration, it checks the condition and if it’s true, it execute the statements inside the loop.

However, there is no special place for the initialization and change, so you will have to add those before/inside the loop respectively if needed. If the condition is not true before the loop, it will not execute at all. If you don’t change the condition inside the loop you’ll never exit!

For example, let’s write a program that shows a helpful message as long as the user wants to see it:

while (user_input == 'Y') {
  cout << "Hello! This program only shows this message...\n\n";
  cout << "Would you like to see this message again? ";
  cin >> user_input;
}

Here we’re assuming that we take user_input before the loop, and if they say they want to see the help message, we show it and keep doing that as long as they press Y.

So what’s happening?

  1. the condition is checked, if user_input == 'Y' is true, we continue on the loop;
  2. iteration (cout, cout, cin);
  3. the condition is checked, if user_input == 'Y' is true, we continue on the loop;
  4. iteration (cout, cout, cin);

… and so on.
If eventually the condition is false, we exit the loop, otherwise we keep repeating indefinitely. Note that if the condition isn’t true at the very beginning the loop doesn’t execute at all!

do-while-loop: 1 + conditional-repetition

Finally, we have the do-while-loop. This is similar to the while-loop, with one key difference: instead of checking the condition before executing statements, it checks the condition after executing the loop body.

The syntax is as follows:

do {
  // instructions to be repeated
} while(CONDITION);

Notice the semicolon ; after the parentheses.

Looking at the same example as the previous one, let’s write a program that shows a helpful message as long as the user wants to see it:

do {
  cout << "Hello! This program only shows this message...\n\n";
  cout << "Would you like to see this message again? ";
  cin >> user_input;
} while(user_input == 'Y');

So what’s happening?

  1. iteration (cout,cout,cin);
  2. the condition is checked, if user_input == 'Y' is true, we continue on the loop;
  3. iteration (cout, cout, cin);
  4. the condition is checked, if user_input == 'Y' is true, we continue on the loop;

… and so on. If eventually the condition is false, we exit the loop, otherwise we keep repeating indefinitely.

However, differently from the while-loop, there is no condition check at the beginning! So the do-while-loop always executes its body at least once.

i++ vs. ++i

We’ve used i++ a few times in the notes and know that it increments the value of i by 1. However, it might not be clear what’s the difference between i++ and ++i, and loops provide a great tool to showcase that difference!

For example, let’s say we receive a number n from the user, and count up from 0 to n. We came up with this code at first:

int n, i=0;
cin >> n;
while (i <= n) {
  cout << i << ", ";
  i++;
}

But wait, we could combine the change step (i++) with the condition, like this:

int n, i=0;
cin >> n;
while (++i <= n) {
  cout << i << ", ";
}

Would this work? Sure, but it would start counting from 1 instead of 0.
Why is that?

For the sake of this example, let’s say n == 3. When we compare ++i <= n, ++i increases the value of i, so it’s now equal to 1, and it returns this new value. So the comparison is 1 <= 3, which is true. Assuming starting from 1 is fine, we’re still printing 1, 2, 3.

Now let’s try the same thing with i++ instead:

int n, i=0;
cin >> n;
while (i++ <= n) {
  cout << i << ", ";
}

When we compare i++ <= n, i++ increases the value of i, so it’s now equal to 1, but it returns the old value of 0. So the comparison is 0 <= 3, which is true.

This seems fine, until we reach the point where i == 3. In that iteration, when we compare i++ <= n, i becomes 4, but we check 3 <= 3, which is true. So we end up printing 1, 2, 3, 4, .

Breaking and advancing loops

C++ gives us two keywords to modify the behavior of loops: break and continue.

break ends the loop it’s inside. So instead of going to the next instruction or condition check, the loop finishes.

For example, this code:

for (int i=0; i < 3; i++) {
  cout << i << ", ";
  break;
}

would only print 0, .

Note that break does not interrupt nested loops, so if you have a loop within another one, break will only quit one level.

For example, if you run this code:

while (condition_1) {           // loop #1
  cout << "hello from while\n";
  for (int i=0; i < 10; i++) {  // loop #2
    cout << "hello from for\n";
    break;  // only stops loop #2
    cout << "bye from for\n";
  }
  cout << "bye1 from while\n";
  break;  // only stops loop #1
  cout << "bye2 from while\n";
}

You should get:

hello from while
hello from for
bye1 from while

continue tells the loop to finish the current iteration, but does not end the loop. So the condition is checked again and a new iteration could start.

For example, this code:

for (int i=0; i < 3; i++) {
  cout << "hello";
  continue;
  cout << "bye";
}

should print hellohellohello.

Similarly, continue does not interrupt nested loops, so if you have a loop within another one, continue will only advance one level.

For example, this code:

while (condition_1) {           // loop #1
  cout << "hello from while\n";
  for (int i=0; i < 3; i++) {  // loop #2
    cout << "hello from for\n";
    continue;  // only stops loop #2
    cout << "bye from for\n";
  }
  cout << "bye1 from while\n";
  continue;  // only stops loop #1
  cout << "bye2 from while\n";
}

should output:

hello from while
hello from for
hello from for
hello from for
bye1 from while
hello from while
...

Loop equivalence

Each loop we discussed has its applications, but they could theoretically all be swapped around.

Equivalence between while and do-while loops

First, let’s see how we can replace a while-loop with a do-while-loop. If you have the following loop:

while (collatz != 1) {
  if (collatz % 2 == 0) {
    collatz /= 2;
  } else {
    collatz = 3 * collatz + 1;
  }
}

We can convert it to a do-while by manually adding a pre-loop check:

if (collatz != 1) {
  do {
    if (collatz % 2 == 0) {
      collatz /= 2;
    } else {
      collatz = 3 * collatz + 1;
    }
  } while(collatz != 1);
}

On the other hand, to convert a do-while into a while, we need to make sure the the loop body executes once before the check. We can do that by copying the body to before the while statement.

For example, if you have this do-while:

do {
  cout << "Enter an odd number: ";
  cin >> n;
} while(n % 2 == 0);

You can convert it to this while:

cout << "Enter an odd number: ";
cin >> n;
while (n % 2 == 0) {
  cout << "Enter an odd number: ";
  cin >> n;
}

Although they end up with the same logic, the while-loop is more readable when you want a condition check before anything happens, and a do-while-loop is more concise when you want to execute once no matter what. So you should use the appropriate loop for your problem, instead of trying to fit one of these everywhere in your code.

Equivalence between for and while loops

It’s also possible to replace a counted loop with a conditional one. For example, if you have a counted loop like this:

for (int i=0; i < 100; i++) {
  cout << i << ", ";
}

You could replace it with a while-loop like this:

int i = 0;         // initialization
while (i < 100) {  // condition
  cout << i << ", ";
  i++;             // increment
}

It’s also possible to go from while to for. If you have a conditional loop like this:

while (user_input == 'Y') {
  cout << "Want to see this message again? ";
  cin >> user_input;
}

You could replace it with a for-loop:

for (; user_input == 'Y'; cin >> user_input) {
  cout << "Want to see this message again? ";
}

Although they end up with the same logic, the while-loop is more readable for a conditional logic, and the for-loop explains better the iteration over a range. Again, you should use the appropriate loop for your problem, instead of trying to fit one of these everywhere in your code.

Scope

If you go back up in the notes you’ll see that we always have brackets { } to define what’s inside our loops. This is a similar requirement as the one we saw for if-statements. So if your loop has only one single statement inside of it, brackets are not required.

For example, this is a valid loop:

for (int i=0; i <= 2; i++)
  cout << i << ", ";

However, similar to if-statements, brackets usually make code more readable and help prevent bugs. So try to always use them.

References


  1. In reality, the for-loop is a specific instance of while-loops that require extra things. But when looking from a syntax point of view, a while-loop requires only the condition part, whereas a for-loop also requires initialization and change. ↩︎