Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.

Due to spam on this forum, all posts now need moderator approval.

 Entire forum ➜ Programming ➜ STL ➜ Introduction to the STL

Introduction to the STL

It is now over 60 days since the last post. This thread is closed.     Refresh page


Posted by Nick Gammon   Australia  (23,173 posts)  Bio   Forum Administrator
Date Tue 01 Jul 2003 04:58 AM (UTC)

Amended on Wed 02 Jul 2003 02:19 AM (UTC) by Nick Gammon

Message
I have been playing with the Standard Template Library (which I will refer to as "STL" from now on, rather than "the STL") and am making some notes now for my own benefit, and any other C++ programmers that are interested in using STL in their programs.




What is STL?

STL is a collection of useful algorithms which are implemented using the C++ "template" mechanism, which allows you to use them for virtually any "type" of data.

Containers

A fair bit of STL is based around the idea of containers - groups of related things - for examples lists, vectors, queues and so on. STL is designed to be consistent in its interface so that once you are used to one container you can pretty-much use them all.

Iterators

STL relies heavily on "iterators" which I will explain in a minute. Basically you use iterators to access elements in a container.

Elements

Containers contain things, which since STL uses templates, can be virtually any C++ type (eg. integer, string, pointer).

Algorithms

STL provides a whole lot of useful algorithms, like sort, merge, generate, find, replace and so on. Generally speaking the algorithms work with iterators, and can be applied to virtually any container. However some algorithms (like sort) need certain types of containers (in this case, one that can be randomly accessed).




Example 1

First, let's illustrate the idea of containers with some straight C++ ...


#include <iostream>

using namespace std;

int main (void)
  {
  // container
  int v [] = { 22, 33, 66, 102 };   

  // iterator
  int * i;               

  i = v;          // iterator is at start

  cout << *i << endl;    // display one element

  ++i;    // move to next element

  cout << *i << endl;    // display one element

  return 0;
  } // end of main


Output

22
33


This example shows an ordinary array as a container of numbers, and the iterator "i" (which is a pointer).

The important thing about the iterator is that you can increment it to move to the next element, and dereference it (*i) to get the element's contents.

Now let's do that the STL way ...



Example 2


#include <iostream>
#include <vector>

using namespace std;

int main (void)
  {
  // container
  vector<int> v;
  
  // add elements to the container
  v.push_back (22);
  v.push_back (33);
  v.push_back (66);
  v.push_back (102);

  // iterator
  vector<int>::iterator i;               

  i = v.begin ();          // iterator is at start

  cout << *i << endl;    // display one element

  ++i;    // move to next element

  cout << *i << endl;    // display one element

  return 0;
  } // end of main


Output

22
33


You are probably thinking the straight C way was easier, but the example is really to illustrate the concepts of iterators and containers.

Also, it shows how using STL adding 1 to an iterator moves onto the next element, however in a more general way. If that example was changed so that the vector became a list, or a queue, it would still work exactly the same way.

To simplify things, we can copy from a C array into a vector, using the "copy" algorithm, thus saving a bit of effort ...



Example 3


#include <iostream>
#include <vector>
#include <iterator>

using namespace std;

#define NUMITEMS(x) (sizeof(x) / sizeof(x[0]))

int main (void)
  {

  // C container
  int v1 [] = { 22, 33, 66, 102 };
 
  // STL container
  vector<int> v;

  // add elements to the container
  copy (v1, &v1 [ NUMITEMS (v1) ], back_inserter (v));

  // iterator
  copy (v.begin (), v.end (), ostream_iterator<int> (cout, " "));

  cout << endl;   // end of line

  return 0;
  } // end of main



Output

22 33 66 102


This example illustrates using "copy" to copy from one container to another. In this case the C array can be used as a container, and the vector as a second container. The "back_inserter" is needed to add elements to the end of the vector, otherwise the copy would copy beyond the end of the vector, and the program would crash.

The second "copy" uses an ostream_iterator (an output stream iterator) to copy to cout, thus simplifying displaying all the values.



