Containers and Iterations
- Describe the container templates of the Standard Library
- Identify which container templates are optimized for which operations
- Introduce the concept of iterators and show how to use them with container objects
"By default, use vector when you need a container" Stroustrup (2014)
Object-oriented languages include library support for containers of object of fundamental, built-in and user-defined types. The container classes and container adapters of the STL provide shells for elemental collections of types. The sequential container classes order their elements in sequence. The container adapters provide interfaces for encapsulating access to elements of specific containers. The iterators of the STL provide access for iterating on the collections. These iterators provide the glue that enables use of the STL algorithms with these collections.
This chapter introduces several sequential container classes along with their member functions. This chapter also introduces the concept of iterators and presents examples of their use with sequential container classes. The algorithms of the STL are described in the following chapter.
Sequential Containers
The STL defines the sequential container classes in the form of class templates. These templates include:
array
- contiguous storage of fixed sizevector
- contiguous storage of variable sizedeque
- non-contiguous storage of variable size, double-ended queueforward_list
- non-contiguous storage of variable size, singly linked listlist
- non-contiguous storage of variable size, doubly linked list
An array
, unlike a built-in array knows its size and can be copied and assigned, but is allocated on the stack rather than on free store, so its size cannot be changed.
vector
The vector
class template defines classes that manage data structures that can change in size but have their elements ordered in sequence. These classes use contiguous storage locations for their elements and are nearly as efficient as arrays. They store their elements on free store and can adjust their size as required. The class template is optimized for fast random access as well as addition and deletion of elements at the back-end of a sequence.
The vector class template is defined in the header file <vector>
.
A vector object admits comparison, assignment, expansion, element addition, and element deletion. Its member functions include:
vector()
- default constructor; creates a container with no elementsvector(int n)
- creates a container withn
elementsvector(int n, const T& t)
- creates a container withn
elements, each initialized to valuet
vector(const vector& v)
- copies the contents of containerv
into the current objectvector(vector&& v) noexcept
- moves the contents of containerv
into the current object~vector()
- destroys the containervector& operator=(const vector& v)
- copies the contents of containerv
into the current objectvector& operator=(vector&& v) noexcept
- moves the contents of containerv
into the current objectsize_t size() const
- returns the number of elements in the current objectsize_t capacity() const
- returns the current capacity of the current objectbool empty() const
- returnstrue
if the current object has no elementsT& operator[](size_t i)
- returns a reference to elementi
const T& operator[](size_t i) const
- returns an unmodifiable reference to elementi
T& at(size_t i)
- returns a reference to elementi
and checks bounds - throwing an exceptionconst T& at(size_t i) const
- returns an unmodifiable reference to elementi
and checks bounds - throwing an exceptionT* data() noexcept
- returns a pointer to the underlying arrayconst T* data() const noexcept
- returns a pointer to the underlying unmodifiable arrayT& front()
- returns a reference to the first elementconst T& front() const
- returns an unmodifiable reference to the first elementT& back()
- returns a reference to the last elementconst T& back() const
- returns an unmodifiable reference to the last elementvoid push_back(const T& t)
- adds elementt
after the last element in the containervoid pop_back()
- removes the last element from the containervoid clear()
- removes all elements from the container
The two at(int)
member functions check that the index received is within bounds, while the subscripting operator does not check bounds for improved efficiency.
The following program creates an initially empty sequence of double
s named prices
, stores three elements, changes the first element, and discards the last element:
// Sequential Containers - Vectors
// vector.cpp
#include <vector>
#include <iostream>
int main()
{
std::vector<double> prices; // initially empty
if(prices.empty()) // is prices empty?
std::cout << "prices is empty" << std::endl;
prices.push_back(10.43); // add 10.43
prices.push_back(20.54); // add 20.54
prices.push_back(32.43); // add 32.43
for(int i = 0; i < prices.size(); i++)
std::cout << prices[i] << " ";
std::cout << std::endl;
prices.front() = 54.11; // change 1st element
prices.pop_back(); // remove last element
for(int i = 0; i < prices.size(); i++)
std::cout << prices[i] << " ";
std::cout << std::endl;
}
prices is empty
10.43 20.54 32.43
54.11 20.54
Since a vector
object stores its elements contiguously, accessing the address of its first element returns a pointer to its contents. To get the pointer to the first element of object v
, we can use either &v[0]
or &v.front()
.
The iterators described below provide an alternative method of access to ranges of elements within a vector
container.
deque
The deque
class template defines classes that manage doubly-ended queues that can change in size from either end and have elements ordered in sequence. Insertion and deletion of elements at either end is efficient, but elements can be scattered throughout memory in different chunks of storage and contiguous storage is not guaranteed.
The deque
class template is defined in the header <deque>
.
A deque
object admits comparison, assignment, expansion, element addition, and element deletion. Its member functions include:
deque()
- default constructor; creates an empty containerdeque(int n)
- creates a container withn
elementsdeque(int n, const T& d)
- creates a container withn
elements, each initialized to valued
deque(const deque& d)
- copies the contents ofd
into the current objectdeque(deque&& d) noexcept
- moves the contents ofd
into the current object~deque()
- destroys the containerdeque& operator=(const deque& d)
- copies the contents of containerd
into the current objectdeque& operator=(deque&& d) noexcept
- moves the contents of containerd
into the current objectsize_t size() const
- returns the number of elements in the current objectsize_t capacity() const
- returns the current capacity of the current objectbool empty() const
- returns true if the current object has no elementsT& operator[](size_t i)
- returns a reference to elementi
const T& operator[](size_t i) const
- returns an unmodifiable reference to elementi
T& at(size_t i)
- returns a reference to elementi
, checks bounds - throwing an exceptionconst T& at(size_t i) const
- returns an unmodifiable reference to elementi
, checks bounds - throwing an exceptionT& front()
- returns a reference to the first elementconst T& front() const
- returns an unmodifiable reference to the first elementT& back()
- returns a reference to the last elementconst T& back() const
- returns an unmodifiable reference to the last elementvoid push_back(const T& t)
- adds elementt
after the last element in the containervoid push_front(const T& t)
- adds elementt
before the first element in the containervoid pop_back()
- removes the last element from the containervoid pop_front()
- removes the first element from the containervoid clear()
- removes all elements from the container
The two at(int)
member functions check that the index received is within bounds, while the subscripting operator does not check bounds for improved efficiency.
The following program creates a double-ended queue of double
s named prices
and initializes each element to 10.50, changes the last element's value to 32.43, removes the first element, assigns the queue to another queue, adds 5.64 and 20.31 to the front of that queue, and adds 10 to its second element:
// Sequential Containers - Double-Ended Queues
// deque.cpp
#include <deque> // for deque template
#include <iostream>
int main()
{
std::deque<double> prices(3, 10.50), costs;
prices.back() = 32.43; // reset last
prices.pop_front(); // remove first
for(int i = 0; i < prices.size(); i++)
std::cout << prices[i] << " ";
std::cout << std::endl;
costs = prices;
costs.push_front(5.64); // add 5.64
costs.push_front(20.31); // add 20.31
costs.at(1) += 10.0; // add 10.0
for(int i = 0; i < costs.size(); i++)
std::cout << costs[i] << " ";
std::cout << std::endl;
}
10.5 32.43
20.31 15.64 10.5 32.43
Unlike a vector
, offsetting a pointer to another deque element causes undefined behavior.
The iterators described below provide an alternative method of access for ranges of elements within a deque
container.
list
The list class template defines classes that manage doubly linked sequences that are optimized for insertion and removal of elements anywhere throughout the list, particularly in larger sequences. This template is sub-optimal for fast random access. For fast random access, the vector
and deque
templates are more efficient.
The list class template is defined in the header <list>
.
A list
object admits comparison, assignment, expansion, element addition, and element deletion. Its member functions include:
list()
- default constructor; creates an empty containerlist(int n)
- creates a container withn
elementslist(int n, const T& val)
- creates a container withn
elements, each initialized to valueval
list(const list& other)
- copies the contents ofother
into the current objectlist(list&& other) noexcept
- moves the content ofother
into the current object~list()
- destroys the containerlist& operator=(const list& other)
- copies the contents ofother
into the current objectlist& operator=(list&& other) noexcept
- moves the contents ofother
into the current objectsize_t size() const
- returns the number of elements in the current objectbool empty() const
- returnstrue
if the current object has no elementsT& front()
- returns a reference to the first elementconst T& front() const
- returns an unmodifiable reference to the first elementT& back()
- returns a reference to the last elementconst T& back() const
- returns an unmodifiable reference to the last elementvoid push_back(const T& elem)
- adds elementelem
after the last element in the containervoid push_front(const T& elem)
- adds elementelem
before the first element in the containervoid pop_back()
- removes the last element from the containervoid pop_front()
- removes the first element from the containeriterator insert(iterator position, const T& elem)
- adds elementelem
at the iterator positioniterator erase(const_iterator position)
- removes from container the element at the iterator positionvoid clear()
- removes all elements from the container
The subscripting operators and the at(int)
member functions, which provide direct element access in other sequential containers, are not supported in this template. Instead, the template defines insert()
and erase()
member functions that use iterators. The section on iterators below described these in more detail.
Container Adapters
The STL includes adapters for converting a container class to operate in a specific context. The adapters include:
stack
- last in, first out (LIFO) contextqueue
- first in, first out (FIFO) contextpriority_queue
- first element is always the "greatest"
stack
The stack
class template defines a container class (by default deque
) that operates in a FILO (first-in, last-out) context. The stack class template is defined in the header <stack>
.
The member functions of the stack class template include:
explicit stack()
- default constructor; creates a stack with no elementsexplicit stack(const Container& c)
- creates a stack initialized to a copy of containerc
stack& operator=(const stack& s)
- copies the contents ofs
into the current object~stack()
- destroys the stacksize_t size() const
- returns the number of elements in the current objectbool empty() const
- returnstrue
if the current object has no elementsT& top()
- returns a reference to the top element of the stackconst T& top() const
- returns an unmodifiable reference to the top element of the stackvoid push(const T& t)
- adds elementt
to the top of the stackvoid pop()
- removes the top element from the stack
The following program creates an initially empty stack of double
s named prices
, pushes three elements onto the stack, changes the top element, and displays the elements as it pops them off the stack:
// Container Adapters - Stacks
// ca_stack.cpp
#include <stack>
#include <iostream>
int main()
{
std::stack<double> prices; // initially empty
prices.push(10.43); // add 10.43
prices.push(20.54); // add 20.54
prices.push(32.43); // add 32.43
prices.top() = 5.41;
while(!prices.empty())
{
std::cout << prices.top() << " ";
prices.pop();
}
std::cout << std::endl;
}
5.41 20.54 10.43
queue
The queue
class template defines a container class (by default deque
) that operates in a FIFO context. The queue
class template is defined in the header <queue>
.
The member functions of the queue class template include:
explicit queue()
- default constructor; creates a queue with no elementsexplicit queue(const Container& c)
- creates a queue initialized to a copy of containerc
size_t size() const
- returns the number of elements in the current objectbool empty() const
- returnstrue
if the current object has no elementsvoid push(const T& t)
- adds elementt
to the top of the queuevoid pop()
- removes the first element from the queueT& front()
- returns a reference to the first element of the queueconst T& front() const
- returns an unmodifiable reference to the first element of the queueT& back()
- returns a reference to the last element of the queueconst T& back() const
- returns an unmodifiable reference to the last element of the queue
Note that the last 4 member functions are absent in the stack
template.
The following program creates an initially empty queue of int
s named tickets
, pushes three elements into the queue, changes the last ticket, and displays the elements as it pops them off the queue:
// Container Adapters - Queues
// ca_queue.cpp
#include <queue>
#include <iostream>
int main()
{
std::queue<int> tickets; // initially empty
tickets.push(10); // add 10
tickets.push(20); // add 20
tickets.push(32); // add 32
tickets.back() = 30;
while(!tickets.empty())
{
std::cout << tickets.front() << " ";
tickets.pop();
}
std::cout << std::endl;
}
10 20 30
Iterators
An iterator is an object that points to an element in a sequence. STL iterators simulate sequential access to elements of STL containers, similar to the access that raw pointers provide for elements of simple arrays. Container classes that do not implement contiguous storage of elements require iterators to access their elements. We use iterators to insert elements into a sequence or to remove them from a sequence.
The definition of an iterator takes the form
Container<type>::iterator identifier;
Container
identifies the template of the container class, type
is the template argument, and identifier
is the name of the iterator.
For example, to define an iterator named iter
for a vector
of doubles
, we write
std::vector<double>::iterator iter;
Each STL container class includes the following member functions that return iterators:
iterator begin() noexcept
- returns an iterator pointing to the first element in a sequenceiterator end() noexcept
- returns an iterator pointing to the element one past the end of a sequenceconst_iterator cbegin() const noexcept
- returns an iterator pointing to the first element in a sequence; the element is unmodifiableconst_iterator cend() noexcept
- returns an iterator pointing to the element one past the end of a sequence; the element is unmodifiable
Incrementing an iterator (++
) updates it to point to the next element. Decrementing an iterator (--
) updates it to point to the previous element. Applying the dereferencing operator (*
) to an iterator returns the value of the element pointed to by the iterator.
For example, to display the contents of a vector sequence, we write
// Iterators - Vectors
// iterator.cpp
#include <vector>
#include <iostream>
int main()
{
std::vector<double> prices; // initially empty
std::vector<double>::iterator it;
prices.push_back(10.43); // add 10.43
prices.push_back(20.54); // add 20.54
prices.push_back(32.43); // add 32.43
for(it = prices.begin(); it != prices.end(); it++)
std::cout << *it << " ";
std::cout << std::endl;
}
10.43 20.54 32.43
We can omit the declaration of i
and use auto
in the initializer of the for
iteration:
for(auto it = prices.begin(); it != prices.end(); it++)
std::cout << *it << " ";
Inserting or Removing Elements
The container class templates define member functions for inserting and removing elements anywhere within a collection:
iterator insert(iterator p, const T& t)
- insertst
at positionp
and returns an iterator pointing to the inserted elementvoid insert(iterator p, size_t n, const T& t)
- insertst
n
times at positionp
void insert(iterator p, InIter f, InIter l)
- inserts the range[f, l)
at positionp
iterator erase(const_iterator p)
- removes the element at positionp
and returns an iterator to the next elementiterator erase(const_iterator f, const_iterator l)
- removes the elements in the range[f, l)
and returns an iterator to the next element
list
Example
The following program creates an empty list
, adds 3 double
s, inserts a price of 12.52 before the last element, removes the second element in the list and displays the elements in the list:
// Iterators - Insertion and Removal
// iterator_list.cpp
#include <list>
#include <iostream>
int main()
{
std::list<double> prices; // initially empty
prices.push_back(10.43); // add 10.43
prices.push_back(20.54); // add 20.54
prices.push_back(32.43); // add 32.43
prices.insert(--prices.end(), 12.52);
prices.erase(++prices.begin());
for(auto it = prices.begin(); it != prices.end(); it++)
std::cout << *it << " ";
std::cout << std::endl;
}
10.43 12.52 32.43
deque
Example
The following program creates a deque
of 3 double
s initialized to 10.50, changes the value of the last element to 32.43, removes the first element, adds 15.64 after the first element, adds 20.31 to the end and displays the elements in the container:
// Iterators - Insertion and Removal
// iterator_deque.cpp
#include <deque>
#include <iostream>
int main()
{
std::deque<double> p(3, 10.50);
p.back() = 32.43; // reset last
p.erase(p.begin()); // remove first
for(auto it = p.begin(); it != p.end(); it++)
std::cout << *it << " ";
std::cout << std::endl;
p.insert(++p.begin(), 15.64);
p.insert(p.end(), 20.31);
for(auto it = p.begin(); it != p.end(); it++)
std::cout << *it << " ";
std::cout << std::endl;
}
10.5 32.43
10.5 15.64 32.43 20.31
Exercises
- Read Wikipedia on the Standard Template Library