STL Tutorial - Sequence Containers
STL Tutorial - Sequence Containers
Sequence Containers
The first question that needs to be addressed is why is there more than one sequence
container. As we will see, they are optimized for different uses. The vector class is
optimized for insertions and deletions at its end, has compatibility with C-style arrays
and provided random access iterators. Random access iterators allow constant time
retrieval of any element and are required by certain generic algorithms. The deque
(pronounced "deck") class is optimized for insertions and deletions at either end and also
provides random access iterators. Lists allow constants time insertions and deletions
anywhere. They provide only bidirectional iterators, and not random access, so they
can't be used with certain generic algorithms. Just about all you need to know about
iterators was covered in the overview lesson. Any other details will be discussed as
needed.
Vectors
We'll begin by examining each of the container classes and outlining their similarities
and differences. In particular, note the usage guidance provided at the end of the bullets
for each class. The vector class is a sequence container that is part of the Standard
Template Library, or STL. Vectors provide fast constant time access to their elements
and fast constant time insertions and deletions at their end. Additionally, since they
provide random access iterators, they may be used with any of the generic algorithms.
Here are the main characteristics of the vector class. We'll examine each point in the
examples that follow.
The vector class provides random access iterators. Random access iterators support the full
range of operations possible on iterators and on pointers. That is they support pointer style
arithmetic and comparisons. They support *, ==, !=, ++, --, +, +=, -, -=, <, <=, > and >=
operations. Random access iterators support fast retrieval from any location. Any element can
be quickly retrieved without traversing the entire container. Certain of the generic algorithms
require random access iterators as arguments. This is true of many of the sorting algorithms.
Random access iterators, which are provided by the vector container class, allow access in
constant time. This means that the time to access an element is independent of its position in
the vector. The time to access the ith element is the same as the time to access the first or
last.
1
Constant time insertions and deletions at the end. (except when the vector is grown). A vector
is grown when it reaches its capacity and additional elements are inserted. We'll see how to
control a vectors capacity when it is constructed and by using the reserve() method of the
vector class.
Linear time insertions at the beginning of the vector. To insert at the beginning of a vector, all
subsequent elements must be moved. The cost of insertions at the start of a vector is
proportional to the number of elements already in the vector. For a vector containing N
elements the cost is O(N).
Insertions or deletions in the middle are expensive operations. All subsequent elements must
be moved.
The vector class has compatibility with built-in arrays. The data in vectors is stored in a
contiguous block, just like a built-in array. A pointer to the start of a vector can be passed to
function expecting a built-in array. This is a useful feature for using some API's that expect an
array argument. A simple example is an array passed to an interface card.
The vector class provides random access iterators, so any of the generic algorithms can be
used. We will see that the list container class, which provides bidirectional iterators cannot be
used with all of the generic algorithms.
With vectors, as long as reallocation does not occur, iterators and references are not
invalidated by insertions. With a deque all iterators and references are invalidated by insertions
in the middle of the sequence.
With vectors, only iterators and references for elements past the point of erasure are
invalidated. With a deque all iterators and references are invalidated by erasures within the
middle of the sequence.
Two containers are considered equal if they are of the same size and elements in the same
position are equal as defined by the “==” operator of the type in the container.
Use when fast random access if required and insertions/deletions will be occurring only
at the end.
Use when compatibility with built-in arrays is required.
Deques
The deque class is a sequence container that is part of the Standard Template Library, or
STL. Deques, like vectors, provide random access iterators that allow constant time (fast)
access to any of their elements and can be used with any of the generic algorithms. They
also are optimized for insertions and deletions at their beginnings and ends. Due to
internal structure of deques, they tend to be slightly less efficient than vectors. That is,
manipulations using deques will operate slightly slower than the same operations on
vectors. Deques are the container of choice if you will be doing insertions and deletions
at both ends. As with vectors, if insertions and deletions will be done in the interior of the
sequence, lists may be a better choice. Here are the main characteristics of the deque
class. We'll examine each point in the examples that follow.
The deque class provides random access iterators. Random access iterators support the full
range of operations possible on iterators and on pointers. That is they support pointer style
arithmetic and comparisons. They support *, ==, !=, ++, --, +, +=, -, -=, <, <=, > and >=
operations. Random access iterators support fast retrieval from any location. Any element can
be quickly retrieved without traversing the entire container. Certain of the generic algorithms
require random access iterators as arguments. This is true of many of the sorting algorithms.
Random access iterators, which are provided by the deque container class, allow access in
constant time. This means that the time to access an element is independent of its position in
2
the deque. The time to access the ith element is the same as the time to access the first or
last.
Constant time insertions and deletions at either end.
Insertions or deletions in the middle are expensive operations. Elements must be moved.
No compatibility with built-in arrays.
The deque class provides random access iterators, so any of the generic algorithms can be
used. We will see that the list container class, which provides bidirectional iterators cannot be
used with all of the generic algorithms.
All iterators and references are invalidated by insertions in the middle of the sequence.
All iterators and references are invalidated by erasures within the middle of the sequence.
Two containers are considered equal if they are of the same size and elements in the same
position are equal as defined by the “==” operator of the type in the container.
Random access of elements is usually slower than vector. But, use if insertions and deletions
are required at both ends. Inserts and erasures at the beginning of the sequence are constant
time; vectors require linear time (time proportional to the size of the vector) to do this. All other
operations are slower.
Use when fast random access if required and insertions/deletions will be occurring at
both ends.
Lists
The list class is a sequence container that is part of the Standard Template Library, or
STL. Lists are usually implemented using a doubly-linked list as the internal structure.
They provide bidirectional iterators, rather than random access iterators like vectors and
deques. Because of this elements are access in linear time rather than constant time. To
access an element the list must be traversed. Additionally, some of the generic
algorithms, mostly those that are sorting-related, require random access iterators. So,
lists can't be used with them. Lists are optimized for insertions and deletions anywhere,
including in the interior of the sequence. Consider using lists when insertions and
deletions are to be made within the interior of the sequence. Here are the main
characteristics of the list class. We'll examine each point in the examples that follow.
The list class provides random bidirectional iterators. They support a subset of pointer style
operations: *, ==, !=, ++, --.
Lists provide linear time access to elements. The time to access an element increases as the
size of the list grows.
Constant time insertions and deletions anywhere.
Elements stored (at least conceptually) as a doubly-linked list.
The iterators are bidirectional rather than random access. This is necessary to allow constant
time insertions and deletions in the interior of a sequence. But, this does limit the use of certain
of the generic algorithms. In particular, sorting related algorithms cannot be used. The list class
provides its own sorting member functions to compensate this deficiency.
Insertions never invalidate iterators and references.
Deletions only invalidate iterators and references to the deleted elements.
Two containers are considered equal if they are of the same size and elements in the same
position are equal as defined by the “==” operator of the type in the container.
Use if insertions/deletions are needed within the sequence.
3
Constructing Sequence Containers
There are multiple constructors provided by each container class to create container objects. The different
constructors provide ways to specify the size of the container and initial values for its elements. Let's take a look
at a simple example showing the various constructors.
int main() {
vector<int> v1;
list<string> l1;
deque<float> d1;
vector<int> v2(100);
// Creates a vector of 100 ints with initial values of 0
deque<int> d2(5);
// Creates a deque of 5 ints with initial values of 0
4
// Creates a deque containing the elements of v3
return 0;
}
Before we start with examples of how to use the STL sequence container classes, let's look at the standard way
to traverse the elements of a container. Generally, two iterators are required for a loop. One is to the start of the
sequence. The second is one past the last element to be accessed. Many of the generic algorithms also require an
iterator to the start of the sequence and an iterator to one past the end of the sequence as arguments. The
element “one past” the end of the sequence is not processed; it serves as a sentinel, or stopping point. Let's
review a list of the vector member functions and operators and then look at some examples.
5
operator!= Test whether two vectors are unequal
operator< Test whether one vector is less than another
operator<= Test whether one vector is less than or equal to another
operator== Test whether two vectors are equal
operator> Test whether one vector is greater than another
operator>= Test whether one vector is greater than or equal to another
Below is an example showing how to use some of methods of the vector class. In the pages that follow, we'll
see how to combine the container classes with the generic algorithms to do some really neat things. Promise. :-)
#include <vector>
#include <iostream>
using namespace std;
int main() {
// Iterator
vector<int>::iterator iter;
cout << "The vector has " << vec1.size() << " elements" << endl;
cout << "The second element is " << vec1[1] << endl;
cout << "The third element is " << vec1.at(2) << endl;
cout << "The first element is " << vec1.front() << endl;
cout << "The last element is " << vec1.back() << endl;
6
// Create a second vector
vector<int> vec2;
return 0;
}
OUTPUT:
43789
The vector has 5 elements
The second element is 3
The third element is 7
The first element is 4
The last element is 9
4372
4372555
Vector Examples
The following example shows how to combine the use of the generic algorithms with the vector container class.
Notice how simple it is to perform common operations such as reversing the sequence or sorting. All of the
generic algorithms will be covered in a later lesson.
int main() {
7
// Create a vector, initialized by an array
int myArray[10] = {99,4,3,7,8,67,9,33,12,67};
vector<int> vec1(myArray, myArray + 10);
// Iterator
vector<int>::iterator iter;
// REVERSE
reverse(vec1.begin(), vec1.end());
// SORT
cout << "Sorted: ";
sort(vec1.begin(), vec1.end());
return 0;
}
OUTPUT:
Original Sequence: 99 4 3 7 8 67 9 33 12 67
Reversed: 67 12 33 9 67 8 7 3 4 99
Sorted: 3 4 7 8 9 12 33 67 67 99
Duplicates removed: 3 4 7 8 9 12 33 67 99
8
Deques - Member Functions
Here are the deque (pronounced deck) methods. Notice that they are the same as the vector member functions
with two exceptions. First, two member functions have been add to insert and delete from the front: pop_front
and push_front. Remember that deques are optimized to allow insertions and deletions at both their beginnings
and ends. Second, the capacity and reserve methods, present in the vector class, are not part of deque. This is
because the issues with the size of the allocated block of memory are pertinent only to vector. Vector requires a
contiguous block of memory. Deque has a different internal structure. Later in this lesson, we'll examine using
the capacity and reserve methods of vector.
Most of the methods of deque are the same as vector. The examples shown for vector will
also work for deque. Below is an example showing how to use some of methods of the deque class that were not
illustrated in the vector example. Remember that both vector and deque allow fast random access to their
9
elements, with vector being slightly faster. Vectors are optimized for insertions and deletions at their ends.
Deques are optimized for insertions and deletions at both ends.
int main() {
cout << "Deque contains " << deq1.size() << " elements" << endl;
10
cout << "Deque contains " << deq1.size() << " elements" << endl;
return 0;
OUTPUT:
2.5 3.5
33.3 67.8 2.5 3.5
Deque contains 4 elements
Deque is empty
Deque contains 0 elements
Deque Examples
The following example shows how to combine the use of the generic algorithms with the deque container class.
Notice how easy it is to search for a value and to merge two sequences. The combination of the container
classes and the generic algorithms leads to simple programming solutions. All of the generic algorithms will be
covered in a later lesson.
int main() {
if (iter == deq1.end()) {
cout << "deq1 does not contain 4" << endl;
}
else {
cout << "deq1 contains " << *iter << endl;
}
sort(deq1.begin(), deq1.end());
sort(deq2.begin(), deq2.end());
return 0;
OUTPUT:
deq1 contains 4
45678
4 6 8 10 12
4 4 5 6 6 7 8 8 10 12
Here are the list methods. Notice that most of the methods are the same as in vector and deque. This is expected
since the container classes are designed to be able to be used in place of one another. There are several new
methods. These are sort, merge, remove, replace and unique. These are optimized for use with lists and should
be used in place of the generic algorithms of the same names. The "at" method and the operator[] are not
present. This is because lists do not support random access of elements. These operators. if present, would need
to traverse the list to work and would be very inefficient.
12
end Returns an iterator to the end of the list
erase Removes element(s) at specified position(s)
front Returns a reference to the first element in the list
insert Inserts element(s) at specified position(s)
max_size Returns the maximum size of the list
merge Merges the argument list into the target list
pop_back Removes the element at the end of the list
pop_front Removes the element at the front of the list
push_back Adds an element to the end of the list
push_front Adds an element to the front of the list
remove Removes elements from a list equal to some value
reverse Reverses the elements in a list
size Returns the number of elements in the list
sort Sorts the elements of a list
splice Adds a range of elements from the argument list to the target list
swap Exchanges the contents of two lists
unique Removes adjacent duplicate elements from a list
operator!= Test whether two lists are unequal
operator< Test whether one list is less than another
operator<= Test whether one list is less than or equal to another
operator== Test whether two lists are equal
operator> Test whether one list is greater than another
operator>= Test whether one list is greater than or equal to another
Below is an example demonstrating the use of some list methods, emphasizing those unique to list
int main() {
list<string> phrase;
list<string>::iterator iter;
phrase.push_back("Welcome");
phrase.push_back("To");
13
phrase.push_back("My");
phrase.push_back("World");
phrase.reverse();
phrase.reverse();
phrase.remove("My");
for (iter = phrase.begin(); iter != phrase.end(); iter++) {
cout << *iter << " ";
}
cout << endl;
return 0;
OUTPUT:
Welcome To My World
World My To Welcome
Welcome To World
List Examples
The following example shows how to combine the use of the generic algorithms with the list container class.
All of the generic algorithms will be covered in a later lesson.
int main() {
list<char>::iterator iter;
14
for (iter = phrase.begin(); iter != phrase.end(); iter++) {
cout << *iter;
}
cout << endl;
return 0;
OUTPUT:
Hello World
Hello ZZZZZ
The final example in this lesson explores how vectors grow in size and how to use reserve to size the vector
correctly prior to use. There are two reasons to avoid reallocations (vector resizing). First, each time a resizing
occurs, a new contiguous memory buffer is allocated, the vector is copied into this new buffer and the original
(old) buffer is freed. This is an expensive operation. A second reason to avoid growing vectors is that all
iterators and references are invalidated after the insertion and growth. This may complicate coding.
#include <vector>
#include <iostream>
using namespace std;
int main() {
int N = 1024;
vector<int> v1;
vector<int> v2;
vector<int>::size_type c;
return 0;
OUTPUT:
v1 grown from 0 to 1
v1 grown from 1 to 2
v1 grown from 2 to 3
v1 grown from 3 to 4
v1 grown from 4 to 6
v1 grown from 6 to 9
v1 grown from 9 to 13
v1 grown from 13 to 19
v1 grown from 19 to 28
v1 grown from 28 to 42
v1 grown from 42 to 63
v1 grown from 63 to 94
v1 grown from 94 to 141
v1 grown from 141 to 211
v1 grown from 211 to 316
v1 grown from 316 to 474
v1 grown from 474 to 711
v1 grown from 474 to 711
v1 grown from 711 to 1066
16