C++ Vectors
Carlos Moreno

Copyright © 2000-2002

 

Arrays are a programming tool that provide a mechanism to store a group of values under a single name.  The values can be any available data type (e.g., int, double, string, etc.).  In C++, we talk about vectors, rather than arrays.

Vectors are declared with the following syntax:

    vector<type> variable_name (number_of_elements);

Type refers to the data type of the elements of the array; variable_name is the name that we assign to the array, and the optional number_of_elements may be provided to indicate how many elements the array will contain.

Examples of vector declarations are the following:

    vector<int> values (5);      // declares a vector of 5 integers
   vector<double> marks (20);   // declares a vector of 20 doubles

When we use vectors in our programs, we must provide, at the top of the file, the line:

    #include <vector>

After a vector has been declared specifying a certain number of elements, we can refer to individual elements in the vector using square brackets to provide a subscript or an index.

In the following example, the program asks the user for the marks for a group of 20 students and stores them in a vector:

     #include <iostream>
    #include <vector>
    using namespace std;

    int main()
    {
        vector<double> student_marks (20);

        for (int i = 0; i < 20; i++)
        {
            cout << "Enter marks for student #" << i+1 << ": ";
            cin >> student_marks[i];
        }

        return 0;
    }

The first statement (right after main) declares a vector called student_marks with capacity to hold 20 values of type double.  These values can be accessed individually as student_marks[0] to student_marks[19].  The for loop has the counter i go from 0 to 19, allowing access to each individual element in a sequential manner, starting at 0 and going through each value from 0 to 19, inclusively.

Vectors have one important advantage with respect to arrays in C, C++ and in other languages:  vectors can be resized during the execution of the program to accommodate extra elements as needed.  Many other programming languages suffer the limitation of fixed-size arrays;  that is, once the arrays are declared with a size, they can not be resized later in the program.

In the example above, if we don't know in advance that there are 20 students, we could ask the user how many students are there, and resize the vector accordingly.

This is shown in the example below:

     #include <iostream>
    #include <vector>
    using namespace std;

    int main()
    {
        vector<double> student_marks;     // no size specified: vector contains
                                          // zero elements

        int num_students;

        cout << "Number of students in this group: ";
        cin >> num_students;

        student_marks.resize (num_students);

        for (int i = 0; i < num_students; i++)
        {
            cout << "Enter marks for student #" << i+1 << ": ";
            cin >> student_marks[i];
        }

        return 0;
    }

Notice that the valid subscripts for a vector with num_students elements are 0 to num_students-1.  For that reason, the for loop goes from 0, while i is less than num_elements.

It's always a better idea to control for loops using the size() method of vector.  That way, we make sure that we loop only through the right subscript values, and we avoid the risk of accidentally exceeding the limits of the vector:

    for (int i = 0; i < student_marks.size(); i++)

The difference in this case seems insignificant, and it almost sounds unnecessary to use the size() method;  but again, it's always a good idea to stick to good programming practices that may be very convenient in larger or more complex programs.

In some situations, we can't determine the number of elements before reading them.  That is, we may have to read numbers and store them in an array to then determine when to stop reading values.  In such situations, the trick of resizing the vector is not an option.

Vectors provide a convenient way of handling this type of situation.  We can use the push_back() method to append one element at the end of the array.  The operation includes resizing to one more element to accommodate for the extra element, and storing the given value at the end of the array.

The example below shows how to use the push_back() method to accept numbers from the user and store them in a vector, until the user indicates that there are no more numbers.  After all the numbers were entered, we use a for loop to print them in the order that they were entered (this is just for illustrations purpose -- it's not particularly useful to do that!):

    #include <iostream>
    #include <vector>
    using namespace std;

    int main()
    {
        vector<double> student_marks;
        int number;
        char answer;

        cout << "Do you want to enter numbers (y/n)? ";
        cin >> answer;

        while (answer == 'y')
        {
            cout << "Enter value: ";
            cin >> number;

                // Now that we have the number from the user,
                // append it at the end of the vector

            student_marks.push_back(number);

            cout << "Do you want to enter more numbers (y/n)? ";
            cin >> answer;
        }

                // Now print all the values;  notice that we didn't need to
                // count how many elements were entered:  we can always use
                // the size() method to ask student_marks how many are there!

        for (int i = 0; i < student_marks.size(); i++)
        {
            cout << "Student #" << i+1 << '\t' << student_marks[i];
        }
                // '\t' is the code for the TAB character

        return 0;
    }


Iterators:  an alternative way of accessing the elements

Iterators provide an arguably more flexible way of accessing elements in a vector.   They allow you to refer directly to the elements of the vector and iterate over the entire vector.

To work with iterators, we first declare a variable of type iterator.  When declaring the iterator, we have to specify that it is an iterator for a vector of the given type, as shown in the example below:

    vector<int>::iterator i;

This statement declares an iterator to (not surprisingly) iterate over the elements of a vector of ints.

The iterator variable (in the above example, i) allows us to access a particular element, and to move to the next element  (those two operations are clearly necessary if we want to iterator over all the elements of the vector).

To access the element "pointed to" by the iterator, we place a * operator in front.  To advance to the next element, we use the ++ operator (either pre-increment or post-increment, although with iterators, it is slightly more efficient to use the pre-increment form).

So, the following fragment of code prints an element and moves the itertator to the next element:

    cout << *i << endl;
    ++i;

We need to do this in a loop, which means that we need to make our iterator start at the beginning of our vector, and loop while it still is within the vector's elements.  To do this, we use the .begin() and .end() methods of vector.  These methods give us iterators positioned at the beginning and at the end of the vector  (more specifically, .end() will give us an iterator positioned at one-past-the-last element, such that we can use it to control the loop).

The following fragment of code illustrates the use of iterators to print all the elements of a vector:

        // values has been declared as:  vector<int> values;
    for (vector<int>::iterator i = values.begin(); i != values.end(); ++i)
    {
        cout << *i << endl;
    }

Notice that, because values.end()is an iterator placed at one-past-the-last element, when i is equal to that value, then we exit the loop, since that means that we are now outside the range of the vector's elements.

If we have a vector of strings and want to print the elements (the strings) and their lengths, we could do the following:

        // names has been declared as:   vector<string> names;
    for (vector<string>::iterator i = names.begin(); i != names.end(); ++i)
    {
        cout << "Name: " << *i << "\tLength: " << (*i).length() << endl;
    }

Instead of writing (*i).length()  (we need the parenthesis, because the dot operator has higher precedence than the unary * operator), we could simply write i->length(), as shown in the fragment below, which is exactly equivalent to the above:

    for (vector<string>::iterator i = names.begin(); i != names.end(); ++i)
    {
        cout << "Name: " << *i << "\tLength: " << i->length() << endl;
    }
 


A Few Algorithms from the Standard Library

One of the additional benefits of using iterators is that the Standard Library algorithms are compatible with this idiom.  In other words, Standard Library algorithms work with iterators, rather than directly with vectors  (for a more advanced discussion on the benefits of this approach, you may take a look at my STL Tutorial -- that tutorial, however, requires a relatively advanced knowledge of C++ features, including templates, classes and object oriented;  use it at your own risk  :-)).

