Introduction to loops
And now the real fun begins -- in the next set of lessons, we’ll cover loops. Loops are control flow constructs that allow a piece of code to execute repeatedly until some condition is met. Loops add a significant amount of flexibility into your programming toolkit, allowing you to do many things that would otherwise be difficult.
For example, let’s say you wanted to print all the numbers between 1 and 10. Without loops, you might try something like this:
1 2 3 4 5 6 7 8 |
#include <iostream> int main() { std::cout << "1 2 3 4 5 6 7 8 9 10"; std::cout << " done!"; return 0; } |
While that’s doable, it becomes increasingly less so as you want to print more numbers: what if you wanted to print all the numbers between 1 and 1000? That would be quite a bit of typing! But such a program is writable in this way because we know at compile time how many numbers we want to print.
Now, let’s change the parameters a bit. What if we wanted to ask the user to enter a number and then print all the numbers between 1 and the number the user entered? The number the user will enter isn’t knowable at compile-time. So how might we go about solving this?
While statements
The while statement (also called a while loop) is the simplest of the three loop types that C++ provides, and it has a definition very similar to that of an if statement
:
while (condition) statement;
A while statement
is declared using the while keyword. When a while statement
is executed, the condition
is evaluated. If the condition evaluates to true
, the associated statement executes.
However, unlike an if statement
, once the statement has finished executing, control returns to the top of the while statement
and the process is repeated. This means a while statement
will keep looping for as long as the condition evaluates to true
.
Let’s take a look at a simple while loop that prints all the numbers from 1 to 10:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> int main() { int count{ 1 }; while (count <= 10) { std::cout << count << " "; ++count; } std::cout << "done!"; return 0; } |
This outputs:
1 2 3 4 5 6 7 8 9 10 done!
Let’s take a closer look at what this program is doing. First, count
is initialized to 1
, which is the first number we’ll print. The condition count <= 10
is true
, so the statement executes. In this case, our statement is a block, so all the statements in the block will execute. The first statement in the block prints 1
and a space, and the second increments count
to 2. Control now returns back to the top of the while statement
, and the condition is evaluated again. 2 <= 10
evaluates to true, so the code block is executed again. The loop will repeatedly execute until count
is 11
, at which point 11 <= 10
will evaluate to false
, and the statement associated with the loop will be skipped. At this point, the loop is done.
While this program is a bit more code than typing all the numbers between 1 and 10, consider how easy it would be to modify the program to print all the numbers between 1 and 1000: all you'd need to do is change count <= 10
to count <= 1000
.
While statements that evaluate to false initially
Note that if the condition initially evaluates to false
, the associated statement will not execute at all. Consider the following program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> int main() { int count{ 15 }; while (count <= 10) { std::cout << count << " "; ++count; } std::cout << "done!"; return 0; } |
The condition 15 <= 10
evaluates to false
, so the associated statement is skipped. The program continues, and the only thing printed is done!
.
Infinite loops
On the other hand, if the expression always evaluates to true, the while loop will execute forever. This is called an infinite loop. Here is an example of an infinite loop:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> int main() { int count{ 1 }; while (count <= 10) // this condition will never be false { std::cout << count << ' '; // so this line will repeatedly execute } return 0; // this line will never execute } |
Because count
is never incremented in this program, count <= 10
will always be true. Consequently, the loop will never terminate, and the program will print "1 1 1 1 1"... forever.
Intentional infinite loops
We can declare an intentional infinite loop like this:
1 2 3 4 |
while (true) { // this loop will execute forever } |
The only way to exit an infinite loop is through a return statement, a break statement, an exit statement, a goto statement, an exception being thrown, or the user killing the program.
Here's a silly example demonstrating this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> int main() { while (true) // infinite loop { std::cout << "Loop again (y/n)? "; char c{}; std::cin >> c; if (c == 'n') return 0; } return 0; } |
This program will continuously loop until the user enters n
as input, at which point the if statement
will evaluate to true
and the associated return 0;
will cause function main()
to exit, terminating the program.
It is common to see this kind of loop in web server applications that run continuously and service web requests.
Best practice
Favor while(true)
for intentional infinite loops.
Loop variables
Often, we want a loop to execute a certain number of times. To do this, it is common to use a loop variable, often called a counter. A loop variable is an integer that used to count how many times a loop has executed. In the examples above, the variable count
is a loop variable.
Loop variables are often given simple names, such as i
, j
, or k
. However, if you want to know where in your program a loop variable is used, and you use the search function on i
, j
, or k
, the search function will return half your program! For this reason, some developer prefer loop variable names like iii
, jjj
, or kkk
. Because these names are more unique, this makes searching for loop variables much easier, and helps them stand out as loop variables. An even better idea is to use "real" variable names, such as count
, or a name that gives more detail about what you're counting (e.g. userCount
).
Loop variables should be signed
Loop variables should almost always be signed, as unsigned integers can lead to unexpected issues. Consider the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <iostream> int main() { unsigned int count{ 10 }; // count from 10 down to 0 while (count >= 0) { if (count == 0) { std::cout << "blastoff!"; } else { std::cout << count << ' '; } --count; } return 0; } |
Take a look at the above example and see if you can spot the error. It's not very obvious.
It turns out, this program is an infinite loop. It starts out by printing 10 9 8 7 6 5 4 3 2 1 blastoff!
as desired, but then goes off the rails, and starts counting down from 4294967295
. Why? Because the loop condition count >= 0
will never be false! When count is 0
, 0 >= 0
is true. Then --count
is executed, and count wraps around back to 4294967295
(Assuming 32-bit integers). And since 4294967295 >= 0
is true, the program continues. Because count
is unsigned, it can never be negative, and because it can never be negative, the loop won't terminate.
Best practice
Loop variables should be of type (signed) int.
Doing something every N iterations
Each time a loop executes, it is called an iteration.
Often, we want to do something every 2nd, 3rd, or 4th iteration, such as print a newline. This can easily be done by using the modulus operator on our counter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <iostream> // Iterate through every number between 1 and 50 int main() { int count{ 1 }; while (count <= 50) { // print the number (pad numbers under 10 with a leading 0 for formatting purposes) if (count < 10) { std::cout << '0'; } std::cout << count << ' '; // if the loop variable is divisible by 10, print a newline if (count % 10 == 0) { std::cout << '\n'; } // increment the loop counter ++count; } return 0; } |
This program produces the result:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
Nested loops
It is also possible to nest loops inside of other loops. In the following example, the inner loop and outer loop each have their own counters. However, note that the loop expression for the inner loop makes use of the outer loop's counter as well!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> // Loop between 1 and 5 int main() { int outer{ 1 }; while (outer <= 5) { // loop between 1 and outer int inner{ 1 }; while (inner <= outer) { std::cout << inner << ' '; ++inner; } // print a newline at the end of each row std::cout << '\n'; ++outer; } return 0; } |
This program prints:
1 1 2 1 2 3 1 2 3 4 1 2 3 4 5
Quiz time
Question #1
In the above program, why is variable
inner
declared inside the while block instead of immediately following the declaration of outer
?
Question #2
Write a program that prints out the letters a through z along with their ASCII codes. Hint: to print characters as integers, you have to use a static_cast.
Question #3
Invert the nested loops example so it prints the following:
5 4 3 2 1 4 3 2 1 3 2 1 2 1 1
Question #4
Now make the numbers print like this:
1 2 1 3 2 1 4 3 2 1 5 4 3 2 1
Hint: Figure out how to make it print like this first:
X X X X 1 X X X 2 1 X X 3 2 1 X 4 3 2 1 5 4 3 2 1
![]() |
![]() |
![]() |
i got the wrong answer but still posted A to Z
int asciiNum{ 65 };
while (asciiNum <= 90)
{
std::cout << static_cast<char>(asciiNum) << '\n' ;
++asciiNum;
}
For Question 2 in the quiz,
Do myChar and 'z' get implicitly converted to integers for the comparison?
`char` is an integral type. Character literals are nothing but fancy-looking integers.
Oh k, thx. Does the same thing go for Booleans as well? If so, would floating point numbers be the only fundamental data type that isn’t stored as an integer?
Anything can be stored as an integer, because everything can be interpreted as an integer. That includes floating point numbers and bools. It's only a matter of how the stored values are interpreted later on. The compiler knows that something is a float or a bool, so it will generate instructions that interact with the data accordingly.
The CPU would happily treat anything as an integer if told to.
Let's take a little journey to assembly to show that there are no types
All these types are different, in C++ at least.
To understand the example, you need to know a few things about assembly
A word is a 2 byte variable.
A dword is a double word -> 2*2 bytes = 4 bytes
A qword is a quad word -> 4*2 bytes = 8 bytes
`[something]` accesses the memory at address `something`. For example
`rip` and `rbp` are variables. Their values don't matter for this example.
`xmm0` and `xmm1` are 16 byte variables. In this example only the first 4/8 bytes of these variables are used.
The compiler turns the above code into
All of the variables, no matter their type, are stored at [rbp-something]. [rbp-something] is just stack memory, there are no types at all. When reading this assembly, we know that [rbp-22] (c) for example is a 2 byte variable, because `mov word` was used to access it.
Apart from knowing that it's a 2 byte variable, we know nothing. It could be an integer, a char, a float, we can't tell. We can see it has the value 123 so it's likely an integer, but that's just a guess.
(d), (g) and (h) are identical, apart from having different values. To the CPU, all these values are just numbers. It could read from anywhere without worries, eg.
This would read a part of `e`, the entirety of `d`, `c`, and `b`, and write this jam of values into `rax`. That doesn't make sense of course, these are different variables, but the CPU absolutely does not care nor know. If it's told to do something it will do so.
The compiler simply doesn't tell the CPU to access the variables like that. The compiler knows that it stored a `std::uint8_t` at [rbp-23], so it won't generate instructions that treat [rbp-23] as anything but a `std::uint8_t` (I'm ignoring optimizations, optimizations make everything weirder).
To our floating points `xmm0` (f) and `xmm1` (e). They're moved into the same memory as all the other variables, we've seen that. But they're coming from somewhere else. In line 1 and 2 of the assembly, they're loaded from another part of memory. Looking at that, we see
LCPI1_0 is a quad word and LCP1_1 a long (Double word). My disassembler shows those values as hexadecimal integers, it could show them as anything, there are no types after all (My disassembler figured out that those values are floating point numbers, but it's just guessing too). Again, there are no types.
We can guess that those values are floating point numbers, because their integer values look like floating point values to someone who's see them before, and because those values are stored in `xmm` variables. `xmm` variables can store integers just as well, so we're guessing again, but they're often used for floating point numbers.
Enough assembly, bottles.
How do you know you can drink from a bottle?
It says "water" on the label, what if there is no label?
The bottle is transparent, what if it's opaque?
You can't know that you can drink from a bottle, until you try, and that attempt may or may not end well. Your hands and mouth are capable of drinking from a bottle that contains sand, probably.
Still, you drink from bottles without worries, because you trust whoever filled the bottle that they've put a drinkable liquid inside. If the bottle manufacturer/filler made a mistake, you won't feel well, but you can drink paint as if it were apple juice.
The bottle manufacturer should not make such a mistake, just like the compiler should not tell the CPU to treat memory that contains a float like a bool.
I hope this helped more than it was confusing. If you have any questions, feel free to ask.
You can find the above code+disassembly and play around with it at https://godbolt.org/z/cobxWM
Wow, thanks for the long explanation. I'm understanding this as there technically being no types, with the compiler giving the CPU the specific instructions on how to interpret the data.
One more question:
For your above example, when the compiler turned the C++ code into assembly, did all the values (including the floating points) "turn" into integers? (Meaning were they were stored as integers)
And from there, with the compiler's instructions, the CPU can interpret those integers accordingly?
All values are stored in binary, that's the only thing computers can do. In the disassembled output of my previous comment, the values are displayed as decimal/hexadecimal integers. Their representation doesn't matter through, as it's just a visual aid for humans.
All the values I've shown are not integers, not floating points, not anything. They're just bits and we're looking at a pretty representation.
Most operations operate on integers, so that's a good way to represent values of unknown type.
Ohhh ok. When you said "All the values I've shown are not integers, not floating points, not anything. They're just bits and we're looking at a pretty representation.", is this similar to how you can say a car (or bottle, or anything) is like a pretty representation of matter?
Yep, I guess you say that. You can be pretty sure that what you're look at is a car, but maybe it's a chocolate car
My initial stab at Question 4. Your solution was a lot less typing, but I got it printing correctly this way. I need more experience to think as abstractly as your solution. :)
Getting the hang of this! My solution was simply the inverse of what your solution was. I used the ascii number and cast it to the char.
Thank you for another amazing lesson :)
1. Section "While statements", the longest paragraph:
> Control now return back to the top of the while statement, and the condition is evaluated again.
"return" should be "returns"
2. Question #2's answer:
2.1 Line 8 has an extra space:
2.2 I think `mychar` should be `myChar`
The questions were fun, looking forward to the next lessons!
Fixed. Thanks for the feedback.
Question #4
MY solution
1. As a suggestion, perhaps you would like to create a user codebase?
Currently, we're just submitting it by WordPress in the comments.
If you have a userbase, each user will submit their own variant on how to code each Quiz question and you will create a gigantic database where you can analyze the difference in code, where are the common mistakes, where are the common misintepretations, what is the most common coding style, etc
For those who can't solve the Question or don't want to, just click skip.
2. This is what I did for Quiz 4.
Is it acceptable?
How I did it. I went on Google to find int(a) and char(a) and took the ASCII code directly.
Does it qualify for Quiz 2?
This is my answer for Quiz 3.
Is this acceptable?
currently all the comments is disappear for all pages
Here's my solution to #4, took really long lol. As always thank you for these tutorials!!
Hi, in relation to quiz 4, i want to ask why decrementing counter inside the second while loop changes the final value of it, causing the program to overflow. Don't changes made to it only exist inside the loop?
This is my solution for task 4
This is question 4 just all done with while loops
Small fix in second code example of chapter Iteration, line 12:
std::cout << '0' << count << ' '; instead of std::cout << "0" << count << ' ';
Thank yoU!
The section on loop variables contains the statement "Then --count is executed, and count overflows back to 4294967295."
Earlier in the tutorial I learned that unsigned integers do not overflow, but wrap around, so I suggest to use this wording here, too. :)
Wording updated, thanks!
Hi and thank you so much for this tutorial. I am a newbie and I am confused about while loops. In the following program I am trying to calculate the standdev of a set of numbers from a file
The number in the current file are
80
90
just to test the program. What I really don't understand is why it reads the last value twice.
Thanks for all your help.
/**************************************************************
Description: Design and write a C++ program that reads a set of
scores from the file scores.dat, and
outputs their
mean and standard deviation on cout.
**************************************************************/
#include <iostream>
#include <fstream>
#include <cmath>
using namespace std;
int main(){
// Declare input stream
ifstream inData;
// Declare variables
int count = 0;
float value;
float valueSquared = 0.0;
float sum = 0.0;
float sumSquared = 0.0;
float average, standDev;
// Open file
inData.open("scores.dat");
if(!inData){
cout << "Cannot open the file." << endl;
return 1;
}
//inData >> value;
//valueSquared = value*value;
//cout << value << ", " << valueSquared << endl;
//sum = value;
//sumSquared = valueSquared;
//cout << sum << ", " << sumSquared << endl;
// Get values and calculate sums
while(!inData.eof()){ //Set the EOF flag
inData >> value;
valueSquared = value*value;
cout << value << ", " << valueSquared << endl;
sum = sum + value;
sumSquared = sumSquared + valueSquared;
count++;
}
cout << count << ", " << sum << ", " << sumSquared << ", " << sum*sum << endl;
// Check that you have at least two values
if(count<=1){
cout << "Average and standard deviation are meaningless for one single value." << endl;
return 1;
}
else{
// Calculate average
average = sum/count;
// Calculate standard deviation
standDev = sqrt((sumSquared-(sum*sum))/(count*(count-1)));
// Output results
cout << "The average and standard deviation of the set of scores are\n" <<
"average: " << average << "\n" << "standard deviation: " << standDev << endl;
}
return 0;
}
For me, this is by far the most difficult chapter ever, at least the questions. Didn't seem to actually get a correct solution at all, and had to check the solutions multiple times (and I still haven't understood much). I guess I'll have to re-do this chapter later on. Thanks for the tutorials again :)
#Prints alphabets a - z
Ohh... Sorry I made a mistake in my code
Correct one
Hi there, I am having some little issues understanding your code understand Nested Loops
This code
for whatever reason, on debian 10, using the terminal in VSCode, my initial solution to the last problem resulted in the shape of some sort of binary tree being printed out.. just to make sure it was an issue with my code, i tried the provided solution, however the same thing resulted when compiling and executing the answer.
ended up doing this really weird and gross looking answer that doesnt satisfy me all that much, but i assume it'll get cleaner in time
I suppose you saw the cool pyramid
This happens if you print a single space, rather than 2 spaces, in line 21 of the quiz's solution. It might also happen if you're not using a monospace font.
Yeah, that's exactly what happened. Why did it require 2 spaces rather than 1?
1 space for where there'd normally be a number. 1 space for where there'd normally be a space. Replace the first space with a character if you can't make sense of it.
Ah, okay. Yeah, replacing the space with a character helped me understand. Cheers
Okay, got all three. Wanted to use a continue; in solution 2 but that's section 5.8. Comments and few questions in the code, thanks. Cheers.
- You're not using `argc` or `argv`, so they shouldn't be there.
- Your formatting will only get worse. Use an auto-formatter.
- Use characters, not integers, for characters.
- Avoid `static` variables, your code isn't reusable.
Thanks Nascar. Got it cleaned up and made the changes. I understand what you mean by use chars 'A' and not number 65, saw and got it fixed. I want to make sure I understand the static comment. I move the variable to outside the while loop so I could just use int variable and not static int variable. Your comment "your code isn't reusable" is in reference to that (static int variable) not being able to be used from an outside or another file as in a header file. I re-read chapter 6.6 and that's what I gathered from it, just wanted to make sure I understood. Thanks for the help. Cheers.
You wrote everything in `main`, so I had to say "code" isn't reusable rather than "function xx" isn't reusable, which would be easier to grasp. Say you wrote a function instead
Now what happens when you call `quiz4()`? Everything is as it was before.
What happens when you call `quiz4()` a second time? It's broken, because `printNumber` was never reset.
If `printNumber` is non-static, it doesn't matter how often you call `quiz4()` or where you call it from. The function the same every time it called (If we ignore `std::cout`'s flags, which could be changed from the outside). The way you solved it now is correct.
Keeping `printNumber` `static` and resetting it at the end of the function would have seemingly solved them problem, but it would still be there, just in a different case. `quiz4()` could only be called from one place at a time. That's what we do throughout the entire tutorial so you wouldn't have noticed that it's broken. But if you get into multithreading, this is no longer the case. `quiz4()` could run several times in parallel. But if all running `quiz4()`s share a variable, you'll run into trouble.
Hello, I quite don't understand the last example. If "inner" is created and initialized after every iteration, then how does it print "1 2", "1 2 3", etc.
maybe not the best, but it is how I was thinking before looking at solution !
This is really discouraging cause after nearly an hour of work, mainly due to sorting out bugs through trial and error, this is the best solution i can come up with. Yet the solution posted is way simpler. I am having serious doubt about me becoming a programmer.
The worst part is if you ask me to write the same program a few hours later. I would have to trial and error my way through the same bugs.
And every time you do it, you get better. You can't expect from yourself to be able to write great code after reading a tutorial. You'll only get better by writing code, failing, and looking up how to do it properly.
This is my honest first attempt... I was kinda reaching on this one... Is my solution with the string initialization less effective than just putting a conditional in there?
`std::string` has to perform a dynamic memory allocation (If the string is long enough), which makes it slower than printing manually. You're not going to notice this, so your solution is fine. "\n" should be '\n'.
Hello Nascardriver and Alex,
The following is the short solution to the question 3 using JUST one (do)while-loop. (I put two versions)
SOLUTION VERSION ONE
SOLUTION VERSION TWO
Instead of the string of spaces below, is it better to concatenate two space chars? How about three? Asking with regards to performance.
" In the following example, the inner loop and outer loops each have their own counters."
Shouldn't it be "and outer loop"?
yes, fixed