Example 4



#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

using namespace std;

template <class ForwardIterator>
void increment_fill (ForwardIterator first, int n)
  {
  int value = 0;
  for (int i = 0; i < n; i++)
    *first++ = value++;
  };

int main (void)
  {
 
  // STL container
  vector<int> v;

  increment_fill (back_inserter (v), 100);

  srand(time(NULL));
//  srand48(time(NULL));  // use on Linux

  // shuffle them
  random_shuffle (v.begin (), v.end ());

  // display results
  copy (v.begin (), v.end (), ostream_iterator<int> (cout, " "));

  cout << endl;   // end of line

  return 0;
  } // end of main



Output

4 19 57 41 36 30 27 87 18 24 66 16 34 69 83 23 97 55 91 15 33 1 31 3 98 2 37 95
90 49 32 48 52 54 96 59 10 8 70 42 71 38 5 94 25 72 84 89 39 73 64 11 12 40 45 92
85 78 86 62 99 79 67 53 46 7 6 56 77 74 75 35 88 26 93 17 14 82 0 58 65 29 28
63 43 9 60 47 13 51 80 50 61 76 22 81 68 20 21 44


This example illustrates using a user-written function to generate a range of numbers, and then randomly shuffle it with the STL random_shuffle algorithm.

I found the "srand" line made the numbers come out differently each time, without it they were always the same. However on Linux (Red Hat Linux 9) I needed the srand48 instead.



- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,173 posts)  Bio   Forum Administrator
Date Reply #1 on Tue 01 Jul 2003 05:41 AM (UTC)

Amended on Thu 08 Jul 2004 11:35 PM (UTC) by Nick Gammon

Message
Strings

STL includes a nice "string" class that lets you do all sorts of things with strings. However one thing it doesn't have built-in is find-and-replace. However it is easy to write your own. Here is an example ...



Example 5


#include <iostream>
#include <iterator>
#include <algorithm>
#include <string>

using namespace std;

// string find-and-replace
string FindAndReplace
  (const string& source, const string target, const string replacement)
  {
  string str = source;
  string::size_type pos = 0,   // where we are now
                    found;     // where the found data is

  if (target.size () > 0)   // searching for nothing will cause a loop
    {
    while ((found = str.find (target, pos)) != string::npos)
      {
      str.replace (found, target.size (), replacement);
      pos = found + replacement.size ();
      }
    }

  return str;
  };   // end of FindAndReplace

int main (void)
  {
 
  string s;

  s = "Nick Gammon is working today and not working tomorrow";
  
  cout << FindAndReplace (s, "working", "sleeping") << endl;

  return 0;
  } // end of main



Output

Nick Gammon is sleeping today and not sleeping tomorrow


Getting a bit fancier now, we'll try making a deck of cards and shuffling it...



Example 6


// disable warnings about long names
#ifdef WIN32
  #pragma warning( disable : 4786)
#endif

#include <iostream>
#include <sstream>
#include <iterator>
#include <algorithm>
#include <functional>
#include <string>
#include <vector>
#include <time.h>

using namespace std;

#define NUMITEMS(x) (sizeof(x) / sizeof(x[0]))

#define MAKE_STRING(msg) \
   (((ostringstream&) (ostringstream() << msg)).str())

// used for generate_n: adds 1 to a starting number 
// and returns a string with that number

class AddN : public unary_function<int, string>
  {
  int start;
  public:
  AddN (const int n) : start (n) {};  // constructor, initialises sequence
  string operator() () { return MAKE_STRING (start++); };
  };

