Beginner's Tutorial On Loop

Learner010 7 Tallied Votes 675 Views Share

Yesterday i completed my exercises on loop , that's why today i've decided to create a tutorial on loop in c++.

Loop

loop allows us to repeat a statement or a group of statements. When we want to repeat a single statement then it is not necessary to put curly braces But if we want to repeat a group of statements then it must be enclosed with curly braces. And it is a good practise to enclosed your loop body with curly braces.

c++ has three loop control structures

while loop
do-while loop
for loop

while loop

Structure of while loop is as follow

while(condition)
{
statement;
statement;
statement;
}

while loop is entry controlled loop because it first check the condition And if result is found True then statements get executed.

let's see example

int a=1;
while(a<=10)
{
cout<<a++;
cout<<endl
}

Output:

1
2
3
4
5
6
7
8
9
10

do-while loop

do-while loop is exit controlled loop because condition is evaluated after each iteration.

i=0;
do
{
cout<<++i<<"\t";
}while(i<10);

Output:-

1   2   3   4   5   6   7   8   9   10

do-while loop will execute a statement or a group of statement for atleast one time even the condition is Zero(false).

let's see example

do
{
cout<<"Hello";
}while(0);

Output:

Hello

For Loop

Syntax of for loop(syntax may be different based on the requirement , here i put what is normally used):

for(initialization;condition;increment/decrement)
{
statement;
statement;
statement;
}

Once initialization is done , it check the condition , and if condition is found true then statements get executed. Then increment/decrement part gets executed.Again it check the condition and execute statements and then increment/decrement. This process continues until condition is false.

let's see example:

for(int i=1;i<=10;i++)
{
cout<<2*i<<"\t";
}

Output:

2   4   6   8   10  12  14  16  18  20

It is not necessary that the last part of the loop must be increment / decrement.
let's see example:

for(int i=0;i<10;cout<<"\t")
{
cout<<++i;
}

Output:

1   2   3   4   5   6   7   8   9   10

Some other structures of for loop:

int i=1;
for(;i<=10;i++)
{
cout<<2*i<<"\t";
}
output:
2   4   6   8   10  12  14  16  18  20

---------------------------------------
---------------------------------------

int i=0;
for(;i<10;)
{
cout<<2*++i;
}
output:
2   4   6   8   10  12  14  16  18  20

Nested Loop

Nested Loop means loop within loop.An inner loop within the body of an outer loop.
let's see example:

int inner,outer;
for(outer=1;outer<3;outer++)
{
    for(inner=1;inner<=3;inner++)
    {
        cout<<"outer: "<<outer<<"\t"<<"inner: "<<inner;
    }
    cout<<endl
}

Output:-

outer: 1    inner1  outer: 1    inner2   outer: 1   inner3
outer: 2    inner1  outer: 2    inner2   outer: 2   inner3

The outer loop changes only after inner loop is complety finished.

Syntax for nested while loop

while(condition)        //outer loop
{
   statements; 
   while(condition)     //inner loop
   {
      statements;
   }
   statements; 
}

Syntax for nested do-while loop

do                              //outer loop
{
  statements; 
   do                           //inner loop
   {
      statements;
   }while(condition);
  statements; 
}while(condition);

Thanks for reading this very simple and little tutorial. I hope it helps to c++ Beginners.

kal_crazy commented: Very nice!! +2
kal_crazy 13 Junior Poster

It would be great if you could have compared all the loops in one paragraph at the end.

Anyways it is still great :)

Learner010 99 Posting Whiz in Training

thanks for expressing pleasure over the tutorial.

It would be great if you could have compared all the loops in one paragraph at the end.

do you mean conclusion at the end ?

ok, i will add conclusion very soon and will contact to administrators for such a updation.

deceptikon 1,790 Code Sniper Team Colleague Featured Poster

Nice. You might also consider including goto-based equivalents for each loop to really nail down the underlying logic.

Learner010 99 Posting Whiz in Training

thanks deceptikon,
i will consider goto in my upcoming tutorials.

once again thanks.

rmsh92 0 Newbie Poster

thanxx

Member Avatar for ArashVenus
ArashVenus

About goto :
programmers abandoned goto for readability reasons long ago , Its suggested that you don't use that .
to be simple , goto adds unnecessary complex to the code , which is never desirable in today's programming .

for example you could write the loop using goto like this :

