A loop statement executes a group of statements repeatedly until a condition is met. There are 3 types of primitive loops in C++: for, while, and do...while.
algorithm calls are generally preferable to hand-written loops.
If you want something an algorithm already does (or something very similar), the algorithm call is clearer, often more efficient and less error prone.
If you need a loop that does something fairly simple (but would require a confusing tangle of binders and adapters if you were using an algorithm), then just write the loop.
for loops can be used to iterate over the elements of a iterator-based range, without using a numeric index or directly accessing the iterators:
vector<float> v = {0.4f, 12.5f, 16.234f}; for(auto val: v) { std::cout << val << " "; } std::cout << std::endl;
This will iterate over every element in
v, with
val getting the value of the current element. The following statement:
for (for-range-declaration : for-range-initializer ) statement
is equivalent to:
{ auto&& __range = for-range-initializer; auto __begin = begin-expr, __end = end-expr; for (; __begin != __end; ++__begin) { for-range-declaration = *__begin; statement } }
{ auto&& __range = for-range-initializer; auto __begin = begin-expr; auto __end = end-expr; // end is allowed to be a different type than begin in C++17 for (; __begin != __end; ++__begin) { for-range-declaration = *__begin; statement } }
This change was introduced for the planned support of Ranges TS in C++20.
In this case, our loop is equivalent to:
{ auto&& __range = v; auto __begin = v.begin(), __end = v.end(); for (; __begin != __end; ++__begin) { auto val = *__begin; std::cout << val << " "; } }
Note that
auto val declares a value type, which will be a copy of a value stored in the range (we are copy-initializing it from the iterator as we go). If the values stored in the range are expensive to copy, you may want to use
const auto &val. You are also not required to use
auto; you can use an appropriate typename, so long as it is implicitly convertible from the range's value type.
If you need access to the iterator, range-based for cannot help you (not without some effort, at least).
If you wish to reference it, you may do so:
vector<float> v = {0.4f, 12.5f, 16.234f}; for(float &val: v) { std::cout << val << " "; }
You could iterate on
const reference if you have
const container:
const vector<float> v = {0.4f, 12.5f, 16.234f}; for(const float &val: v) { std::cout << val << " "; }
One would use forwarding references when the sequence iterator returns a proxy object and you need to operate on that object in a non-
const way. Note: it will most likely confuse readers of your code.
vector<bool> v(10); for(auto&& val: v) { val = true; }
The "range" type provided to range-based
for can be one of the following:
Language arrays:
float arr[] = {0.4f, 12.5f, 16.234f}; for(auto val: arr) { std::cout << val << " "; }
Note that allocating a dynamic array does not count:
float *arr = new float[3]{0.4f, 12.5f, 16.234f}; for(auto val: arr) //Compile error. { std::cout << val << " "; }
Any type which has member functions
begin() and
end(), which return iterators to the elements of the type. The standard library containers qualify, but user-defined types can be used as well:
struct Rng { float arr[3]; // pointers are iterators const float* begin() const {return &arr[0];} const float* end() const {return &arr[3];} float* begin() {return &arr[0];} float* end() {return &arr[3];} }; int main() { Rng rng = {{0.4f, 12.5f, 16.234f}}; for(auto val: rng) { std::cout << val << " "; } }
Any type which has non-member
begin(type) and
end(type) functions which can found via argument dependent lookup, based on
type. This is useful for creating a range type without having to modify class type itself:
namespace Mine { struct Rng {float arr[3];}; // pointers are iterators const float* begin(const Rng &rng) {return &rng.arr[0];} const float* end(const Rng &rng) {return &rng.arr[3];} float* begin(Rng &rng) {return &rng.arr[0];} float* end(Rng &rng) {return &rng.arr[3];} } int main() { Mine::Rng rng = {{0.4f, 12.5f, 16.234f}}; for(auto val: rng) { std::cout << val << " "; } }
A
for loop executes statements in the
loop body, while the loop
condition is true. Before the loop
initialization statement is executed exactly once. After each cycle, the
iteration execution part is executed.
A
for loop is defined as follows:
for (/*initialization statement*/; /*condition*/; /*iteration execution*/) { // body of the loop }
Explanation of the placeholder statements:
initialization statement: This statement gets executed only once, at the beginning of the
forloop. You can enter a declaration of multiple variables of one type, such as
int i = 0, a = 2, b = 3. These variables are only valid in the scope of the loop. Variables defined before the loop with the same name are hidden during execution of the loop.
condition: This statement gets evaluated ahead of each loop body execution, and aborts the loop if it evaluates to
false.
iteration execution: This statement gets executed after the loop body, ahead of the next condition evaluation, unless the
forloop is aborted in the body (by
break,
goto,
returnor an exception being thrown). You can enter multiple statements in the
iteration executionpart, such as
a++, b+=10, c=b+a.
The rough equivalent of a
for loop, rewritten as a
while loop is:
/*initialization*/ while (/*condition*/) { // body of the loop; using 'continue' will skip to increment part below /*iteration execution*/ }
The most common case for using a
for loop is to execute statements a specific number of times. For example, consider the following:
for(int i = 0; i < 10; i++) { std::cout << i << std::endl; }
A valid loop is also:
for(int a = 0, b = 10, c = 20; (a+b+c < 100); c--, b++, a+=c) { std::cout << a << " " << b << " " << c << std::endl; }
An example of hiding declared variables before a loop is:
int i = 99; //i = 99 for(int i = 0; i < 10; i++) { //we declare a new variable i //some operations, the value of i ranges from 0 to 9 during loop execution } //after the loop is executed, we can access i with value of 99
But if you want to use the already declared variable and not hide it, then omit the declaration part:
int i = 99; //i = 99 for(i = 0; i < 10; i++) { //we are using already declared variable i //some operations, the value of i ranges from 0 to 9 during loop execution } //after the loop is executed, we can access i with value of 10
Notes:
forloop and is released upon termination of the loop.
initialization statementcan be modified during the loop, as well as the variable checked in the
condition.
Example of a loop which counts from 0 to 10:
for (int counter = 0; counter <= 10; ++counter) { std::cout << counter << '\n'; } // counter is not accessible here (had value 11 at the end)
Explanation of the code fragments:
int counter = 0initializes the variable
counterto 0. (This variable can only be used inside of the
forloop.)
counter <= 10is a Boolean condition that checks whether
counteris less than or equal to 10. If it is
true, the loop executes. If it is
false, the loop ends.
++counteris an increment operation that increments the value of
counterby 1 ahead of the next condition check.
By leaving all statements empty, you can create an infinite loop:
// infinite loop for (;;) std::cout << "Never ending!\n";
The
while loop equivalent of the above is:
// infinite loop while (true) std::cout << "Never ending!\n";
However, an infinite loop can still be left by using the statements
break,
goto, or
return or by throwing an exception.
The next common example of iterating over all elements from an STL collection (e.g., a
vector) without using the
<algorithm> header is:
std::vector<std::string> names = {"Albert Einstein", "Stephen Hawking", "Michael Ellis"}; for(std::vector<std::string>::iterator it = names.begin(); it != names.end(); ++it) { std::cout << *it << std::endl; }
A
while loop executes statements repeatedly until the given condition evaluates to
false. This control statement is used when it is not known, in advance, how many times a block of code is to be executed.
For example, to print all the numbers from 0 up to 9, the following code can be used:
int i = 0; while (i < 10) { std::cout << i << " "; ++i; // Increment counter } std::cout << std::endl; // End of line; "0 1 2 3 4 5 6 7 8 9" is printed to the console
Note that since C++17, the first 2 statements can be combined
while (int i = 0; i < 10) //... The rest is the same
To create an infinite loop, the following construct can be used:
while (true) { // Do something forever (however, you can exit the loop by calling 'break' }
There is another variant of
while loops, namely the
do...while construct. See the do-while loop example for more information.
In the condition of the
for and
while loops, it's also permitted to declare an object. This object will be considered to be in scope until the end of the loop, and will persist through each iteration of the loop:
for (int i = 0; i < 5; ++i) { do_something(i); } // i is no longer in scope. for (auto& a : some_container) { a.do_something(); } // a is no longer in scope. while(std::shared_ptr<Object> p = get_object()) { p->do_something(); } // p is no longer in scope.
However, it is not permitted to do the same with a
do...while loop; instead, declare the variable before the loop, and (optionally) enclose both the variable and the loop within a local scope if you want the variable to go out of scope after the loop ends:
//This doesn't compile do { s = do_something(); } while (short s > 0); // Good short s; do { s = do_something(); } while (s > 0);
This is because the statement portion of a
do...while loop (the loop's body) is evaluated before the expression portion (the
while) is reached, and thus, any declaration in the expression will not be visible during the first iteration of the loop.
A do-while loop is very similar to a while loop, except that the condition is checked at the end of each cycle, not at the start. The loop is therefore guaranteed to execute at least once.
The following code will print
0, as the condition will evaluate to
false at the end of the first iteration:
int i =0; do { std::cout << i; ++i; // Increment counter } while (i < 0); std::cout << std::endl; // End of line; 0 is printed to the console
Note: Do not forget the semicolon at the end of
while(condition);, which is needed in the do-while construct.
In contrast to the do-while loop, the following will not print anything, because the condition evaluates to
false at the beginning of the first iteration:
int i =0; while (i < 0) { std::cout << i; ++i; // Increment counter } std::cout << std::endl; // End of line; nothing is printed to the console
Note: A while loop can be exited without the condition becoming false by using a
break,
goto, or
return statement.
int i = 0; do { std::cout << i; ++i; // Increment counter if (i > 5) { break; } } while (true); std::cout << std::endl; // End of line; 0 1 2 3 4 5 is printed to the console
A trivial do-while loop is also occasionally used to write macros that require their own scope (in which case the trailing semicolon is omitted from the macro definition and required to be provided by the user):
#define BAD_MACRO(x) f1(x); f2(x); f3(x); // Only the call to f1 is protected by the condition here if (cond) BAD_MACRO(var); #define GOOD_MACRO(x) do { f1(x); f2(x); f3(x); } while(0) // All calls are protected here if (cond) GOOD_MACRO(var);
Loop"; } }
Using range-base loops, you can loop over a sub-part of a given container or other range by generating a proxy object that qualifies for range-based for loops.
template<class Iterator, class Sentinel=Iterator> struct range_t { Iterator b; Sentinel e; Iterator begin() const { return b; } Sentinel end() const { return e; } bool empty() const { return begin()==end(); } range_t without_front( std::size_t count=1 ) const { if (std::is_same< std::random_access_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category >{} ) { count = (std::min)(std::size_t(std::distance(b,e)), count); } return {std::next(b, count), e}; } range_t without_back( std::size_t count=1 ) const { if (std::is_same< std::random_access_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category >{} ) { count = (std::min)(std::size_t(std::distance(b,e)), count); } return {b, std::prev(e, count)}; } }; template<class Iterator, class Sentinel> range_t<Iterator, Sentinel> range( Iterator b, Sentinal e ) { return {b,e}; } template<class Iterable> auto range( Iterable& r ) { using std::begin; using std::end; return range(begin(r),end(r)); } template<class C> auto except_first( C& c ) { auto r = range(c); if (r.empty()) return r; return r.without_front(); }
now we can do:
std::vector<int> v = {1,2,3,4}; for (auto i : except_first(v)) std::cout << i << '\n';
and print out
2 3 4
Be aware that intermediate objects generated in the
for(:range_expression) part of the
for loop will have expired by the time the
for loop starts.