Lecture 21 Standard Template Library STL: At a C++ standards meeting in 1994, the committee voted to adopt a proposal by Alex Stepanov of Hewlett-Packard Laboratories to include, as part of the standard C++ library, a collection of generic algorithms that he, Meng Lee and their colleagues had developed. Collectively, the classes and algorithms in this library are known as the Standard Template Library (STL). The idea of STL is to make various algorithms as independent as possible of the data structures on which they act. A simple, but very limited, view of STL is the generality that using template functions provides. For example: template< class T> void sort( T rgtobjects[], int nsize) This function, presumably, would sort any type of array of nsize objects (of type T). But STL is much more. Consider the following task. Suppose we wanted a sort algorithm that would work efficiently on either an array or a doubly linked list. STL has three major components designed to work with each other. Containers hold your objects. Iterators let you move through, change, retrieve, etc. your objects in a container. Algorithms implement useful tasks (like sorting) with containers and iterators. containers - containers are template classes. The are sometimes called collections. A container is an object that holds other objects. There are three types: sequence container classes, associative container classes and container adapters. First-class containers (see below) are sequence and associative containers. Sequence container classes are like arrays - they act like a linear list or array of values. Examples are: vector list like C array linked list of values
deque (doubly ended queues) Associative containers contain a set of objects that can be accessed through keys. Examples are: set multiset map multimap like mathematical set no duplicates set with duplicates 1:1 associations 1:N associations Container adapters modify underlying containers like vectors, lists, etc.. Examples are: stack queue priority_queue lifo fifo There are a number of standard container member functions. Here are some: size empty max_size swap begin end rbegin rend clear erase gives number of objects in container returns true if no objects in container gives max size of container swaps the elements of two containers returns iterator of first element (const and non- const version) returns iterator that points past end (const and non- const version) returns iterator that points befor beginning (const and non- const version) gives number of objects in container (const and non- const version) empties container removes object from container iterators - iterators (as we have seen) are generalizations of pointers. The are typically used to move through a sequence of items using ++, to dereference (*) and other operator overloads. The deal with first-class containers. Here are the supported iterators (notice the hierarchy): istream_iterator ostream_iterator input_iterator output_iterator to treat istream as a container to treat ostream as a container reads from a container writes to a container
forward_iterator random_access_iterator both input and output in one direction like forward in both directions jump around Support for the containers is as follows: vector list deque set multiset map multimap stack queue priority_queue random_access_iterator random_access_iterator none none none Iterator operators are: All types ++p p++ pre-increment iterator post-increment operator Input iterators: *p dereference as r-value p = p1 assign one iterator to another p == p1 compare two iterators p!= p1 Output iterators: *p dereference as l-value p = p1 assign one iterator to another Forward iterators have both Input and Output iterator behavior Bidirectional iterators: --p p-- pre-decrement iterator post- decrement operator Random-access iterators:
p+= k increment p by k positionsrator p -= k p + k iterator pointing k positions beyond p p k p[ k ] iterator pointing to kth object after p p < p1 true if iterator p < p1 p <= p1 p > p1 p >= p1 Here s some typical code one might see: #include <list> #include "str.h" using namespace std; // need this to make a list // our string class // include the right globals for STL void main() list<str> ListOfStrings; ListOfStrings.push_front( Str("Gary Koehler") ); ListOfStrings.push_front( Str("Mark Crandall") ); ListOfStrings.push_front( Str("Dan Conway") ); ListOfStrings.push_front( Str("Kenny Cheng") ); ListOfStrings.push_front( Str("Indranil Bose") ); list<str>::iterator p; // iterator p = ListOfStrings.begin(); // iterator initialized do cout << *p << endl; while ( ++p!= ListOfStrings.end() ); Output is: Indranil Bose Kenny Cheng Dan Conway Mark Crandall Gary Koehler algorithms - the stl algorithms are template functions that perform a variety of tasks like sorting or searching. Here are some examples:
Mutating-sequence algoithms (these modify a container): copy remove reverse_copy copy_backward remove_copy rotate fill remove_copy_if rotate_copy fill_n remove_if stable_partition generate replace swap generate_n replace_copy swap_ranges iter_swap replace_copy_if transform partition replace_if unique random_shuffle reverse unique_copy inplace_merge merge Non mutating-sequence algoithms: adjacent_find count count_if find find_if for_each equal mismatch search search_n Numerical algorithms: Other: accumulate adjacent_difference inner_product partial_sum accumulate sort stable_sort partial_sort partial_sort_copy nth_element lower_bound upper_bound equal_range binary_search There are a number of typedefs that are provided for ease of use. reference a reference to the type of element in the container
const_reference const reference value_type type of object in the container iterator an iterator that points to type of object in container const_iterator a const iterator reverse_iterator iterator that iterates in reverse const_reverse_iterator const version Here are STL header files for STL implemented in Visual C++ (notice no.h): <algorithm> -- for defining numerous templates that implement useful algorithms <deque> -- for defining a template class that implements a deque container <functional> -- for defining several templates that help construct predicates for the templates defined in <algorithm> and <numeric> <iterator> -- for defining several templates that help define and manipulate iterators <list> -- for defining a template class that implements a list container <map> -- for defining template classes that implement associative containers <memory> -- for defining several templates that allocate and free storage for various container classes <numeric> -- for defining several templates that implement useful numeric functions <queue> -- for defining a template class that implements a queue container <set> -- for defining template classes that implement associative containers with unique elements <stack> -- for defining a template class that implements a stack container <utility> -- for defining several templates of general utility <vector> -- for defining a template class that implements a vector container Namespaces (a minor detail): A namespace defines a set of variables and functions as contained within the same scope and gives the set a name. These are used to keep conflicts between global identifiers, possibly from different vendors or different programmers. The variables and functions are called members of the namespace. Example: namespace Boat int nlength; int nmodel; char* pszname; int NLenBoat();
int Boat::NlenBoat() return nlength; Boat::nLength = 15; Namespaces can be augmented anywhere in the code as: namespace Boat int nwidth; They can also be nested as in: namespace Boat namespace size int nwidth; int nlength; int nmodel; char* pszname; Boat::size::nWidth = 8; By default, the global namespace contains all external variables. It has no name and references are by the :: operator. Namespaces can also be aliased (have a duplicate name) such as: namespace Ship = Boat; A using declaration specifies that a namespace variable/function can be used without the :: qualifier as in: using Boat::nModel; nmodel = 9; A using directive specifies that all of the namespace can be referenced without the qualifier. This is done as: using namespace Boat;
Practical Issue: When you use a container class on an object you create, you need to provide a certain level of operability. Always think about: Copy constructor Assignment operator Many algorithms need to compare objects, so you might need: operator== operator!= operator> operator< Example Usage - a deque: Lets use a deque to hold your Rational objects. The declaration of such a beast is: deque< Rational > deqrata; Here s a complete program: #include <deque> #include <algorithm> #include "rational.h" using namespace std; // need this to make a deque // and this to do things with the deque // gotta include the right globals for STL void main() deque< Rational > deqrata; // our deque of rationals Rational a(-3, 5), b, c(2, -3), d(-4, -9); // add to deque deqrata.push_front( a ); deqrata.push_front( b ); deqrata.push_back( c ); deqrata.push_front( d ); for(int i=0;i<deqrata.size();i++)
cout << deqrata[i] << endl; Output is: 4/9 0/1-3/5-2/3 Same Example Usage - a deque but with an iterator: #include <deque> #include <algorithm> #include "rational.h" using namespace std; // need this to make a deque // and this to do things with the deque // gotta include the right globals for STL void main() deque< Rational > deqrata; // our deque of rationals Rational a(-3, 5), b, c(2, -3), d(-4, -9); // add to deque deqrata.push_front( a ); deqrata.push_front( b ); deqrata.push_back( c ); deqrata.push_front( d ); deque< Rational>::iterator k = deqrata.begin(); while ( k!= deqrata.end()) cout << *k++ << endl; Example Usage - a queue: The sample shows queue implementation using list and deque containers. Don t forget, queues are container adapters they need the underlying container. #include <list> #include <iostream> #include <queue>
#include <deque> using namespace std ; // has all the stl declarations // Using queue with list typedef list<int > INTLIST; typedef queue<int> INTQUEUE; // Using queue with deque typedef deque<char*> CHARDEQUE; typedef queue<char*> CHARQUEUE; void main(void) int size_q; INTQUEUE q; CHARQUEUE p; // Insert items in the queue(uses list) q.push(42); q.push(100); q.push(49); q.push(201); // Output the size of queue size_q = q.size(); cout << "size of q is:" << size_q << endl; // Output items in queue using front() // and use pop() to get to next item until // queue is empty while (!q.empty()) cout << q.front() << endl; q.pop(); // Insert items in the queue(uses deque) p.push("cat"); p.push("ape"); p.push("dog"); p.push("mouse"); p.push("horse"); // Output the item inserted last using back() cout << p.back() << endl;
// Output the size of queue size_q = p.size(); cout << "size of p is:" << size_q << endl; // Output items in queue using front() // and use pop() to get to next item until // queue is empty while (!p.empty()) cout << p.front() << endl; p.pop(); Program Output: size of q is:4 42 100 49 201 horse size of p is:5 cat ape dog mouse horse