int main (void)
  {
 
  // my vector of cards

  vector<string> cards;

  // honour cards have special names - use a C array

  string honours [] = { "Ace", "King", "Queen", "Jack" };

  // copy from C array to vector - back_insert adds to vector cards

  copy (honours, &honours [ NUMITEMS (honours) ], back_inserter (cards));

  // generate 9 other cards starting at 2

  generate_n (back_inserter (cards), 9, AddN (2));

  // add the word " hearts" to the end of each one

  transform (cards.begin (), cards.end (),
             cards.begin (),
             bind2nd (plus<string> (), " hearts"));

  // shuffle them

  srand(time(NULL));
//  srand48(time(NULL));  // use on Linux

  random_shuffle (cards.begin (), cards.end ());

  // display them

  copy (cards.begin (), cards.end (), 
        ostream_iterator<string> (cout, ", "));
  cout << endl;

  return 0;
  } // end of main



Output

9 hearts, 5 hearts, 3 hearts, 4 hearts, Queen hearts, Jack hearts, 7 hearts, 6 hearts, Ace hearts, King hearts, 10 hearts, 8 hearts, 2 hearts,


This example illustrates a few more things - using generate_n to generate a sequence (adding 1 each time) - this is similar in concept to the increment_fill in the earlier example (example 4) but showing how we can do the same thing by writing a function that is called by the generate_n algorithm.

In this case I wanted the numbers to be strings (so we could put names like hearts, clubs, etc. after them) so a slightly different approach was warranted.

The MAKE_STRING define I found on another website, it lets you transform arbitrary mixtures of numbers and text into a single string.

For example:


string mystr = MAKE_STRING (
    "hello " << boolalpha << true << 
    hex << 1234 << 
    octal << 3456 << 
    dec << 123456 << 
    scientific << 123.45 << 
    fixed << 1234.567 " );


The keywords "hex" "octal" etc. let you change the base for numbers.


The other really interesting thing in this example is the use of "transform" with "plus" to add the word "hearts" to the end of each item in the vector.

What is happening is that transform applies a transformation to each item in a sequence, in this case the transformation is plus<string> (ie. string concatenation) and the second argument is "hearts". To bind the argument "hearts" to the "plus" we use bind2nd.




To get a bit fancier again, we'll make a copy of the 13 cards and then change "hearts" to "diamonds". To do this we need to use the "insert" member function of vector, and then write a specialised Find_Replace unary function which can be called by transform. However to avoid hard-coding what we are finding and replacing, we will make a special unary function "Find_Replace". When it is instantiated its constructor is passed what to find, and what to replace it with. It saves those in internal variables.

Then the transform function calls the operator() once for each element in the array, effectively doing the find/replace for us on each element. Changes in bold.



Example 7


// disable warnings about long names
#ifdef WIN32
  #pragma warning( disable : 4786)
#endif

#include <iostream>
#include <sstream>
#include <iterator>
#include <algorithm>
#include <functional>
#include <string>
#include <vector>
#include <time.h>

using namespace std;

#define NUMITEMS(x) (sizeof(x) / sizeof(x[0]))

#define MAKE_STRING(msg) \
   (((ostringstream&) (ostringstream() << msg)).str())

// used for generate_n: adds 1 to a starting number 
// and returns a string with that number

class AddN : public unary_function<int, string>
  {
  int start;
  public:
  AddN (const int n) : start (n) {};  // constructor, initialises sequence
  string operator() () { return MAKE_STRING (start++); };
  };


// string find-and-replace
inline string FindAndReplace
  (const string& source, const string target, const string replacement)
  {
  string str = source;
  string::size_type pos = 0,   // where we are now
                    found;     // where the found data is

  if (target.size () > 0)   // searching for nothing will cause a loop
    {
    while ((found = str.find (target, pos)) != string::npos)
      {
      str.replace (found, target.size (), replacement);
      pos = found + replacement.size ();
      }
    }

  return str;
  };   // end of FindAndReplace

// a functor that will find and replace

class Find_Replace : public unary_function<string, string>
  {
  const string target;
  const string replacement;
  public:
  Find_Replace (const string tar, const string repl)
    : target (tar), replacement (repl) {};  // constructor
  string operator() (const string & str)
    { return FindAndReplace (str, target, replacement); };
  };


int main (void)
  {
 
  // my vector of cards

  vector<string> cards;

  // honour cards have special names - use a C array

  string honours [] = { "Ace", "King", "Queen", "Jack" };

  // copy from C array to vector - back_insert adds to vector cards

  copy (honours, &honours [ NUMITEMS (honours) ], back_inserter (cards));

  // generate 9 other cards starting at 2

  generate_n (back_inserter (cards), 9, AddN (2));

  // add the word " hearts" to the end of each one

  transform (cards.begin (), cards.end (),
             cards.begin (),
             bind2nd (plus<string> (), " hearts"));


  // make a copy at the end

  cards.insert (cards.end (), cards.begin (), cards.end ());
  
  // convert the word "hearts" to "diamonds"

  transform (cards.begin (), cards.begin () + 13,
             cards.begin (),
             Find_Replace ("hearts", "diamonds"));



  // shuffle them

  srand(time(NULL));
//  srand48(time(NULL));  // use on Linux

  random_shuffle (cards.begin (), cards.end ());

  // display them

  copy (cards.begin (), cards.end (), 
        ostream_iterator<string> (cout, ", "));
  cout << endl;

  return 0;
  } // end of main



Output

10 hearts, 9 diamonds, Queen diamonds, 6 hearts, 3 diamonds, 2 diamonds, 4 hearts, 2 hearts, Jack diamonds, Ace hearts, 8 diamonds, King diamonds, Jack hearts, Ace diamonds, 10 diamonds, 8 hearts, King hearts, 5 hearts, 4 diamonds, 7 diamonds, 9 hearts, 3 hearts, 5 diamonds, Queen hearts, 7 hearts, 6 diamonds,


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,173 posts)  Bio   Forum Administrator
Date Reply #2 on Wed 02 Jul 2003 12:45 AM (UTC)

