Skip to Content
CSE332SObject-Oriented Programming Lab (Lecture 9)

CSE332S Object-Oriented Programming in C++ (Lecture 9)

Sequential Containers

Hold elements of a parameterized type (specified when the container variable is declared): vector<int> v; vector<string> v1;

Elements are inserted/accessed based on their location (index)

  • A single location cannot be in more than 1 container
  • Container owns elements it contains - copied in by value, contents of container are destroyed when the container is destroyed

Containers provide an appropriate interface to add, remove, and access elements

  • Interface provided is determined by the specifics of the container - underlying data structure

usually the provided interface of the container runs in constant time

Non-random access containers

Cannot access elements in constant time, must traverse the container to get to the desired element.

Forward list

  • implemented as a singly linked list of elements
  • Elements are not contiguous in memory (no random access)
  • Contains a pointer to the first element (can only grow at front, supplies a forward_iterator)

List

  • implemented as a doubly linked list of elements
  • Elements are not contiguous in memory (no random access)
  • Contains a pointer to front and back (can grow at front or back, supplies a bidirectional_iterator)

Random access containers

Add, remove, and access elements in constant time.

Vector

  • implemented as a dynamically sized array of elements
  • Elements are contiguous in memory (random access)
  • Can only grow at the back via push_back() (amortized constant time, may expand the array takes linear time)

Deque

  • double-ended queue of elements
  • Elements do not have to be contiguous in memory, but must be accessible in constant time (random access)
  • Can grow at front or back of the queue

Iterators and iterator types

Could use the subscript/indexing (operator[]) operator with a loop

  • Not all containers supply an [] operator, but we should still be able to traverse and access their elements

Containers provide iterator types:

  • vector<int>::iterator i; // iterator over non-const elements
  • vector<int>::const_iterator ci; // iterator over const elements

Containers provide functions for creating iterators to the beginning and just past the end of the container:

vector<int> v = { 1, 2, 3, 4, 5 }; auto start = v.cbegin(); // cbegin() gives const iterator, you can't modify the elements, you can use .begin() to get a non-const iterator while (start != v.cend()) { // over const elements, v.cend() is not a valid element, it's just one pass the end. cout << *start << endl; ++start; }

More on iterators

  • Iterators generalize different uses of pointers
    • Most importantly, define left-inclusive intervals over the ranges of elements in a container [begin, end)
  • Iterators interface between algorithms and data structures (Iterator design pattern)
    • Algorithms manipulate iterators, not containers
  • An iterator’s value can represent 3 kinds of states:
    • dereferencable (points to a valid location in a range), eg *start
    • past the end (points just past last valid location in a range), eg v.cend()
    • singular (points to nothing), eg nullptr
  • Can construct, compare, copy, and assign iterators so that native and library types can inter-operate

Properties of Iterator Intervals

  • Valid intervals can be traversed safely with an iterator
    • An empty range [p,p) is valid
    • If [first, last) is valid and non-empty, then [first+1, last) is also valid
      • Proof: iterative induction on the interval
    • If [first, last) is valid
      • and position mid is reachable from first
      • and last is reachable from mid
      • then [first, mid) and [mid, last) are also valid
      • Proof: divide and conquer induction on the interval
    • If [first, mid) and [mid, last) are valid, then [first, last) is valid
      • Proof: divide and conquer induction on the interval

Interface supplied by different iterator types

  • Output iterators: used in output operations (write),
    • “destructive” read at head of stream (istream)
  • Input iterators: used in input operations (read),
    • “transient” write to stream (ostream)
  • Forward iterators: used in forward operations (read, write), common used in forward linked list
    • Value persists after read/write
  • Bidirectional iterators: used in bidirectional operations (read, write), common used in doubly linked list
    • Value have locations
  • Random access iterators: used in random access operations (read, write), common used in vector
    • Can express distance between two iterators
Category/OperationOutputInputForwardBidirectionalRandom Access
ReadN/A=*p(r-value)=*p(r-value)=*p(r-value)=*p(r-value)
AccessN/A->->->->,[]
Write*p=(l-value)N/A*p=(l-value)*p=(l-value)*p=(l-value)
Iteration++++++++,--++,--,+,-,+=,-=
ComparisonN/A==,!===,!===,!===,!=,<,>,<=,>=

Generic algorithms in CPP

A standard collection of generic algorithms

  • Applicable to various types and containers
    • E.g., sorting integers (int) vs. intervals (pair<int, int>)
    • E.g., sorting elements in a vector vs. in a C-style array
  • Polymorphic even without inheritance relationships - interface polymorphism
    • Types substituted need not have a common base class
    • Must only provide the interface the algorithm requires
    • Common iterator interfaces allow algorithms to work with many types of containers, without knowing the implementation details of the container
  • Significantly used with the sequence containers
    • To reorder elements within a container’s sequence
    • To store/fetch values into/from a container
    • To calculate various values and properties from it

Organization of C++ Algorithm Libraries

The <algorithm> header file contains

  • Non-modifying sequence operations
    • Do some calculation but don’t change sequence itself
    • Examples include count, count_if
  • Mutating sequence operations
    • Modify the order or values of the sequence elements
    • Examples include copy, random_shuffle
  • Sorting and related operations
    • Modify the order in which elements appear in a sequence
    • Examples include sort, next_permutation
  • The <numeric> header file contains
    • General numeric operations
    • Scalar and matrix algebra, especially used with vector<T>
    • Examples include accumulate, inner_product

Using Algorithms

Example using std::sort()

#include <algorithm> #include <vector> #include <iostream> using namespace std; int main(int argc, char* argv[]) { vector<int> v = { 3, 1, 4, 1, 5, 9 }; sort(v.begin(), v.end()); // sort the vector for (int i : v) { cout << i << " "; } return 0; }

Sort forward list of strings

forward_list<string> fl = { "hello", "world", "this", "is", "a", "test" }; sort(fl.begin(), fl.end());

This is not valid because forward list does not support random access iterators.

Sort vector of strings

vector<string> v = { "hello", "world", "this", "is", "a", "test" }; sort(v.begin(), v.end());
Last updated on