The Standard Library algorithms deal with "sequences" rather than with containers (e.g., vectors).  A sequence is specified by a pair of iterators that indicate the beginning of the sequence and the end (given by an iterator to one-past-the-last element of the sequence).

So, for example, the algorithm sort may be used to sort the elements of a vector.  We would use .begin() and .end() to obtain the sequence of elements that sort expects.  The line below shows an example of sorting the values of a vector:

    sort (values.begin(), values.end());

This is just one of the many available algorithms from the Standard Library.  I will mention only a few of the simple ones  (some algorithms require knowledge of more advanced techniques to work with them -- again, you may check the STL Tutorial if you want to learn more and have familiarity with those advanced issues).

The following sample program illustrates several useful algorithms -- notice that we include both <algorithm> and <numeric>  (the <numeric> part is for the accumulate algorithm):

    #include <iostream>
    #include <vector>
    #include <string>
    #include <algorithm>
    #include <numeric>

    using namespace std;

    int main()
    {
        vector<string> student_names;
        vector<double> student_marks;

        do
        {
            string name;
            cout << "Enter student name (Enter to finish): ";

            getline (cin, name);

                // Now that we have the name from the user,
                // append it at the end of the vector if valid
            if (name != "")
            {
                student_names.push_back (name);
            }
 
        }
        while (name != "");

            // sort them alphabetically
        sort (student_names.begin(), student_names.end());

            // Now enter grades for each student

        student_marks.resize (student_names.size());

        vector<string>::iterator s;
        vector<double>::iterator m;

        for (s = student_names.begin(), m = student_marks.begin();
             s != student_names.end();
             ++s, ++m)

        {
            do
            {
                cout << "Enter marks (0 to 100) for " << *s << ": ";
                cin >> *m;

                if (*m < 0 || *m > 100)
                {
                    cout << "Error: marks must be between 0 and 100" << endl;
                }
            }
            while (*m < 0 || *m > 100);

        }

        if (! students_marks.empty())    // equivalent to student_marks.size() != 0
        {
            cout << "There are " << count (student_marks.begin(), student_marks.end(), 100) 
                 << " students that got highest mark!" << endl;  

            cout << "Lowest mark: "
                 << *min_element (student_marks.begin(), student_marks.end())
                 << "\nHighest mark: "
                 << *max_element (student_marks.begin(), student_marks.end()) << endl;

            cout << "Average: "
                 << accumulate (student_marks.begin(), student_marks.end(), 0) /
                    student_marks.size() << endl;
        }

        r
eturn 0;
    }


Some other useful algorithms (not shown in the above demo) are:

Algorithm
   
Description and Example of use
 
find

Finds the first occurence of a given value, and returns an iterator to it.  If the value is not found in the sequence, it returns end.

if (find (marks.begin(), marks.end(), 0) == marks.end())
{
    // 0 is not found in the sequence
}

 
replace

Replaces all the occurences of a given value with another one.

replace (names.begin(), names.end(), "", "not valid");
 
reverse

Reverses the order of the elements in the sequence.

sort (values.begin(), values.end());
reverse (values.begin(), values.end());
   
// the sequence is now sorted in descendant order



random_shuffle

Reorders the sequence randomly, such that every possible permutation is equally probable.

random_shuffle (cards.begin(), cards.end());


An important detail to notice:  these algorithms may be used with strings, to iterate over the characters of the string.  For instance, if you want to replace all of the spaces with underscores, you could simply do:

    replace (str.begin(), str.end(), ' ', '_');

In some cases, however, it may be better to use the built-in string facilities  (for instance, why using the find algorithm with strings, if we have the find method as part of the string manipulation facilities?)


  For some examples see: C++ examples



Return to main page   Return to Tutorials