|
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>| 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