The Standard Template Library
What are Templates?
Generic "blueprints" which can be used to create instances of Abstract Data Types for different
data types without having to rewrite code. There are two types of templates:
-
Function Templates
-
Class Templates
Function Templates
These are templates which can be implemented to handle any type of data. You define the template
function with generic arguments and then when you make a call to the function in your code. The compiler
uses the template and the data types of the arguments to generate a function which uses the appropriate
data type.
Example: The max function
#include
using namespace std ;
//-------------------------------------------------------------
//max returns the maximum of the two elements
//-------------------------------------------------------------
template <class T>
T max(T a, T b)
{
return a > b ? a : b ;
}
void main()
{
cout << "max(10, 15) = " << max(10, 15) << endl ;
cout << "max('k', 's') = " << max('k', 's') << endl ;
cout << "max(10.1, 15.2) = " << max(10.1, 15.2) << endl ;
}
Example: Bubble Sort
/******************************************/
/* BubbleSort() */
/* Requires that the operators > and = are*/
/* overloaded for class T. */
/******************************************/
template <class T, int>
void BubbleSort(T DataArray[], int count)
{
int i, j;
T temp;
for(i=0; i<count; i++)
for(j=0; j<(count-i - 1); j++)
{
if(DataArray[j] > DataArray[j+1])
{
temp = DataArray[j];
DataArray[j] = DataArray[j+1];
DataArray[j+1] = temp;
}
}
}
Class Templates
These allow you to create generic classes for any type of data.
Example: Stack template (not to be confused with the stack template in the STL)
//--------------------------------------------------------
// StackTemplate.h - A generic stack class
//--------------------------------------------------------
#ifndef STACKTEMPLATE_H
#define STACKTEMPLATE_H
template <class T>
class Stack
{
public:
Stack(int = 10) ;// Default size = 10
~Stack() { delete [] stackPtr ; }
bool push(const T&);
bool pop(T&) ;
bool isEmpty()const { return top == -1 ; }
bool isFull() const { return top == size - 1 ; }
private:
int size ; // number of elements on Stack.
int top ;
T* stackPtr ;
} ;
// Constructor with the default size 10
template <class T>
Stack<T>::Stack(int s)
{
size = ((s > 0) && (s < 1000)) ? s : 10 ;
top = -1 ; // initialize stack
stackPtr = new T[size] ;
}
// push an element onto the Stack
template <class T>
bool Stack<T>::push(const T& item)
{
if (!isFull())
{
stackPtr[++top] = item ;
return 1 ; // push successful
}
return 0 ; // push unsuccessful
}
// pop an element off the Stack
template <class T>
bool Stack<T>::pop(T& popValue)
{
if (!isEmpty())
{
popValue = stackPtr[top--] ;
return 1 ; // pop successful
}
return 0 ; // pop unsuccessful
}
An example using the Stack template class
#include <iostream>
#include "Stack.h"
using namespace std ;
void main()
{
typedef Stack<float> FloatStack ;
typedef Stack<int> IntStack ;
FloatStack fs(5) ;
float f = 1.1 ;
cout << "Pushing elements onto fs" << endl ;
while (fs.push(f))
{
cout << f << ' ' ;
f += 1.1 ;
}
cout << endl << "Stack Full." << endl
<< endl << "Popping elements from fs" << endl ;
while (fs.pop(f))
cout << f << ' ' ;
cout << endl << "Stack Empty" << endl ;
cout << endl ;
IntStack is ;
int i = 1.1 ;
cout << "Pushing elements onto is" << endl ;
while (is.push(i))
{
cout << i << ' ' ;
i += 1 ;
}
cout << endl << "Stack Full" << endl
<< endl << "Popping elements from is" << endl ;
while (is.pop(i))
cout << i << ' ' ;
cout << endl << "Stack Empty" << endl ;
}

The output from the above example
But be aware that that with class templates:
-
Header and implementation must both be in the same file (the .h file). They cannot be in separate
files (.h and .cpp).
-
While the interface defining all the STL template classes is defined by ANSI
the implementation is not. Thus, you may find slightly different implementations
in different compilers. But, this should not matter since the interface is the same.
Most compilers today use the original Stepanov and Lee 1994 implementation from
Hewlett Packard.
Now, while you can create your own template classes you will mostly be using those already created
in the Standard Template Library.
The Standard Template Library
Historic Notes

