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 elementsvector<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)
- Most importantly, define left-inclusive intervals over the ranges of elements in a container
- 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*startpast the end(points just past last valid location in a range), egv.cend()singular(points to nothing), egnullptr
- 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
midis reachable fromfirst - and
lastis reachable frommid - then
[first, mid)and[mid, last)are also valid - Proof: divide and conquer induction on the interval
- and position
- If
[first, mid)and[mid, last)are valid, then[first, last)is valid- Proof: divide and conquer induction on the interval
- An empty range
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/Operation | Output | Input | Forward | Bidirectional | Random Access |
|---|---|---|---|---|---|
| Read | N/A | =*p(r-value) | =*p(r-value) | =*p(r-value) | =*p(r-value) |
| Access | N/A | -> | -> | -> | ->,[] |
| Write | *p=(l-value) | N/A | *p=(l-value) | *p=(l-value) | *p=(l-value) |
| Iteration | ++ | ++ | ++ | ++,-- | ++,--,+,-,+=,-= |
| Comparison | N/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
vectorvs. in a C-style array
- E.g., sorting integers (
- 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()
-
sortalgorithm- Reorders a given range
- Can also plug in a functor to change the ordering function
- http://www.cplusplus.com/reference/algorithm/sort/
-
Requires random access iterators.
-
Requires elements being sorted implement
operator<(less than)
#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());