C++ (basic): for, references and syntax

Asked

Viewed 391 times

8

My teacher presented this function to us:

void escala(std::vector<double> &v, double fator)

{
    for (auto &vi:v){
    vi *= fator;
    }
}

It serves to multiply all the elements of a vector by a pre-defined factor.

I have 2 questions:

1. I understood why the vector v is passed as a reference in the function argument. However, because 'vi', inside the 'for', is passed as reference also? (otherwise, it doesn’t work, I have tested)

2. Can anyone explain to me how this alternative for syntax works? Maybe it is even because I do not understand so well that I am in doubt 1. I did not find anything on the internet about, always appears the standard syntax 'for( ; ; )'.

2 answers

8

To answer the first question, we can start with the second question, which will make the reasons for the first question more obvious.

The construction (called the range-based for, or is based on interval):

 for (declaração : expressão) corpo

It is only syntactic sugar and is transformed to the following construction:

{
    auto && __range = expressão ;
    auto __begin = begin_expr; // normalmente begin_expr é begin(__range)
    auto __end = end_expr; // normalmente end_expr é end(__range)
    for ( ; __begin != __end; ++__begin) {
        declaração = *__begin;
        corpo
    }
}

Note that this transformation conforms to .

That version of for works with any expression that can be iterated. In your example, std::vector can be iterated as it has member functions std::vector::begin and std::vector::end, that return iterators to the beginning of the vector and to the end, respectively. For example:

#include <cassert>

int main() {
    std::vector<int> v{1, 2, 3};
    int soma = 0;
    for (int i : v)
        soma += i;
    assert(6 == soma);
}

If we just take the for from the above example, we see that

  • int i is the statement,
  • v is the expression and
  • soma += i; is the body.

In the transformation (a little more simplified), we will have:

{
    auto __begin = begin(v);
    auto __end = end(v);
    for ( ; __begin != __end; ++__begin) {
        int i = *__begin;
        soma += i;
    }
}

Notice that the part int i = *__begin is where each element of the interval (the vector v in that case) is assigned to the variable i. The body of for comes next.

The for interval-based, As said before, it works with any interval. Therefore, any container that can achieve an iterator of its beginning and end will work with this construction of for (e. g. arrays fixed-size, some standard library container, such as std::map, std::vector, std::array etc.).

If the concept iterator is unfamiliar, see it as an object that can traverse the elements of a container. For example, a pointer pro first element of the vector can be considered an iterator, because when we increment this pointer, we get access to the elements in sequence of that vector, until we reach the end, which is a pointer to after the last element (meaning the arrival, or end of iteration). In that case, __begin and __end would be such iterators, where __begin has access to the first element (and is incremented during the loop to access the following elements) and __end represents the end of the iteration. When __begin is equal to __end, means the loop ended and all elements were iterated.

Returning to the first question, we see that it is now obvious why we declare vi as a reference. If we look at your example:

for (auto &vi : v) {
    vi *= fator;
}

We have auto &vi as statement, v as expression and vi *= fator as corpo:

{
    auto __begin = begin(v);
    auto __end = end(v);
    for ( ; __begin != __end; ++__begin) {
        auto &vi = *__begin;
        vi *= fator;
    }
}

When we assign vector elements v in each iteration, we achieve the reference for that element. If we had the statement as just auto vi (or double vi), we would be copying the element to the variable vi. Since the intention is to modify the element, a copy would not work, we want write access to the element, so the reference is used.

Heed, the use of __ as prefix of names are reserved by standardization. Do not write names like this on your own, as they are used exactly to prevent user code from conflicting with standard library code, syntactic sugars or intrinsic compiler functions.

5

  1. Because you need to continue keeping the reference to the object within the code. If you do not use &vi in the for, will not change the original object that was passed by reference, but a copy of it existing within the for only.

  2. This building is called range-based and is used to traverse all array items, resembling the command foreach existing in other languages. Simply put, it would be like saying "for each item of &v, use the variable vi", that is, in each interaction of for an element shall be associated with variable vi, as if it were:

vi = &v[0], vi = &v[1], and so on.

More about range-based loop you can read here: https://msdn.microsoft.com/pt-br/library/jj203382.aspx

Browser other questions tagged

You are not signed in. Login or sign up in order to post.