|
-
Primarily developed by Alexander Stepanov starting in 1979.
He referred to it as Generic Programming
-
Ada was the first language to incorporate this idea.
-
Working at General Electric Research and Development with David Musser
they first published their work in Ada in 1987.
-
Because Ada has not had much acceptance outside the Defense Industry they
switched their work to C++. Also, because the C/C++ use of pointers made
the flexibility of templates possible.
-
Stepanov continued his research, first at AT&T Bell Laboratories, and then
at Hewlett Packard.
-
In 1992 Meng Lee became a major contributor.
-
This research may have continued for some time and resulted in an HP
proprietary library had not Andrew Koenig of Bell labs gotten wind of it and
asked Stepanov to present it to the ANSI/ISO committee for inclusion in
the C++ standard.
-
The committee approved and Stepanov and Lee were able to get a first cut
ready for the committee by 1994.
-
Their version of the STL was approved and in August of 1994 HP made it
widely available by publishing it on the internet.
|
Advanced C++ that is the basis for the STL
The ability to create templates in C++ is based on three things that are part of the language.
-
Void pointers
-
Function overloading
-
Pointers to functions
void pointers
With typed pointers, i.e. int *iptr he pointer can only point to an int variable.
Void pointers, however, can be used to store any memory address, but do require type casting to be used:
int x = 5; // Create an int pointer
void *vptr; // Create a pointer with no type associated with it
vptr = reinterpret_cast(&x);// Cast address of an int to a void pointer
*(reinterpret_cast (vptr)) = 10; // Cast vptr to an int pointer to change x
Function overloading
Templates produce compile time polymorphism. That is, new code is generated by the compiler
to handle different data types. "Standard" function overloading produces run-time polymorphism. That is,
all versions of the overloaded function exist and be created by the compiler, but different action is
obtained by calling the appropriate version. For example: If you had five different overloaded functions
called Multiply all five would be compiled and included in your code even if you only called two
of the functions. With a Multiply function template only the two versions used in the program
would be created and compiled into the application.
Pointers to Functions
If you have a function: void myFunction(int x) you can create a pointer to a function of
that type and set the pointer pointing to the function. The pointer can then be used to call
the function:
void (*funcPtr)(int); // Declare a function pointer
funcPtr = myFunction; // Set the pointer to myFunction
funcPtr(255); // Call the function using the pointer
The implication here is that with templates you can pass into a function a pointer to another function
which it can then call. For example using the BubbleSort function given above:
myClass array[50];
void (*sort)(myClass[], int);
sort = BubbleSort;
sortMyClass(array, sort, 50); // pass the sort function
template <Class T>
void sortMyClass (myClass *arr, int count, (void (*fp)(T [], int)))
{
fp(arr, count);
}
What is in the STL
The Standard Template Library provides a number of important tools for programmers which fall
into three categories.
-
Containers
-
Algorithms
-
Iterators
Containers
As the name implies these are data structures that "contain" or hold things.
There are three major types of containers:
-
Ordered
-
Unordered
-
set
-
multiset
-
map
-
multimap
-
Other
Note: As of Fall, 2008 the following are not part of the C++ Standard Template Library but you can find
them in SGIs' STL extensions and in the GNU C++ Library. They are scheduled to be added to the C++
standard as of Technical Report 1 (TR1). TR1 is also supposed to include some functionality
related to regular expressions, smart pointers (an abstract data type that simulates a pointer while
providing additional features such as automatic garbage collection or bounds checking), hash tables,
and random number generators. Four new templates will be added to the Unordered group. All of
these are similar to the template whose name is part of their name (set, multiset, map, and multimap)
but they are implemented using a hash table. A hash function must exist for each key type.
-
hash_set When released in TR1 will have the name of unordered_set
-
hash_multiset When released in TR1 will have the name of unordered_multiset
-
hash_map When released in TR1 will have the name of unordered_map
-
hash_multimap When released in TR1 will have the name of unordered_multimap
Container Adapters
These allow you to modify one of the container templates for a specific use.
Basically these wrap a container class and provide for specialized functionality.
There are three container adapters:
-
stack
-
queue
-
priority_queue
Algorithms
Algorithms are template functions that can be applied to containers to process their contents in various ways.
For example: Sort, Compare, Merge, Insert, Delete, Swap, Copy, and Fill.
These are represented by template functions which are not members of classes. They are standalone functions.
The basic idea is that the same algorithm (for example sort) can be applied to any container object.
This includes arrays and any container objects you create.
All these are defined in headers <algorithm>, <functional>, and <numeric>.
Iterators
Think of an iterator as a generalized pointer that can point to items in a container. It allows you to
move through all of the items in a container. Iterators provide the means for containers and algorithms
to interact. For example, n iterator provides a means for the sort algorithm to access each item found
in a container and thus perform the sorting. If you have an iterator you can advance it to the next item
in the container by using the ++ operator. You can also access the item the iterator references by
"dereferencing" the iterator with the * operator just as if it were a pointer. (Which may give you a
hint that maybe an iterator really is a pointer in disquise.)
Types of Iterators
There are six types of iterators including two special types. The names of each make it clear how they work,
except for the last two.
-
Forward iterators - iterate from the beginning to the end of a container.
-
Reverse iterators - iterate from the end to the beginning of a container.
-
Bidirectional iterators - iterate in both directions through a container.
-
Random-access iterators - can reference any item in a container without a linear progression.
-
Input iterators Can be set to reference a specific device such as a file or cin and
used to input sequences of items into a container.
-
Output iterators Can be set to reference a file or cout to sequentially output the
items from a container
All iterators and related functions are defined in the header files <iterator>,
<memarg>, and <utility>
So, lets jump right in and look at some of the most used templates
Ordered Collections
<vector>
Overview
-
Allows random access to any of the members.
-
Can be thought of as a dynamic array of objects, i.e. has the ability to automatically
resize itself when inserting or deleting an object.
-
Inserting and removing an element to/from the end of the vector takes constant time (O(1))
-
Inserting and removing an element to/from the beginning or middle of the vector takes
linear time (O(n)), i.e. the bigger the vector the longer it takes.
-
There is a special implementation for type bool which optimizes for space by storing boolean
values as bits.
Constructors
vector<double>dVec; // empty vector to hold doubles
vector<int>iVec(5,0); // int vector of 5 elements all initialized to 0
vector<float>fVec(20); // float vector of 20 elements
vector<string>sVec(10, "abc"); // string vector of 10 elements all set to abc
vector<simple>simVec(); // empty vector to hold struct simple
Overloaded operators
[]
Functions
Changing the contents
push_back(newValue) add newValue to the end of the vector
insert(itr, newValue) insert newValue just before itr
pop_back() remove last element in the vector
clear() remove all elements
erase(itr1, itr2) remove all elements from itr1 to itr2.
resize(n) adjust size by adding or removing so that size is n
Getting size information
size() gives current number of elements
capacity() gives number of elements vector can hold without resizing
max_size() gives maximum number of elements the vector can hold
empty() returns true if vector is empty
Locating elements
v[n] find element n in vector v
front() first element in vector v[0]
back() last element in vector v[size-1]
Iterators
begin() iterator to first element in vector
end() iterator to last element in vector
rbegin() reverse iterator to last element in vector
rend() reverse iterator to first element in vector
Other functions
swap(v[x], v[y]) swaps elements x and y
Code Sample
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
int main(int argc, char **argv)
{
vector<string> SS;
cout << "Creating string vector and adding 3 strings.\n";
SS.push_back("The number is 10");
SS.push_back("The number is 20");
SS.push_back("The number is 30");
cout << "Loop by index:" << endl;
unsigned int i;
for(i=0; i < SS.size(); i++)
{
cout << SS[i] << endl;
}
cout << endl << "Loop by Forward Iterator:" << endl;
for(vector<string>::const_iterator cii = SS.begin(); cii != SS.end(); cii++)
{
cout << *cii << endl;
}
// FYI: A const_iterator can be used to iterate through the entire vector but
// prevents modifying any item in the vector.
cout << endl << "Loop by Reverse Iterator:" << endl;
for(vector<string>::reverse_iterator rii = SS.rbegin(); rii != SS.rend(); rii++)
{
cout << *rii << endl;
}
cout << "\nInformation on the vector:\n";
cout << "\tsize = " << SS.size() << endl;
cout << "\tcapacity = " << SS.capacity() << endl;
cout << "\tmax_size = " << SS.max_size() << endl;
cout << "\tSS[0] = " << SS[0] << endl;
cout << "\tSS[2] = " << SS[2] << endl << endl;
// Demonstration of a template function
cout << "Calling swap(SS[0], SS[2]);\n";
swap(SS[0], SS[2]);
cout << "\tSS[0] = " << SS[0] << endl;
cout << "\tSS[2] = " << SS[2] << endl << endl;
SS[0] = "Can I do this?";
cout << "SS[0] = " << SS[0] << endl;
return 0;
}
<list>
Overview
-
Created as a double linked list.
-
Elements are not stored contiguously in memory as they are in a vector.
-
Opposite performance from a vector. Slow lookup and access (linear time), but once a
position has been found, quick insertion and deletion (constant time).
Constructors
list <double>dList; // empty list to hold doubles
list <char>cList (); // empty list to hold characters
list <simple>simList (); // empty list to hold struct simple
Functions
Changing the contents
push_back(newValue) add newValue to the end of the list
push_front(newValue) add newValue to the front of the list
insert(itr, newValue) insert newValue just before itr
remove(T) Remove element T
assign(n, T) Create list of n elements all set to T
pop_back() remove last element in the list
pop_front() remove first element in the list
clear() remove all elements
erase(itr1, itr2) remove all elements from itr1 to itr2.
Getting size information
size() gives current number of elements
max_size() gives maximum number of elements the list can hold
empty() returns true if list is empty
Locating elements
front() first element in list
back() last element in list
Iterators
begin() iterator to first element in list
end() iterator to last element in list
rbegin() reverse iterator to last element in list
rend() reverse iterator to first element in list
Other functions
sort() sorts the elements of the list
Code Sample
#include <iostream>
#include <list>
#include <string>
using namespace std;
int main(int argc, char **argv)
{
list <int> L;
cout << "Demonstrating push_back, push_front, and insert in a list.\n\n";
L.push_back(1); // Insert a new element at the end
L.push_front(3); // Insert a new element at the beginning
L.insert(++L.begin(), 2); // Insert "2" before position of first argument
L.push_front(4); // Insert a new element at the beginning
L.push_back(5); // Insert a new element at the end
L.push_back(6); // Insert a new element at the end
cout << "Elements in the list (in order):\n\t";
for(list<int>::iterator i=L.begin(); i != L.end(); ++i) cout << *i << " ";
cout << endl << endl;
cout << "Information on the list:\n";
cout << "\tsize = " << L.size() << endl;
cout << "\tmax_size = " << L.max_size() << endl << endl;
cout << "Sorting the list:\n";
L.sort();
cout << "Elements in the list (in order):\n\t";
for(i=L.begin(); i != L.end(); ++i) cout << *i << " ";
cout << endl << endl;
cout << "Demonstrating pop_back, pop_front, and remove from a list.\n";
L.pop_back(); // Remove element at the end
L.pop_front(); // Remove element at the beginning
L.remove(3); // Remove element from middle
cout << "Elements in the list (in order):\n\t";
for(i=L.begin(); i != L.end(); ++i) cout << *i << " ";
cout << endl << endl;
cout << "Demonstrating erase() to remove all but two elements.\n";
L.erase(++L.begin(), --L.end());
cout << "Elements in the list:\n\t";
for(i=L.begin(); i != L.end(); ++i) cout << *i << " ";
cout << endl << endl;
cout < "Demonstrating assign(5, 2) to put five 2s in the list.\n";
L.assign(5, 2);
cout << "Elements in the list:\n\t";
for(i=L.begin(); i != L.end(); ++i) cout << *i << " ";
cout << endl << endl;
return 0;
}
<deque>
Overview
-
Name means Double Ended QUEue
-
Similar to a Vector but provides for fast (O(1) inserts or deletes at the
beginning or end of the deque.
-
Care must be taken with any iterators after inserting or deleting as they may no longer be valid.
-
Can do random inserts and deletes but they are less efficient.
Constructors
deque<double>dDeque; // empty deque to hold doubles
deque <char>cDeque (); // empty deque to hold characters
deque <simple>simDeque (); // empty deque to hold struct simple
Overloaded operators
[]
Functions
Changing the contents
push_back(newValue) add newValue to the end of the deque
push_front(newValue) add newValue to the front of the deque
insert(itr, newValue) insert newValue just before itr
remove(T) Remove element T
assign(n, T) Create deque of n elements all set to T
pop_back() remove last element in the deque
pop_front() remove first element in the deque
clear() remove all elements
erase(itr1, itr2) remove all elements from itr1 to itr2.
Getting size information
size() gives current number of elements
max_size() gives maximum number of elements the deque can hold
empty() returns true if deque is empty
Locating elements
front() first element in deque
back() last element in deque
Iterators
begin() iterator to first element in deque
end() iterator to last element in deque
rbegin() reverse iterator to last element in deque
rend() reverse iterator to first element in deque
Other functions
sort() sorts the elements in the deque
Code Sample
#include <iostream>
#include <deque>
#include <algorithm>
#include <string>
using namespace std;
int main(int argc, char **argv)
{
deque<string> DD;
cout << "Creating string deque and adding 3 strings using push_front.\n";
DD.push_front("Deque the halls 1");
DD.push_front("Deque the halls 2");
DD.push_front("Deque the halls 3");
cout << "Creating string deque and adding 3 strings using push_back.\n\n";
DD.push_back("Deque the halls 4");
DD.push_back("Deque the halls 5");
DD.push_back("Deque the halls 6");
cout << "Loop by index:" << endl;
unsigned int i;
for(i=0; i < DD.size(); i++)
{
cout << DD[i] << endl;
}
cout << endl << "Constant Iterator:" << endl;
for(deque<string>::const_iterator=DD.begin(); cii!=DD.end(); cii++)
{
cout << *cii << endl;
}
// FYI: A const_iterator can be used to iterate through the entire deque but
// prevents modifying any item in the deque.
cout << endl << "Reverse Iterator:" << endl;
for(deque<string>::reverse_iterator rii=DD.rbegin(); rii!=DD.rend(); rii++)
{
cout << *rii << endl;
}
cout << "Information on the vector:\n";
cout << "\tsize = " << DD.size() << endl;
cout << "\tmax_size = " << DD.max_size() << endl;
cout << "\tDD[0] = " << DD[0] << endl;
cout << "\tDD[2] = " << DD[2] << endl << endl;
cout << "Calling swap(DD[0], DD[2]);\n";
swap(DD[0], DD[2]);
cout << "\tDD[0] = " << DD[0] << endl;
cout << "\tDD[2] = " << DD[2] << endl << endl;
return 0;
}
Do you begin to see some commonality among these templates? There is an attempt to create a common
interface within the limits of what each is supposed to do, e.g. list does not use the [ ]
operator because a list does not allow random access.
Unordered Collections
<set> and <multiset>
Overview
-
Even though sets are usually considered an unsorted collection the set template
is implemented as a sorted collection of elements.
-
Inserting/erasing elements in a set does not invalidate iterators pointing in the set.
-
Provides set operations of union, intersection, difference, and symmetric difference (
sym dif = A XOR B, i.e. set of all elements not in both sets, inverse of the intersection).
-
Also provides for a test of inclusion.
-
Any data types used for a set must implement the comparison operator < or a custom comparator
function must be specified.
-
Actually implemented internally as a self-balancing binary search tree. (See example of
an AVL tree in the Code Vault.)
-
Set allows only 1 instance of a particular element, multiset allows duplicate elements.
Constructors
set<char> cSet; // Set of characters
int iArr[] = {5, 10, 25, 50, 100};
set<int> iSet(iArr, iArr+5); // Set of ints built from iArr
// using pointers to the first and last
// elements as iterators.
bool simpleLessThan(simple *s1, simple *s2)
{
return s1->X < s2->X;
}
// Set of simple with a given function to do "less than"
set<simple, bool(*)(simple *, simple *)> sSet(simpleLessThan);
Overloaded operators
Must overload < or provide a "less than" comparison function
Functions
Changing the contents
insert(newValue) insert newValue into the set
insert(itr1, itr2) insert all elements from another container referenced by [itr1, itr2)
erase(keyValue) remove keyValue if it is there
erase(itr) remove item referenced by itr
erase(itr1, itr2) remove all items in range [itr1, itr2)
clear() remove all items
Getting size information
size() gives current number of elements
max_size() gives maximum number of elements the deque can hold
empty() returns true if deque is empty
Locating elements
find(keyValue) returns iterator to keyValue if present
count(keyValue) returns count of keyValue (1 if present, 0 if not)
Iterators
begin() iterator to first element in set
end() iterator to last element in set
rbegin() reverse iterator to last element in set
rend() reverse iterator to first element in set
Code Sample
#include <iostream>
#include <set>
#include <algorithm>
#include <string>
using namespace std;
// Simple structure
struct simple
{
int X;
char ch;
};
// Comparator function for set demo using X field of a struct simple
bool simpleLessThan(const simple &s1, const simple &s2)
{
return s1.X < s2.X;
}
int main(int argc, char **argv)
{
// Create an empty set of characters
set<char> cSet1;
set<char> cSet2;
set<char> cSet3;
cout << "Creating char set 1 and adding 10 characters.\n";
for(char c = 'A'; c < 'K'; c++)
{
cSet1.insert(c);
}
cout << "Printing contents of char set 1 using iterators begin and end.\n\t";
for(set<char>::iterator itr = cSet1.begin(); itr != cSet1.end(); itr++)
{
cout << *itr << " ";
}
cout << "\nInformation on cSet1:\n";
cout << "\tsize = " << cSet1.size() << endl;
cout << "\tmax_size = " << cSet1.max_size() << endl;
cout << "\tcount = " << cSet1.count('A') << endl;
cout << "Demonstrating find function on cSet1\n";
char ch = 'C';
set<char>::const_iterator cItr;
// FYI: A const_iterator can be used to iterate through the entire set but
// prevents modifying any item in the set.
cItr = cSet1.find(ch);
cout << "\tfind('C') returned iterator to " << *cItr;
cout << "\n\n";
cout << "Creating char set 2 and adding 10 characters.\n";
for(char c = 'E'; c < 'N'; c++)
{
cSet2.insert(c);
}
cout << "Printing contents of char set 2 using iterators begin and end.\n\t";
for(set<char>::iterator itr = cSet2.begin(); itr != cSet2.end(); itr++)
{
cout << *itr << " ";
}
cout << "\n\nCreating char set 3 as a unnion of cSet1 and cSet2.\n";
insert_iterator<set<char>>unionItr(cSet3, cSet3.begin()); // iterator for result
set_union(cSet1.begin(), cSet1.end(), cSet2.begin(), cSet2.end(), unionItr);
cout << "Printing contents of char set 3 using iterators begin and end.\n\t";
for(set<char>::iterator itr = cSet3.begin(); itr != cSet3.end(); itr++)
{
cout << *itr << " ";
}
cout << "\n\nClearing set 3\n";
cout << "Refilling set 3 as an intersection of cSet1 and cSet2.\n";
cSet3.clear();
insert_iterator<set<char>>intersectionItr(cSet3, cSet3.begin()); // iterator for result
set_intersection(cSet1.begin(), cSet1.end(), cSet2.begin(), cSet2.end(), intersectionItr);
cout << "Printing contents of char set 3 using iterators begin and end.\n\t";
for(set<char>::iterator itr = cSet3.begin(); itr != cSet3.end(); itr++)
{
cout << *itr << " ";
}
cout << "\n\n";
cout << "\n\nClearing set 3\n";
cout << "Refilling set 3 as the difference of cSet1 and cSet2.\n";
cSet3.clear();
insert_iterator<set<char>>diffItr(cSet3, cSet3.begin()); // iterator for result
set_difference(cSet1.begin(), cSet1.end(), cSet2.begin(), cSet2.end(), diffItr);
cout << "Printing contents of char set 3 using iterators begin and end.\n\t";
for(set<char>::iterator itr = cSet3.begin(); itr != cSet3.end(); itr++)
{
cout << *itr << " ";
}
cout << "\n\n";
cout << "\n\nClearing set 3\n";
cout << "Refilling set 3 as the symmetric difference of cSet1 and cSet2.\n";
cSet3.clear();
insert_iterator<set<char>>symDiffItr(cSet3, cSet3.begin()); // iterator for result
set_symmetric_difference(cSet1.begin(), cSet1.end(), cSet2.begin(), cSet2.end(), symDiffItr);
cout << "Printing contents of char set 3 using iterators begin and end.\n\t";
for(set<char>::iterator itr = cSet3.begin(); itr != cSet3.end(); itr++)
{
cout << *itr << " ";
}
cout << "\n\n";
// Create a set of 5 ints from an array of ints by giving the
// iterators for begin and end
int iArr[] = {5, 10, 25, 50, 100};
set<int> iSet(iArr, iArr+5);
// Create a set of struct simple with a required function to do "less than" comparison
set<simple, bool(*)(const simple &, const simple &)> sSet(simpleLessThan);
cout << "Creating int set and initializing to {5, 10, 25, 50, 100}.\n";
cout << "Printing contents of int set using iterators begin and end.\n\t";
for(set<int>::iterator itr = iSet.begin(); itr != iSet.end(); itr++)
{
cout << *itr << " ";
}
cout << "\n\n";
cout << "Creating struct simple set and adding 5 elements.\n";
for(int i=0; i<5; i++)
{
simple *s1 = new simple();
s1->X = i;
s1->ch = 'A' + i;
cout << "\tAdding " << s1->X << ", " << s1->ch << " to the set.\n";
sSet.insert(*s1);
}
cout << "Getting iterators to two elements to demonstrate < operator.\n";
set<simple, bool(*)(const simple &, const simple &)>::iterator simItr1 = sSet.begin();
set<simple, bool(*)(const simple &, const simple &)>::iterator simItr2 = sSet.begin();
simItr2++;
simItr2++;
cout << "Comparing simItr1 = " << simItr1->X << ", " << simItr1->ch << endl;
cout << " with simItr2 = " << simItr2->X << ", " << simItr2->ch << endl;
if(simpleLessThan(*simItr1, *simItr2))
cout << "simItr1 is less than simItr2" << endl;
else
cout << "simItr1 is greater than simItr2" << endl;
cout << "Printing contents of struct set using iterators begin and end.\n";
for(simItr1 = sSet.begin(); simItr1 != sSet.end(); simItr1++)
{
cout << "\tStruct = " << simItr1->X << ", " << simItr1->ch << endl;
}
return 0;
}
<map> and <multimap>
Overview
-
A sorted associative array, i.e. it allows mapping from one data item (a key) to another
(a value). Similar to the use of a key field in a class used to build a sorted linked list.
-
Given a key you can find the associated value.
-
Type of key must implement comparison operator < or custom comparator function must be specified.
-
Also implemented internally as a self-balancing binary search tree.
-
Map allows only 1 instance of a particular key. Multimap allows duplicate keys and allows
a single key to map to multiple items.
Constructors
map<int, string> Employees; // Create map of strings keyed on int
multimap<string, int> m; // Create multimap of ints keyed on string
Overloaded operators
Must overload < or provide a less than comparison function
[ ] overloaded for insertion
Functions
Changing the contents
insert(pair<keyType, valType>(key, val)) Insert a pair object
mapName[key] = val Add a key/element with overloaded [ ] operator
erase(keyValue) remove keyValue if it is there
erase(itr) remove item referenced by itr
erase(itr1, itr2) remove all items in range [itr1, itr2)
clear() remove all items
Getting size information
size() gives current number of elements
max_size() gives maximum number of elements the deque can hold
empty() returns true if deque is empty
Locating elements
m[keyVal] find item with the given key
find(keyValue) find item with the given key. Returns a pair object
count(keyValue) returns count of items with the given key
Iterators
begin() iterator to first element in set
end() iterator to last element in set
rbegin() reverse iterator to last element in set
rend() reverse iterator to first element in set
Code Sample
#include <iostream>
#include <map>
#include <algorithm>
#include <string>
#include <utility>
using namespace std;
int main()
{
map<int, string> Employees;
//---------------------------------------------------------------------
// Demonstrate map template
//---------------------------------------------------------------------
// Assignment using array index notation
cout << "Creating map<int, string> Employees and adding 5 members\n";
Employees[5234] = "Mike C.";
Employees[3374] = "Charlie M.";
Employees[1923] = "David D.";
Employees[7582] = "John A.";
Employees[5328] = "Peter Q.";
// Print using iterator
cout << "Printing all members using iterators through the pair objects\n";
for( map<int,string>::iterator ii=Employees.begin(); ii!=Employees.end(); ++ii)
{
cout << "\t" <<(*ii).first << ": " << (*ii).second << endl;
}
cout << "\nPrinting all members using their index notation\n";
cout << "\tEmployees[5234]=" << Employees[5234] << endl;
cout << "\tEmployees[3374]=" << Employees[3374] << endl;
cout << "\tEmployees[1923]=" << Employees[1923] << endl;
cout << "\tEmployees[7582]=" << Employees[7582] << endl;
cout << "\tEmployees[5328]=" << Employees[5328] << endl << endl;
cout << "Using find to locate an element with Employees.find(3374)\n";
cout << " Note: find returns a pair object.\n";
map<int, string>::iterator itr = Employees.find(3374);
cout << "\tFound: [" << (*itr).first << ", " << (*itr).second << "]" << endl << endl;
cout << "Information on the map:\n";
cout << "\tsize = " << Employees.size() << endl;
cout << "\tmax_size = " << Employees.max_size() << endl << endl;
//---------------------------------------------------------------------
// Demonstrate map template
//---------------------------------------------------------------------
// Compare (<) function not required since it is built into string class.
// else declaration of a comparison function would have to be included
// in the multimap definition i.e. multimap<string, int, compare> m;
cout << "Creating multimap<string, int> m and adding 6 members\n";
multimap<string, int> m;
m.insert(pair<string, int>("a", 1));
m.insert(pair<string, int>("c", 2));
m.insert(pair<string, int>("b", 3));
m.insert(pair<string, int>("b", 4));
m.insert(pair<string, int>("a", 5));
m.insert(pair<string, int>("b", 6));
cout << "Number of elements with key a: " << m.count("a") << endl;
cout << "Number of elements with key b: " << m.count("b") << endl;
cout << "Number of elements with key c: " << m.count("c") << endl;
cout << "Elements in m: " << endl;
for (multimap<string, int>::iterator it = m.begin(); it != m.end(); it++)
{
cout << "\t [" << (*it).first << ", " << (*it).second << "]" << endl;
}
pair<multimap<string, int>::iterator, multimap<string, int>::iterator> ppp;
// equal_range(b) returns pair<iterator,iterator> representing the range
// of element with key b
ppp = m.equal_range("b");
// Loop through range of maps of key "b"
cout << endl << "Range of \"b\" elements:" << endl;
for (multimap<string, int>::iterator it2 = ppp.first; it2 != ppp.second; it2++)
{
cout << " [" << (*it2).first << ", " << (*it2).second << "]" << endl;
}
}
Other Unordered Containers
Be aware that these exist but because of time and space limitations and the fact that they are not
used as much as the other containers we will not spend time on them.
-
bitset
-
Stores a series of bits similar to a fixed-sized vector of bools.
-
Supports bitwise operations on bits, such as the bits of an int variable.
Includes functions such as flip(), reset(), set(), size(), to_string(), etc.
-
Does not have iterators.
-
valarray
-
Similar to vector.
-
Designed for high speed numerics at the expense of some programming ease and
general purpose use.
Container Adapters
These allow you to modify one of the above mentioned sequence containers for a specific use.
Basically these wrap a container class and provide for specialized functionality.
There are 3 types:
<stack>
Overview
-
Its a LIFO structure (Last In First Out). You can push an item on the top of the stack or
you can pop an item from the top of the stock. No other access is allowed.
-
The stack template wraps a list template object and provides the behavior of a stack.
Constructors
stack<dataType> myStack;
This will create a list template of dataType objects and then place the list inside of a stack so you
get the push and pop actions required of a stack.
Functions
Changing the contents
push(val)
pop()
Getting size information
size()
empty()
Locating elements
top()
Code Sample
#include <iostream>
#include <stack>
using namespace std;
int main()
{
stack<int> iStack;
int val;
// Push some stuff on the int stack
cout << "Creating an int stack and pushing 10 values on it.\n\n";
for(int i=0; i<10; i++)
{
val = i * 100;
iStack.push(val);
}
cout << "Printing stack.top then popping values from the stack.\n\t ";
while(!iStack.empty())
{
cout << iStack.top() << " ";
iStack.pop();
}
if(iStack.empty())
cout << "\n\nStack is now empty.\n";
else
cout << "\n\nOops! Why isn't the stack empty?\n"; // Won't happen
cout << "\n\n";
}
<queue>
Overview
-
Its a FIFO structure (First In First Out). You push items in at the tail of the queue and
pop items off at the head of the queue.
-
The queue template wraps a list template object and provides the behavior of a queue.
Constructors
queue<dataType> myQueue;
This will create a list template of dataType objects and then place the list inside of a queue
so you get the push and pop actions of a queue.
Functions
Changing the contents
push(val)
pop()
Getting size information
size()
empty()
Locating elements
front()
back()
Code Sample
#include <iostream>
#include <queue>
#include <list>
using namespace std;
int main()
{
queue<char> cQueue;
// Push some stuff on the char queue
cout << "Creating a char queue and pushing 10 values on it.\n\n";
for(char i='A'; i<'K'; i++)
{
cQueue.push(i);
}
cout << "\tQueue.front = " << cQueue.front() << endl;
cout << "\tQueue.back = " << cQueue.back() << endl << endl;
cout << "Printing queue.front then popping values from the queue.\n\t ";
while(!cQueue.empty())
{
cout << cQueue.front() << " ";
cQueue.pop();
}
if(cQueue.empty())
cout << "\n\nQueue is now empty.\n";
else
cout << "\n\nOops! Why isn't the queue empty?\n"; // Won't happen
cout << "\n\n";
}
<priority_queue> found in <queue>
Overview
-
Works just like the queue class except that when you pop (dequeue) an item it removes the
item from the list with the highest priority (value).
Constructors
priority_queue<dataType> myPQueue;
This will create a list template of dataType objects and then place the list inside of a priority_queue
so you get the push and pop actions of a queue.
Functions
Changing the contents
push(val)
pop()
Getting size information
size()
empty()
Locating elements
top()
Code Sample
#include <iostream>
#include <queue>
#include <list>
using namespace std;
int main()
{
priority_queue<int>piQueue;
int val;
// Push some stuff on the int queue
cout << "Creating an int priority_queue and pushing 10 values on it.\n";
cout << " Values are pushed from smallest to largest.\n\n";
for(int i=0; i<10; i++)
{
val = i * 100;
piQueue.push(val);
}
cout << "\tpiQueue.top = " << piQueue.top() << endl << endl;
cout << "Printing queue.top then popping values from the queue.\n";
cout << " Items popped from largest to smallest.\n\n\t ";
while(!piQueue.empty())
{
cout << piQueue.top() << " ";
piQueue.pop();
}
if(piQueue.empty())
cout << "\n\nQueue is now empty.\n";
else
cout << "\n\nOops! Why isn't the queue empty?\n"; // Won't happen
cout << "\n\n";
}
Algorithms
Space and time limitations prevent going into any detail about other algorithms other
than those already demonstrated. All of the algorithms are found in <algorithm> <functional>
and <numeric>. The list below, however, will give you some idea as to the variety
of template functions available.
Iterators
Iterators are defined in <iterator>. The main uses of iterators have already been shown
in the previous demonstrations.