int a=0;
part1 : if (a<10) a++;
else if (a=>10) return 0;
goto part1;

now comapre the code above with these :

for (int i=0; i<10; i++);

which is equal to

int i=0;
while (i<10) i++;

which one do you think is easier to read and understand ?

ddanbe commented: Right! Goto gohome! +14
Learner010 99 Posting Whiz in Training

which one do you think is easier to read and understand ?

Obviously Loops.

i didn't meet with such a condition where goto is the only choice .i read about goto and find the following (its exactly what i thought):

Although the use of goto is almost always bad programming practice (surely you can find a better way of doing XYZ), there are times when it really isn't a bad choice. Some might even argue that, when it is useful, it's the best choice.

anyways, i'll create tutorial on conditional and unconditional statement in c++. There i'll try to explain my best.
Right Now , i am back to c++ and start learning where i stopped it and i'll write tutorial on strings(to just make you familiar with its library function),functions etc.And also will write tutorial on file manipulation when i finish learning it.
For Tutorial on pointers , i've already informed to deceptiokon.I am damn sure that he can write much better on it . I Can't Explain mcuh of it(I am not too good at Pointers, actually don't know every aspect of it).

So, if deceptikon is reading this post then please start writing on it so that no beginner will be scared of pointer anymore.Also your tutorial titled "Array in c++" was great.

Ancient Dragon 5,243 Achieved Level 70 Team Colleague Featured Poster

The only time I've ever seen goto useful is to break out of a very deeply nested set of loops. Too bad c++ didn't implement C's setjmp and longjmp functions because they would be similar to goto but could jump across functions. But of course that would be impossible in c++ because it would cause lots of memory leaks since class destructors wouldn't get called. I think throw is c++ answer to longjmp.

vegaseat 1,735 DaniWeb's Hypocrite Team Colleague

Nice tutorial!
Sometimes an endless loop comes in handy ...

  // an endless loop with break condition
  int k = 0;
  while(true) {
    k++;
    cout << k << "  ";
    if (k >= 10)
        break;
  }

You can put this into a function and replace break with return.

Also, put your deeply nested loops into a function and use return instead of the much besmirched goto.

vegaseat 1,735 DaniWeb's Hypocrite Team Colleague

One more addition. You can turn a function into a loop ...

int countdown_loop(int count) {
  // a recursive function to count down from count to zero
  cout << count << "  ";
  if (count == 0)
    return 0;
  else
    return countdown_loop(count - 1);
}
David_50 13 Junior Poster in Training

A simple way to design a loop is to continue until you are about to repeat a step, and then add the loop and adjust any calculations for changing variables within the loop. Next, decide where to exit and insert an "if ( conditons ) break, goto or return".

Be careful you are not in a 'switch-case' (great for comment documentation of logic) within the loop, as 'break' is now overloaded to serve it, but a 'goto' or 'return' has no such limitation. The 'goto' is not as clear and maintainable way to loop or exit a loop, so it is strongly depricated, if allowed in the language at all, but in essence the compiler turns the higher loop constucts into 'if' and 'goto'.

Now, if the loop is not always entered even once, a 'for' or 'while' might be a good control, as they test before the loop. If the test is at the end, then it is naturally a 'do-while'.

In actual practice, the discovered optimal loop is actually an unconditional 'do-while' or 'while' with a conditional break/return in the middle. For instance, in selecting multiple database rows, after the row fetch one discovers no row was fetched as the cursor is exhausted (empty). Sadly, sometimes steps are repeated to make a loop fit a 'while', 'for' or 'do-while' form, which partially defeats the economy of the loop. Of course, an optimizing compiler may unroll the loop for better speed, in which case the value of the loop is not in reduced object code, but still there is great value in the simplicity and reduced source code to maintain.

In generated code, a 'while' branches to the end of the loop to test for exit first, as if it was a patch over a 'do-while', which for some CPUs slows processing by discarding instruction prefetch, so a 'do-while', if appropriate, is actually faster. Similarly, it is faster to put a loop into a subroutine than to call a subroutine in a loop, as not-inlined calls are more expensive than branches. The try-catch is also expensive, so loop inside it, rather than trying inside the loop.

Since it is hard to identify the boundaries of a very long loop within a very long block, often the whole loop is moved to a subroutine, with the side benefit of allowing 'return' to break the loop, even within 'switch-case'.

AndrisP 193 Posting Pro in Training

And more compact version:

for(int i=0;i<10;std::cout<<i++<<std::endl);
ravenous 266 Posting Pro in Training

`for(int i=0;i<10;std::cout<<i++<<std::endl);

Yikes! Don't write code like this :) In general, tricks in code made code unreadable in the long-term.

I think it's a reasonably good rule that one should never write a loop unless if can't be avoided. I have a hierachy of things that one should try and do if a loop-type situation is required:

  1. Use an STL algorithm that describes the thing that you're trying to do in the "loop"
  2. Use std::for_each with a lambda that does the thing that you want to do
  3. Use a range-based for (or BOOST_FOREACH, if you don't have range-based for support in your compiler)
  4. Use a for, while or do-while loop

This might seem like over-kill, but in most cases, it can lead to more readable code that is also more maintainable and less likely to become unmaintainable over time (as more people edit the code). For instance, the first loop in your examples:

int a = 1;
while( a <= 10 )
{
    std::cout << a++;
    std::cout << std::endl;
}

can be written using the STL algorithm std::generate_n:

std::generate_n( std::ostream_iterator< int >( std::cout, "\n" ), 10, []()
{
    static int i = 0;
    return i++;
});

This might look like some strange incantation, but if you think about it (and you're familliar with STL algorithms) then it actually explains what it does just by the names of the STL components that are used:

  • std::generate_n: We're going to be making some fixed number (10) of new things and sending them somewhere
  • std::ostream_iterator< int >: we're sending things (ints) to an output stream of some sort
  • std::cout; Ah! it's the console

In this simple example, then this all seems rather complicated, but that's because the loop is quite simple... at the moment. As the loop gets more complicated, then things get a bit muddy in the case of using the for loop and, I think, the case for using algorithms gets a bit stronger. For example, what does this do:

std::vector< int > numbers;
for ( size_t i = 0; i < 100; ++i )
    numbers.push_back( i );

std::array< int, 3 > blacklist = { 87, 150, 237 };

const short int special_client_key = 17727;

std::vector< int > copy_of_numbers;
for ( size_t i = 0; i < numbers.size(); ++i )
{
    if ( ! (numbers[ i ] % 2 ) )
        continue;

    const int number_to_check = 3 * numbers[ i ];

    if ( ( number_to_check % 10) != 7 )
        continue;

    bool b = false;
    for ( size_t j = 0; j < blacklist.size(); ++j )
    {
        if ( blacklist[ j ] != number_to_check )
            continue;

        b = true;
        break;
    }

    if ( b )
        continue;

    const int number_to_add = number_to_check ^ special_client_key;

    copy_of_numbers.push_back( number_to_add );
}

// Check what we've copied
for ( size_t i = 0; i < copy_of_numbers.size(); ++i )
{
    std::cout << copy_of_numbers[ i ] << " ";
}

std::cout << std::endl;

This isn't even really a very long loop. If it looks horrible, then take a look at how we might arrive here. It's a simple and slippery slope that the for loop makes is easy to slide down. Here's a story that illustrates how this might happen...

"We need some numbers copied from one vector to another for the client". Great! Here you go:

std::vector< int > numbers;
for ( size_t i = 0; i < 100; ++i )
    numbers.push_back( i );

std::vector< int > copy_of_numbers;
for ( size_t i = 0; i < numbers.size(); ++i )
{
    copy_of_numbers.push_back( numbers[ i ] );
}

std::cout << std::endl;

// Check what we've copied
for ( size_t i = 0; i < copy_of_numbers.size(); ++i )
{
    std::cout << copy_of_numbers[ i ] << " ";
}

std::cout << std::endl;

OK, so far so good. Nothing too offensive here. Now, someone else comes along and says "actually, we want to multiply the numbers by 3 before we put them in". OK, fine, lets do that:

std::vector< int > numbers;
for ( size_t i = 0; i < 100; ++i )
    numbers.push_back( i );

std::vector< int > copy_of_numbers;
for ( size_t i = 0; i < numbers.size(); ++i )
{            
    if ( ( 3 * numbers[ i ] % 10) != 7 )
        continue;

    copy_of_numbers.push_back( 3 * numbers[ i ] );
}

std::cout << std::endl;

// Check what we've copied
for ( size_t i = 0; i < copy_of_numbers.size(); ++i )
{
    std::cout << copy_of_numbers[ i ] << " ";
}

std::cout << std::endl;

There we go. Easy! It's still easy-ish to figure out what's going on here. You have to actually study the loop a bit and spot that it's multiplying the things in numbers by 3 before putting them in the copy array, but it's still do-able.

Some time passes... someone else says "Cripes! we should only be adding the even numbers to the copy!". OK, lets do that then:

std::vector< int > numbers;
for ( size_t i = 0; i < 100; ++i )
    numbers.push_back( i );

std::vector< int > copy_of_numbers;
for ( size_t i = 0; i < numbers.size(); ++i )
{
    if ( ! (numbers[ i ] % 2 ) )
        continue;

    copy_of_numbers.push_back( 3 * numbers[ i ] );
}

std::cout << std::endl;

// Check what we've copied
for ( size_t i = 0; i < copy_of_numbers.size(); ++i )
{
    std::cout << copy_of_numbers[ i ] << " ";
}

std::cout << std::endl;

OK, there we go. The loop's starting to get a bit fuller now. We're still OK though, because we just wrote it, so we still remember how we got here.

More time passes... "What?! we're adding all the even numbers?! We're only supposed to add numbers to the copy if the last digit is a seven". OK, then we need another check:

std::vector< int > numbers;
for ( size_t i = 0; i < 100; ++i )
    numbers.push_back( i );

std::vector< int > copy_of_numbers;
for ( size_t i = 0; i < numbers.size(); ++i )
{
    if ( ! (numbers[ i ] % 2 ) )
        continue;

    if ( ( 3 * numbers[ i ] % 10) != 7 )
        continue;

    copy_of_numbers.push_back( 3 * numbers[ i ] );
}

// Check what we've copied
for ( size_t i = 0; i < copy_of_numbers.size(); ++i )
{
    std::cout << copy_of_numbers[ i ] << " ";
}

std::cout << std::endl;

Ooookaay... Still managable, right? Right?

More time passes... "Did you copy things that were in the blacklist?". Yeah, there's a blacklist:

std::vector< int > numbers;
for ( size_t i = 0; i < 100; ++i )
    numbers.push_back( i );

std::array< int, 3 > blacklist = { 87, 150, 237 };

std::vector< int > copy_of_numbers;
for ( size_t i = 0; i < numbers.size(); ++i )
{
    if ( ! (numbers[ i ] % 2 ) )
        continue;

    const int number_to_add = 3 * numbers[ i ];

    if ( ( number_to_add % 10) != 7 )
        continue;

    bool b = false;
    for ( size_t j = 0; j < blacklist.size(); ++j )
    {
        if ( blacklist[ j ] != number_to_add )
            continue;

        b = true;
        break;
    }

    if ( b )
        continue;

    copy_of_numbers.push_back( number_to_add );
}

// Check what we've copied
for ( size_t i = 0; i < copy_of_numbers.size(); ++i )
{
    std::cout << copy_of_numbers[ i ] << " ";
}

Okie dokie, that last edit has pretty much totalled the readability of our loop. Now it's almost impossible to understand the code by quick inspection. Only a concentrated reading through line-by-line is going to reveal its secrets!

A year later... "The client wants us to XOR the number with their special value (17727), not just multiply the value by 3.".

OK, Check!

std::vector< int > numbers;
for ( size_t i = 0; i < 100; ++i )
    numbers.push_back( i );

std::array< int, 3 > blacklist = { 87, 150, 237 };

const short int special_client_key = 17727;

std::vector< int > copy_of_numbers;
for ( size_t i = 0; i < numbers.size(); ++i )
{
    if ( ! (numbers[ i ] % 2 ) )
        continue;

    const int number_to_check = 3 * numbers[ i ];

    if ( ( number_to_check % 10) != 7 )
        continue;

    bool b = false;
    for ( size_t j = 0; j < blacklist.size(); ++j )
    {
        if ( blacklist[ j ] != number_to_check )
            continue;

        b = true;
        break;
    }

    if ( b )
        continue;

    const int number_to_add = number_to_check ^ special_client_key;

    copy_of_numbers.push_back( number_to_add );
}

// Check what we've copied
for ( size_t i = 0; i < copy_of_numbers.size(); ++i )
{
    std::cout << copy_of_numbers[ i ] << " ";
}

std::cout << std::endl;

And there we go. We're in some kind of hell. And this is a very simple example. So, how would this look when using STL algorithms? Glad you asked! Here's the equivalent code:

std::vector< int > numbers( 100 );
std::iota( numbers.begin(), numbers.end(), 0 );

std::array< int, 3 > blacklist = { 87, 150, 237 };

const short int special_client_key = 17727;

auto convert_value = []( int i ){ return 3 * i; };

auto is_even_doesnt_end_in_7_and_not_blacklisted = [&]( int i )->bool{
    if ( ! (i % 2 ) )
        return false;

    const int converted_value = convert_value( i );

    if ( ( converted_value % 10) != 7 )
        return false;

    const bool is_blacklisted = std::any_of( 
        blacklist.cbegin(), blacklist.cend(), 
        [&]( int i ){ return i == converted_value; }
    );

    if ( is_blacklisted )
        return false;

    return true;
};

std::vector< int > copy_of_numbers;
std::copy_if( 
    numbers.cbegin(), numbers.cend(), 
    std::back_inserter( copy_of_numbers ),
    is_even_doesnt_end_in_7_and_not_blacklisted
);

auto convert_and_hash_value = [&]( int i ) {
    return convert_value( i ) ^ special_client_key;  
};

std::transform( 
    copy_of_numbers.cbegin(), copy_of_numbers.cend(), 
    copy_of_numbers.begin(),
    convert_and_hash_value
);

std::copy( 
    copy_of_numbers.cbegin(), copy_of_numbers.cend(),
    std::ostream_iterator< int >( std::cout, " ")
);

std::cout << std::endl;

Still complicated: that's kind of unavoidable since the thing that we're trying to do isn't that simple any more. However, now it's organised into somewhat self-documenting parts:

  • There's a bunch of lambdas at the start. The names of these help to document the code in the places that they're used. For this reason, I tend to prefer named lambdas to anonymous ones in all but the simplest cases.
  • std::copy_if: Just by looking at the name we immediately know that we're copying something, but only under certain conditions. To learn that from the loop-based code, we'd have to read all the code in the loop and spot the fact that there's a copy from the numbers vector at the end of it.
  • is_even_doesnt_end_in_7_and_not_blacklisted: Reading this lets us know the conditions under which we want to copy. OK, we could have had this in the original, loop-based code. They key here is that we're forced to have some function that does this checking. In the loop-based version we just have to hope that people do. That's quite a big difference in the real world!
  • std::transform: Again, the clue is in the name: we're taking some things from one container, doing something to them and then sticking them in another container. In this case that second container happens to be the first container, but hey :)
  • convert_and_hash_value: This is the operation that we're doing on the stuff in the container. Again, we could have had it in the loop-based case, but we wouldn't be forced to (so in general we should assume that people won't do it :) )

So that's a bit of a contrived example, but in the wild there are millions of actual cases that I'm sure are much worse than that! I think that algorithms can help out :)

Your tutorial is still a fine one; I wasn't trying to rubbish it or anything like that. It's just that I think that people should be careful with language for-loops - they're the thin end of a wedge if you ask me :)

David_50 13 Junior Poster in Training

You can also exit a loop (and subroutine) with a raised exception that is caught by a specialty handler 'upstairs', which is closer to the setjmp/longjmp() of C (which essentially readjusts the stack pointer and program counter as if you had returned and then done a goto).

Good formatting is very supportive of both review, debug and future maintenance. You, too, deserve cleanly formatted code. I like to put one boolean predicate on a line, indented, use parentheses even if unnecessary due to operator priority, use curly braces always. White space is cheap, errors are costly. Of course, complete error checking is also helpful in debug! I often set complex constructs like if off with blank lines, so only one line commands are stacked tight. I like to set off declarations from conmmands with a blank line, and generally declare at the head of a curly brace as required in C, just to keep C++ and JAVA declarations easy to find and not mixed with commands. This does not preclude nice things like late model JAVA "for (type x : type_name[] )", where the declaration is essentially local to the following loop body.

Markland 0 Newbie Poster

Hi Learner010
Nice tutorial! Great to see your enthusiasm for programming in C++.

A few points:
In your first example:

int a=1;
while(a<=10)
{
cout<<a++;
cout<<endl
}

The program wouldn't compile due to an error...
cout<<endl; --- semi-colon left off.

This error is repeated in your Nested Loop example.

Finally, in your do-while example I do understand the mention of ZERO (FALSE), however it might be a good time to also mention that any integer 1,2,3....etc would produce a TRUE condition in the loop.

Overall, a great job of putting together your tutorial.

Cheers...

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.