Amended on Wed 02 Jul 2003 01:22 AM (UTC) by Nick Gammon

Message
Case-independent compares

Standard STL does not give you case-independent matching, so we need to write a special function to achieve that. The example below is one approach ...



Example 8


#include <iostream>
#include <string>

using namespace std;

// case-independent (ci) equality binary function
// for use by ciStringEqual
template <class T>
struct ci_equal_to : public binary_function<T,T,bool> 
{
  bool operator() (const T& c1, const T& c2) const 
    { return tolower (c1) == tolower (c2); }
};

// case-independent (ci) string compare
// returns true if strings are EQUAL
bool ciStringEqual (string s1, string s2)
  {
  
  pair <string::const_iterator, 
        string::const_iterator> result =
    mismatch (s1.begin (), s1.end (),   // source range
              s2.begin (),              // comparison start
              ci_equal_to<unsigned char> ());  // comparison

  // match if both at end
  return result.first == s1.end () &&
         result.second == s2.end ();

  } // end of ciStringEqual 


int main (void)
  {
 
  if (ciStringEqual ("nick", "NICK"))
    cout << "match";
  else
    cout << "no match";

  cout << endl;

  return 0;
  } // end of main



Output

match


This was actually a bit tricky to get right. It uses the "mismatch" algorithm to find the first mismatch between two strings, passing down a custom compare function, which compares using "tolower" and thus gives case-insensitivity.





The next trick is to do a case-independent compare of a list (rather than a vector) - finding a string that matches using the "find_if" function. This involves writing a special compare function, that basically calls the case-independent compare used earlier. The special function is in bold. However it is possible to avoid using it by using "ptr_fun" to transform the original function into a form acceptable to the find_if algorithm.




Example 9


// disable warnings about long names
#ifdef WIN32
  #pragma warning( disable : 4786)
#endif

#include <iostream>
#include <string>
#include <list>
#include <algorithm>
#include <iterator>
#include <functional>

using namespace std;

// case-independent (ci) equality binary function
// for use by ciStringEqual
template <class T>
struct ci_equal_to : public binary_function<T,T,bool> 
{
  bool operator() (const T& c1, const T& c2) const 
    { return tolower (c1) == tolower (c2); }
};

// case-independent (ci) string compare
// returns true if strings are EQUAL
// also used by binary function fStringCompare
bool ciStringEqual (string s1, string s2)
  {
  
  pair <string::const_iterator, 
        string::const_iterator> result =
    mismatch (s1.begin (), s1.end (),   // source range
              s2.begin (),              // comparison start
              ci_equal_to<unsigned char> ());  // comparison

  // match if both at end
  return result.first == s1.end () &&
         result.second == s2.end ();

  } // end of ciStringEqual


// case-insensitive string comparison functor

struct fStringCompare : binary_function<string, string, bool>
  {
  bool operator() (const string & s1, const string & s2) const
    { return ciStringEqual (s1, s2); };
  }; // end of fStringCompare



int main (void)
  {

  list<string> s;

  s.push_back ("Hello");
  s.push_back ("World");
  s.push_back ("Nice");
  s.push_back ("Day");

  list<string>::iterator pos;

  // method 1 - use the ciStringEqual function directly

  pos = find_if (s.begin (), s.end (), 
        bind2nd (ptr_fun (ciStringEqual), "nice"));

  if (pos != s.end ())
    cout << "Found " << *pos << endl;
  else
    cout << "Not found." << endl;

  // method 2 - use the special compare function
  
  pos = find_if (s.begin (), s.end (), bind2nd (fStringCompare (), "day"));

  if (pos != s.end ())
    cout << "Found " << *pos << endl;
  else
    cout << "Not found." << endl;

  return 0;
  } // end of main


Output

Found Nice
Found Day


This example uses "bind2nd" to "bind" the word "nice" to the value returned by scanning the vector, so that the comparison function compares the current value to "nice" in each case.




Finally, I'll try to make a single function that can be used as a straight case-insensitive string comparison, and also be used in find_if, without needing to use ptr_fun. I'm not sure how useful this is, but here it is ...

Example 10


// disable warnings about long names
#ifdef WIN32
  #pragma warning( disable : 4786)
#endif

#include <iostream>
#include <string>
#include <list>
#include <list>
#include <algorithm>
#include <iterator>
#include <functional>

using namespace std;



// case-independent (ci) string compare
// returns true if strings are EQUAL
struct ciStringEqual : binary_function<string, string, bool>
  {

  // case-independent (ci) equality binary function
  // for use by ciStringEqual
  template <class T>
  struct ci_equal_to : public binary_function<T,T,bool> 
    {
    bool operator() (const T& c1, const T& c2) const 
      { return tolower (c1) == tolower (c2); }
    };

  bool operator() (const string & s1, const string & s2) const
    {
  
    pair <string::const_iterator, 
          string::const_iterator> result =
      mismatch (s1.begin (), s1.end (),   // source range
                s2.begin (),              // comparison start
                ci_equal_to<unsigned char> ());  // comparison

    // match if both at end
    return result.first == s1.end () &&
           result.second == s2.end ();

    }
  }; // end of ciStringEqual


int main (void)
  {

  list<string> s;

  s.push_back ("Hello");
  s.push_back ("World");
  s.push_back ("Nice");
  s.push_back ("Day");

  list<string>::iterator pos;

  // scan list for a comparison

  pos = find_if (s.begin (), s.end (), bind2nd (ciStringEqual (), "nice"));

  if (pos != s.end ())
    cout << "Found " << *pos;
  else
    cout << "Not found.";
 
  cout << endl;

  // do a single string comparison
  // note extra set of parenthese

  if (ciStringEqual () ("nick", "NICK"))
    cout << "match";
  else
    cout << "no match";

  cout << endl;

  return 0;
  } // end of main




Output

Found Nice
match


The modified function is in bold. Note that to call it as a straight string compare you now need an extra set of brackets, because it is a function object that needs to be constructed, and then the comparison that it is doing is passed as in the second set of brackets.

Whilst this version might be a bit more confusing to look at, what I do like about it is the ci_equal_to function has moved inside the ciStringEqual structure, effectively encapsulating its operation to where it is needed.


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


17,067 views.

It is now over 60 days since the last post. This thread is closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.