C++ Reference Material
STL Tutorial Introduction
Given the complexity and power of the STL (the Standard Template Library of C++), and the fact that its use is not exactly intuitive for beginners, putting together even a "tutorial introduction" becomes a somewhat daunting task right from the get go. Especially if one wants to keep it short, as I do.
So, how shall we proceed? First, we shall assume a couple of things:
Thus, you know that the STL has things called containers, iterators and algorithms. Our purpose here will be to see how these three things come together in what might be called "typical STL fashion". Though there is much else to learn about the STL, once you have a handle on this, you have made considerable progress toward becoming an STL user.
Fortunately, we can begin by drawing some analogies between STL containers, iterators and algorithms and things you already know about.
We begin by noting that, at a superficial level at least, any STL container is analogous to an array, in that it is something that allows you to store and retrieve elements. All STL containers are implemented by template classes, which means that any given container can easily be used to hold elements of virtually any type. So, you have a choice of containers and you can choose what kind of values you would like to store in whatever container you happen to be using at the time. For example, the STL has a container called vector, which is the most array-like of all the STL containers, and in fact it is often billed as a "better array". This is everyone's favorite STL container, it is ofter referred to as the the STL's "workhorse" container (because you should probably use it unless there is a good reason to use something else), and we will use it in the examples which follow.
Your C++ experience will provide you with an opportunity to use pointers to "point at" and manipulate array elements. STL iterators are used to "point at" and manipulate STL container elements in a manner quite analogous to the way in which pointers are used in the array context. But ... each STL container class will have its own kind of iterator, one which is most appropriate for that particular container. And, in general, STL iterators are much "safer" to use than old-fashioned pointers. Interestingly, though, those "old-fashioned" pointers can be, and often are, used with STL containers in the same way that STL iterators themselves are used, bolstering the claim that an STL iterator is, after all, "just a generalized pointer to an array".
And algorithms ... well, you know what they are. Often they come in the form of stand-alone or "free" functions which usually take in some data via a parameter list and perform some task and/or return one or more computed values. And all of this is true of STL algorithms as well. But ... where STL algorithms get their additional power and flexibility is from the fact that many of their parameters are STL iterators rather than STL containers. Since these iterators can point at the elements of many different kinds of STL containers this means that STL algorithms have a "built-in" ability to deal with a variety of containers containing a variety of element types. In other words, an algorithm can work on the elements of a container without knowing or caring what kind of container it is.
And now, let's cut to the chase. Consider the line of C++ code
vector<int> v(10);
which declares a "vector of integers" of size 10, i.e. a place to store 10 values of type int. To include such a declaration in your program you would also need the following compiler include directive:
#include <vector>
The above declaration of v is somewhat analogous to the line of code
int a[10];
which declares an ordinary C++ "array of integers" of size 10, also a place to store 10 values of type int. So ... what's the difference, and why the big fuss about vectors? Well ... consider the following points:
int i; for (i=0; i<10; i++) cout << a[i];If we try the same thing with vector v, i.e.
int i; for (i=0; i<10; i++) cout << v[i];it may in fact work, but an up-to-date compiler will at least warn you that the index variable i has the "wrong" type, and the code should really be written like this:
vector<int>::size_type i; for (i=0; i<10; i++) cout << v[i];The reason for this is that the int data type has values (all the negative ones) that cannot possibly be valid indices for a vector. Thus the vector class would like to be sure that we always use non-negative index values when accessing vector components and provides for us a typedef alias called size_type (an unsigned integral type) for just this purpose.
vector<int>::size_type i; for (i=0; i<v.size(); i++) cout << v[i];which will work for any vector of whatever size. We no longer need to know and remember that (in the above case) the size of the vector v is 10.
v.push_back(17);to add the value 17 (for example) to the end of the vector. After execution of this statement the size of the vector v has increased to 11 and v[10] contains the value 17. (Remember that the first 10 values of v are all 0, and are found in the locations with indices in the range 0..9.) This ability of a vector to "grow" in order to accommodate the addition of new values to its "back end" is one of the major advantages of vectors over arrays.
int a[0];makes no sense, since nothing can ever be stored in it, declaring an empty vector and then adding some values makes perfect sense:
vector<int> v; //construct an empty vector to hold some integers v.push_back(3); // v now contains 3 in v[0] v.push_back(6); // v now contains 3 in v[0], 6 in v[1] v.push_back(4); // v now contains 3 in v[0], 6 in v[1], 4 in v[2]This is clearly a rather laborious and inconvenient way to get values into a vector. Since an array containing the same values can be initialized with the statement
int a[3] = {3, 6, 4};or even
int a[] = {3, 6, 4};one might hope that something like
vector<int> v = {3. 6, 4}; //syntax error!would work for vectors, but unfortunately (as the comment indicates) this does not work. However, a useful way of combining the old and the new to initialize a vector with a sequence of specific values is illustrated by the following two lines of code:
int a[] = {3, 6, 4}; //initialize array a first vector<int> v(a, a+3); //construct vector v containing values from aThis vector definition (declaration with initialization) requires a few words of explanation. We are using another constructor from the vector class, this time the one that takes two input parameters which are "iterators" (or in this case, ordinary pointers acting as iterators) pointing at the beginning element, and "one-past-the-last" element of a range of values to be placed in the newly constructed vector. In this case the first parameter (a), is a pointer to the first element of the array a, and the second parameter (a+3) is a pointer to the "first position beyond the last element" of the array a. This notion of a "range" of elements being determined by a pair of iterators (or pointers) pointing to the first element and "one-past-the-last" element (as opposed to the last element itself) of a sequence of values permeates the STL and is something beginning STL programmers need to get a handle on right up front. An even better way to create v with the values from a would be
int a[] = {3, 6, 4}; //initialize array a first vector<int> v(a, a+sizeof(a)/sizeof(int)); //construct v with values from awhich works independently of the size of the array a.
int a[] = {3, 6, 4}; //initialize array a first vector<int> v(a, a+sizeof(a)/sizeof(int)); //construct v with values from a vector<int>::iterator p; //declare an iterator for a vector of integers for (p=v.begin(); p!=v.end(); p++) cout << *p << " "; //output all values of vNote that the iterator object p is incremented in the same way that an ordinary array pointer would be incremented, and that *p is used to dereference p and get access to the value to which p is pointing, once again in the same way that a pointer would be dereferenced to gain access to an array element.
#include <algorithm> ... sort(v.begin(), v.end());which shows the STL sort algorithm being used to sort the values in a vector. By default this algorithm sorts in ascending order and will work for a vector of any items whatsoever, as long as operator<() is defined for the components of v, so that the sort algorithm knows what it means for one component to precede another. If a sort in descending order is required instead, this can be accomplished with
#include <algorithm> #include <functional> ... sort(v.begin(), v.end(), greater<T>());in which T must be the component type of v, and the expression greater<T>() provides a function object (from STL's built-in collection of function objects, available from the header <functional>) that tells the sort algorithm to change its thinking to consider one component as preceding another when it is greater than the other (which requires that operator>() be defined for the component type of the vector). Many STL algorithms have an overloaded version that permits their default behavior to be altered by providing an additional function object parameter (or ordinary function parameter) which is used by the algorithm to help it perform its task in a different way.
As a second example of an STL algorithm applied to a vector, consider
#include <algorithm> ... cout << *max_element(v.begin(), v.end()) << endl;which outputs the "largest" value in the vector (whatever that means for the given vector). The max_element algorithm actually returns an iterator object pointing to the largest value, rather than the largest value itself. This is why we have to dereference the return value of the algorithm: to obtain, for output, the value to which the returned iterator object is pointing.
v1 = v2;and the relational operators also work with vectors, so that you can make tests like
if (v1 == v2) ...or
if (v1 < v2) ...provided you understand how these things work. Assignment of vectors works just like assignment always works (or should work) ... the thing on the right of the assignment operator replaces the thing on the left of the assignment operator. The relational operators are potentially a little trickier. We can summarize the situation by saying the comparisons are "lexicographic". That's a big word meaning this: when two vectors are being compared (to see if one is "less than" the other, for example), corresponding pairs of components are compared (starting at the beginning) until a determination can be made. How the vectors compare is determined by how the first two components that are different compare. That is, if the component in the first vector is less than the corresponding component in the second vector, then the first vector is itself less than the second vector. This is just like "alphabetic" comparison, but instead of comparing letters you are comparing components (whatever they may be). This of course implies that components can in fact be compared.
In this brief introduction you have seen just one of the STL container types (vector) and you have seen how much more versatile vectors are than arrays. You have also seen how STL algorithms can be used to manipulate or "process" the components of a vector through iterators. All of this just scratches the surface, of course, but it should give you a reasonable sense of "how things work" in the STL, at least enough to get you started.
What you need to do now is learn about some of the other STL containers (deque, list, map, multimap, set, multiset) and container adaptors (stack, queue, priority_queue), and get familiar with as many of the STL algorithms as you can manage. In doing so you will also want to learn more about how iterators behave and about the iterator adaptors for streams that are very convenient for I/O operations. And do not forget that function objects can be usefully employed in many different situations when using the STL, so you should make sure that function objects are in your personal STL toolkit if you expect to become a competent STL programmer.
As you learn about these new STL features, you should be struck by how much what you already know about vectors carries over to the other containers, particularly if your very next step is to digest the detail page on vectors provided on this web site. So ... that's what we recommend. Do note, however, that that page is a reference page, not a tutorial page. But you will also find there a large number of sample programs that should show you nearly all you would ever need to know about vectors, and a few other things along the way.