简体   繁体   中英

C++: how to loop through integer elements in a vector

I would like to loop through elements of a vector in C++.

I am very new at this so I don't understand the details very well.

For example:

for (elements in vector) {
    if () {
        check something
    else {
        //else add another element to the vector
        vectorname.push_back(n)
    }
}

Its the for (vector elements) that I am having trouble with.

You'd normally use what's called a range-based for loop for this:

for (auto element : your_vector)
    if (condition(element))
        // whatever
    else
        your_vector.push_back(something);

But note: modifying a vector in the middle of iteration is generally a poor idea. And if your basic notion is to add the element if it's not already present, you may want to look up std::set , std::map , std::unordered_set or std::unordered_map instead.

In order to do this properly (and safely), you need to understand how std::vector works.

vector capatity

You may know that a vector works much like an array with "infinite" size. Meaning, it can hold as many elements as you want, as long as you have enough memory to hold them. But how does it do that?

A vector has an internal buffer (think of it like an array allocated with new ) that may be the same size as the elements you're storing, but generally it's larger . It uses the extra space in the buffer to insert any new elements that you want to insert when you use push_back() .

The amount of elements the vector has is known as its size , and the amount of elements it can hold is known as its capacity . You can query those via the size() and capacity() member functions.

However, this extra space must end at some point. That's when the magic happens: When the vector notices it doesn't have enough memory to hold more elements, it allocates a new buffer, larger 1 than the previous one, and copies all elements to it. The important thing to notice here is that the new buffer will have a different address . As we continue with this explanation, keep this in mind.

iterators

Now, we need to talk about iterators. I don't know how much of C++ you have studied yet, but think of an old plain array:

int my_array[5] = {1,2,3,4,5};

you can take the address of the first element by doing:

int* begin = my_array;

and you can take the address of the end of the array (more specifically, one past the last element) by doing:

int* end = begin + sizeof(my_array)/sizeof(int);

if you have these addresses, one way to iterate the array and print all elements would be:

for (int* it = begin; it < end; ++it) {
    std::cout << *it;
}

An iterator works much like a pointer. If you increment it (like we do with the pointer using ++it above), it will point to the next element. If you dereference it (again, like we do with the pointer using *it above), it will return the element it is pointing to.

std::vector provides us with two member functions, begin() and end() , that return iterators analogous to our begin and end pointers above. This is what you need to keep in mind from this section: Internally, these iterators have pointers that point to the elements in the vector's internal buffer .

a simpler way to iterate

Theoretically, you can use std::vector::begin() and std::vector::end to iterate a vector like this:

std::vector<int> v{1,2,3,4,5};
for (std::vector<int>::iterator it = v.begin; it != v.end(); ++it) {
    std::cout << *it;
}

Note that, apart from the ugly type of it , this is exactly the same as our pointer example. C++ introduced the keyword auto , that lets us get rid of these ugly types, when we don't really need to know them:

std::vector<int> v{1,2,3,4,5};
for (auto it = v.begin; it != v.end(); ++it) {
    std::cout << *it;
}

This works exactly the same (in fact, it has the exact same type), but now we don't need to type (or read) that uglyness.

But, there's an even better way. C++ has also introduced range-based for :

std::vector<int> v{1,2,3,4,5};
for (auto it : v) {
    std::cout << it;
}

the range-based for construct does several things for you:

  • It calls v.begin() and v.end() 2 to get the upper and lower bounds of the range we're going to iterate;
  • Keeps an internal iterator (let's call it i ), and calls ++i on every step of the loop;
  • Dereferences the iterator (by calling *i ) and stores it in the it variable for us. This means we do not need to dereference it ourselves (note how the std::cout << it line looks different from the other examples)

putting it all together

Let's do a small exercise. We're going to iterate a vector of numbers, and, for each odd number, we are going to insert a new elements equal to 2*n .

This is the naive way that we could probably think at first:

std::vector<int> v{1,2,3,4,5};
for (int i : v) {
    if (i%2==1) {
        v.push_back(i*2);
    }
}

Of course, this is wrong ! Vector v will start with a capacity of 5 . This means that, when we try using push_back for the first time, it will allocate a new buffer.

If the buffer was reallocated, its address has changed. Then, what happens to the internal pointer that the range-based for is using to iterate the vector? It no longer points to the buffer !

This it what we call a reference invalidation . Look at the reference for std::vector::push_back . At the very beginning, it says:

If the new size() is greater than capacity() then all iterators and references (including the past-the-end iterator) are invalidated . Otherwise only the past-the-end iterator is invalidated.

Once the range-based for tries to increment and dereference the now invalid pointer, bad things will happen.

There are several ways to avoid this. For instance, in this particular algorithm, I know that we can never insert more than n new elements. This means that the size of the vector can never go past 2n after the loop has ended. With this knowledge in hand, I can increase the vector's capacity beforehand:

std::vector<int> v{1,2,3,4,5};
v.reserve(v.size()*2); // Increases the capacity of the vector to at least size*2.

// The code bellow now works properly!
for (int i : v) {
    if (i%2==1) {
        v.push_back(i*2);
    }
}

If for some reason I don't know this information for a particular algorithm, I can use a separate vector to store the new elements, and then add them to our vector at the end:

std::vector<int> v{1,2,3,4,5};
std::vector<int> doubles;

for (int i : v) {
    if (i%2==1) {
        doubles.push_back(i*2);
    }
}
// Reserving space is not necessary because the vector will allocate
// memory if it needs to anyway, but this does makes things faster
v.reserve(v.size() + doubles.size());

// There's a standard algorithm (std::copy), that, when used in conjunction with
// std::back_inserter, does this for us, but I find that the code bellow is more
// readable.
for (int i : doubles) {
    v.push_back(i);
}

Finally, there's the old plain for , using an int to iterate. The iterator cannot be invalidated because it holds an index, instead of a pointer to the internal buffer:

std::vector<int> v{1,2,3,4,5};

for (int i = 0; i < v.size(); ++i) {
    if (v[i]%2==1) {
        doubles.push_back(v[i]*2);
    }
}

Hopefully by now, you understand the advantages and drawbacks of each method. Happy studies!


1 How much larger depends on the implementation. Generally, implementations choose to allocate a new buffer of twice the size of the current buffer.

2 This is a small lie. The whole story is a bit more complicated: It actually tries to call begin(v) and end(v) . Because vector is in the std namespace, it ends up calling std::begin and std::end , which, in turn, call v.begin() and v.end() . All of this machinery is there to ensure that the range-based for works not only with standard containers, but also with anything with a proper implementation for begin and end . That includes, for instance, regular plain arrays.

Here is the quick code snippet using iterators to iterate the vector-

#include<iostream> 
#include<iterator> // for iterators to include
#include<vector> // for vectors to include
using namespace std; 
int main() 
{ 
    vector<int> ar = { 1, 2, 3, 4, 5 }; 

    // Declaring iterator to a vector 
    vector<int>::iterator ptr; 

    // Displaying vector elements using begin() and end() 
    cout << "The vector elements are : "; 
    for (ptr = ar.begin(); ptr < ar.end(); ptr++) 
        cout << *ptr << " "; 

    return 0;     
} 

Article to read more - Iterate through a C++ Vector using a 'for' loop . Hope it will help.

Try this,

#include<iostream>
#include<vector>

int main()
{
    std::vector<int> vec(5);

    for(int i=0;i<10;i++)
    {
      if(i<vec.size())
           vec[i]=i;
      else
           vec.push_back(i);
    }

    for(int i=0;i<vec.size();i++)
           std::cout<<vec[i];


    return 0;
}

Output:

0123456789
Process returned 0 (0x0)   execution time : 0.328 s
Press any key to continue.

There are a lot of ways to do it:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> data = { 0, 1, 2, 3, 4, 5 };

    for (int i = 0; i < data.size(); i++)
        std::cout << data[i] << " ";
    std::cout << std::endl;

    for (std::vector<int>::size_type i = 0; i < data.size(); i++) // preferred over int
        std::cout << data[i] << " ";
    std::cout << std::endl;

    for (auto i : data)
        std::cout << i << " ";
    std::cout << std::endl;

    for (std::vector<int>::iterator i = data.begin(); i < data.end(); i++)
        std::cout << *i << " ";
    std::cout << std::endl;
    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM