Home > Articles > Programming > C/C++

  • Print
  • + Share This
This chapter is from the book

This chapter is from the book

Using the Sequential Containers

A sequential container holds an ordered collection of elements of a single type. There is a first element, a second element, and so on, until the last element. The vector and the list are the two primary sequential containers. A vector holds its elements in a contiguous area of memory. Random access—for example, accessing element 5, then 17, and then 9—is efficient; each element is a fixed offset from the beginning of the vector. Insertion of an element at any position other than the end of the vector, however, is inefficient; each element to the right of the inserted element must be shifted one by copying the value of each element in turn. Similarly, the deletion of any element other than the last element of a vector is inefficient.

A list represents noncontiguous memory double-linked to allow both forward and backward traversal. Each element of a list contains three fields: the value, a back pointer to the preceding element of the list, and a front pointer to the next element of the list. Insertion and deletion of elements at any position within the list is efficient. The list must simply set the appropriate back and front pointers. Random access, on the other hand, is less efficiently supported. Accessing element 5, then 17, and then 9 requires traversal of the intervening elements. (Think of the difference between a CD and a cassette tape in going from one track to another.)

To represent the elements of a numeric sequence, a vector is the more appropriate container. Why? There is a great deal of random access of the elements. fibon_elem(), for example, indexes into the container based on the position passed to it by the user. Moreover, we never delete elements, and the elements are always inserted at the end of the vector.

When is a list more appropriate? If we were reading test scores from a file and wanted to store each score in ascending order, we likely would be randomly inserting into the container with each score we read. In this case, the list container is preferred.

A third sequential container is a deque (pronounced deck). A deque behaves pretty much like a vector—the elements are stored contiguously. Unlike a vector, however, a deque supports efficient insertion and deletion of its front element (as well as its back element). If, for example, we need to insert elements at the front of the container and delete them from the back, a deque is the most appropriate container type. (The standard library queue class is implemented using a deque to hold the queue's elements.)

To use a sequential container, we must include its associated header file, one of the following:

#include <vector>
#include <list>
#include <deque>

There are five ways to define a sequential container object:

  1. Create an empty container:

  2. list< string > slist;
    vector< int > ivec;
    1. Create a container of some size. Each element is initialized to its default value. (Recall that the default value for the built-in arithmetic types such as int and double is 0.)

    2. list< int >   ilist( 1024 );
      vector< string > svec( 32 );
      1. 3 each element:

      2. vector< int > ivec( 10, -1 );
        list< string > slist( 16, "unassigned" );
        1. Create a container, providing an iterator pair marking a range of elements with which to initialize the container:

        2. int ia[ 8 ] = 
            { 1, 1, 2, 3, 5, 8, 13, 21 };
          vector< int > fib( ia, ia+8 );
          1. Create a container, providing a second container object. The new container is initialized by copying the second:

          2. list< string > slist; // empty
            // fill slist ...
            list< string > slist2( slist ); // copy of slist ...

          Two special operations support insertion and deletion at the back of the container: push_back() and pop_back(). push_back() inserts an element at the back. pop_back() deletes the element. In addition, the list and deque containers (but not the vector) support push_front() and pop_front(). The pop_back() and pop_front() operations do not return the deleted value. To read the front value, we use front(); to read the back value, we use back(). For example,

          #include <deque>
          deque<int> a_line;
          int ival;
          while ( cin >> ival )
              // insert ival at back of a_line
              a_line.push_back( ival );
              // ok: read the value at front of a_line
              int curr_value = a_line.front();
              // ... do something ...
              // delete the value at front of a_line

          push_front() and push_back() are specialized insertion operations. There are four variations of the more general insert() operation supported by each of the containers:

          • iterator insert( iterator position, elemType value ) inserts value before position. It returns an iterator addressing the inserted element. For example, the following inserts ival in sorted order within ilist:

          •   list<int> ilist;
              // ... fill up ilist 
              list<int>::iterator it = ilist.begin();
              while ( it != ilist.end() )
               if ( *it >= ival )
                  ilist.insert( it, ival );
                  break; // exit loop
               if ( it == ilist.end() )
                ilist.push_back( ival );
            • void insert( iterator position, int count, elemType value ) inserts count elements of value before position. For example,

            •   string sval( "Part Two" ); 
                list<string> slist;
                // ... fill slist ...
                   it = find( slist.begin(), slist.end(), sval );
                slist.insert( it, 8, string( "dummy" ));
              • void insert( iterator1 position, iterator2 first, iterator2 last ) inserts the range of elements marked by first,last before position:

              •   int ia1[7] = { 1, 1, 2, 3, 5, 55, 89 };
                  int ia2[4] = { 8, 13, 21, 34 };
                  list<int> elems( ia1, ia1+7 ); 
                    it = find( elems.begin(), elems.end(), 55 );
                  elems.insert( it, ia2, ia2+4 ); 
              • iterator insert( iterator position ) inserts an element before position. The element is initialized to the default value of its type.

              pop_front() and pop_back() are specialized element-erase operations. There are two versions of the more general erase() operation:

              • iterator erase( iterator posit ) erases the element addressed by posit. For example, using the slist defined earlier, let's erase() the first instance of str:

              •   list<string>::iterator 
                    it = find( slist.begin(), slist.end(), str );
                  slist.erase( it );
                • iterator erase( iterator first, iterator last ) erases the elements starting with first, up to but not including last. For example, again using the slist defined earlier, let's erase() the num_times instances of str:

                •   list<string>::iterator 
                      first = slist.begin(),
                      last = slist.end();
                    // it1: first element to erase,
                    // it2: first element beyond elements to erase 
                    list<string>::iterator it1 = find( first, last, str );  
                    list<string>::iterator it2 = find( first, last, sval );
                    slist.erase( it1, it2 );

                The returned iterator in both instances of erase() addresses the element following the element or element range deleted.

                The list class does not support offset arithmetic of its iterators. This is why we do not write

                  // error: offset arithmetic is not 
                  // supported for list class
                  slist.erase( it1, it1+num_tries ); 

                but instead provide erase() with both it1 and it2.

  • + Share This
  • 🔖 Save To Your Account