Title: Sequential Containers
1Sequential Containers
- Based on Koffmann and Wolfgang
- Chapter 4
2Chapter Outline
- Template Classes and the Vector
- Applications of vector
- Implementation of the vector class
- The Copy Constructor, Assignment Operator, and
Destructor - Single-Linked Lists and Double-Linked Lists
- The list class and the Iterator
- Implementation of a Double-Linked List Class
- Applications of the list Class
- Standard Library Containers
- Standard Library Algorithms and Function Objects
3Template Container Classes
- A template container class is a class that stores
and processes a collection of information. - The type of this information is a parameter that
is specified when the template class is
instantiated. - Example
- templatelttypename Tgtclass some_container
- The parameter T is a placeholder for the actual
data type. - some_containerltItem_Typegt call_lengths // holds
Item_Type - Some_containerltPeoplegt people // holds People
4The Vector
- An enhancement of the array.
- Can be used like an array
- Access a value via an index
- x vi
- vj z
- Additional capabilities
- Increase or decrease its length
- Insert an element at a specified position
- Remove an element from a specified position
5Vector Example
- // Create a vector to hold strings.
- vectorltstringgt my_vector
- // Add some entries.
- my_vector.push_back("Bashful")
- my_vector.push_back("Awful")
- my_vector.push_back("Jumpy")
- my_vector.push_back("Happy")
6Vector Example (2)
Had to slide 2,3 to 3,4
my_vector.insert(2, Doc)
7Vector Example (3)
my_vector.push_back("Dopey") // add at end
Cheap to add at end
8Vector Example (4)
my_vector.erase(1)
Had to slide 2..5 to 1..4
lst.set(1, Sneezy)
Replacement is cheap
9Example Applications of vector
- vectorltItem_Typegt some_Item_Types
- Item_Type nums 5, 7, 2, 15
- for (size_t i 0 i lt 4 i)
- some_Item_Types.push_back(numsi)
- Item_Type sum 0
- for (size_t i 0 i lt 4 i)
- sum some_Item_Typesi
-
- cout ltlt "sum is " ltlt sum ltlt endl
10Using vector in PhoneDirectory
- vectorltDirectory_Entrygt the_directory
- / Adds a new entry
- _at_param name The name of the person
- _at_param new_number The new number
- /
- void add(string name, string number)
- Directory_Entry new_entry(name, number)
- the_directory.push_back(new_entry)
11Using vector in PhoneDirectory (2)
- string Phone_Directoryadd_or_change_entry(const
string name, - const string number)
-
- string old_number ""
- Item_Type index find(name)
- if (index ! -1)
- old_number the_directoryindex.get_number()
- the_directoryindex.set_number(number)
- else
- add(name, number)
-
- modified true
- return old_number
12Implementing a vector Class
- KWvector simple implementation of stdvector
- Physical size of array indicated by data field
current_capacity - Number of data items indicated by the data field
num_items
13KWvector Fields, Constructor
- templatelttypename Item_Typegt
- class vector
- private
- // Data fields
- / The initial capacity of the array /
- static const size_t INITIAL_CAPACITY
10 - / The current capacity of the array /
- size_t current_capacity
- / The current num_items of the array
/ - size_t num_items
- / The array to contain the data /
- Item_Type the_data
- public
- // Member Functions
- / Constructsltigt lt/igtan empty vector with
the default - initial capacity.
- /
- vectorltItem_Typegt() current_capacity(INITI
AL_CAPACITY), - the_data(new Item_TypeINITIAL_CAPACITY),
num_items(0)
14Implementing vectorpush_back
15Implementing vectorpush_back (2)
- void push_back(const Item_Type the_value)
- // Make sure there is space for the new item.
- if (num_items current_capacity)
- // Allocate an expanded array
- reserve(2 current_capacity)
-
- // Insert the new item.
- the_datanum_items the_value
- num_items
-
16Implementing vectorreserve
- void reserve(size_t new_capacity)
- if (new_capacity gt current_capacity)
- if (new_capacity gt 2 current_capacity)
- current_capacity new_capacity
- else
- current_capacity 2 // Double the
capacity. - Item_Type new_data new Item_Typecurrent_ca
pacity - // Copy the data over.
- for (size_t i 0 i lt num_items i)
- new_datai the_datai
- // Free the memory occupied by the old copy.
- delete the_data
- // Now point to the new data.
- the_data new_data
-
17Implementing vectorinsert
18Implementing vectorinsert (2)
- void insert(size_t index, const Item_Type
the_value) - // Validate index.
- if (index gt num_items)
- throw stdout_of_range
- ("index to insert is out of range")
-
- // Ensure that there is space for the new item.
- if (num_items current_capacity)
- reserve(2 current_capacity) // Allocate
an expanded array -
- // Move data from index to num_items - 1 down.
- for (size_t i num_items i gt index i--)
- the_datai the_datai - 1
-
- // Insert the new item.
- the_dataindex the_value
- num_items
19Implementing vectorerase
20Implementing vectorerase (2)
- void erase(size_t index)
- // Validate index.
- if (index gt num_items)
- throw stdout_of_range
- ("index to insert is out of range")
-
- // Move items below the removed one up.
- for (size_t i index 1 i lt num_items i)
- the_datai - 1 the_datai
-
- num_items--
-
21Implementing vectoroperator
- Item_Type operator(size_t index)
- // Verify that the index is legal.
- if (index lt 0 index gt num_items)
- throw stdout_of_range
- ("index to operator is out of range")
-
- return the_dataindex
-
22The copy constructor
- The compiler defines a special constructor, known
as the copy constructor, that constructs a copy
of a given object. - This constructor is automatically invoked when
objects are passed by value to a function and
when values are returned. - It can also be explicitly invoked. E.G.
- vectorltItem_Typegt v2(v1)
- will construct the vector v2 to be a copy of v1.
23Shallow Copy versus Deep Copy
- The copy constructor that is automatically
created by the compiler (known as the default
copy constructor) makes a copy of each data
member. - Note that the data member the_data in both v1 and
v2 point to the same array.
24Shallow Copy after v1.push_back(20)
25Deep Copy of a Vector
26Copy Constructor That Makes Deep Copy
- vectorltItem_Typegt(const vectorltItem_Typegt other)
- current_capacity(other.capacity),
num_items(other.num_items), - the_data(new Item_Typeother.current_capacit
y) - for (size_t i 0 i lt num_items i)
- the_datai other.the_datai
-
27Destructor
- When a vector is constructed, space for the data
array is allocated via the new operator. - All space that is allocated with the new operator
should be returned to the free storage pool via
the delete operator when it is no longer needed. - Failure to return allocated memory results in a
memory leak and can lead to your program crashing
because memory is not available. - When an object is no longer needed, its
destructor is called. - It is the destructors responsibility to free any
allocated memory.
28The vectors destructor
- virtual vectorltItem_Typegt()
- delete the_data
-
29The swap function
- The swap function swaps the data fields of two
vectors. - void swap(vectorltItem_Typegt other)
- stdswap(num_items, other.num_items)
- stdswap(current_capacity, other.current_capaci
ty) - stdswap(the_data, other.the_data)
-
30The assignment operator
- The statement
- v2 v1
- invokes the assignment operator.
- Like the copy constructor, it should make a deep
copy. - We can use the copy constructor and swap function
to implement the assignment operator. - vectorltItem_Typegt operator(const
vectorltItem_Typegt other) - // Make a copy of the other vector.
- vectorltItem_Typegt the_copy(other)
- // Swap contents of self with the copy.
- swap(the_copy)
- // Return -- upon return the old value will be
destroyed. - return this
-
31The Rule of Three
- A class that dynamically allocates memory (or
other resources) should define - A copy constructor
- A destructor
- An assignment operator
- Corollary If a class defines one of
- Copy constructor
- Destructor
- Assignment operator
- then it should define all three.
32Performance of the vector Class
- Index operator executes in constant time O(1)
- Inserting or removing general elements is linear
time O(n) - Adding at end is (usually) constant time O(1)
- With our reallocation policy the average is O(1)
- The worst case is O(n) because of reallocation
33Singly-Linked Lists and Doubly-Linked Lists
- The vector insert and erase methods are O(n)
- Because they need to shift the underlying array
- Linked list overcomes this
- Add/remove items anywhere in constant time O(1)
- Each element (node) in a linked list stores
- The element information, of type Item_Type
- A link to the next node
- A link to the previous node (optional)
34A List Node
- A node contains
- A data item
- One or more links
- A link is a pointer to a list node
- The node class is usually defined inside another
class - It is a hidden inner class
- The details of a node should be kept private
35List Nodes for Singly-Linked Lists
36List Nodes for Singly-Linked Lists
- ifndef NODE_H_
- define NODE_H_
- / A Node is the building block for a
single-linked list. / - struct Node
- // Data Fields
- / The data /
- Item_Type data
- / The pointer to the next node. /
- Node next
- // Constructor
- / Creates a new Node that points to another
Node. - _at_param data_item The data stored
- _at_param next_ptr pointer to the Node that is
- pointed to by the new Node
- /
- Node(const Item_Type data_item, Node next_ptr
NULL) - data(data_item), next(next_ptr)
37struct versus class
- A struct is the same as a class.
- Except that the default visibility for a struct
is public. - Generally structs are used to define classes that
only contain public data fields. - Constructors may be provided.
- Other member functions (operators) are usually
not defined for structs.
38Inserting a Node into a Single Linked List
Node bob new Node("Bob") bob-gtnext
harry-gtnext // step 1 harry-gtnext bob // step
2
39Removing a node from a single-linked list
Node ptr tom-gtnext tom-gtnext
tom-gtnext-gtnext delete ptr
40Doubly-Linked Lists
- Limitations of a singly-linked list include
- Can insert only after a referenced node
- Removing node requires pointer to previous node
- Can traverse list only in the forward direction
- We can remove these limitations
- Add a pointer in each node to the previous node
doubly-linked list
41Doubly-Linked Lists, The Diagrams
42Inserting into a Double-Linked List
DNode sharon new DNode("Sharon") // Link new
DNode to its neighbors sharon-gtnext sam //
Step 1 sharon-gtprev sam-gtprev // Step 2
43Inserting into a Double-Linked List (2)
// Link old predicessor of sam to new
predicessor. sam-gtprev-gtnext sharon // Step
3 // Link to new predicessor. sam-gtprev sharon
// Step 4
44Removal from a Double-Linked List
harry-gtprev-gtnext harry-gtnext // Step
1 harry-gtnext-gtprev harry-gtprev // Step
2 delete harry
45Circular Lists
- Circular doubly-linked list
- Link last node to the first node, and
- Link first node to the last
- Advantages
- Can easily keep going past ends
- Can visit all elements from any starting point
- Can never fall off the end of a list
- Disadvantage code must avoid infinite loop!
- Can also build singly-linked circular lists
- Traverse in forward direction only
46Implementing a Circular List
47The iterator
- Suppose we want to access each element of a list
in a loop - for (Item_Type index 0 index lt a_list.size()
index) - // Do something with next_element, the element
- // at position index
- Item_Type next_element a_listindex // not
valid -
- The subscripting operator (operator) is not
defined for the list class. - Instead an iterator is used to access elements in
a list.
48The iterator (2)
49Using an Iterator
- We use an iterator like a pointer.
- // Access each list element and process it
- for (listltItem_Typegtiterator iter
a_list.begin() - iter ! a_list.end() iter)
- // Do something with next element (iter)
- Item_Type next_element iter
- . . .
50Notes on using iterators in loops
- We test for the end of the loop with the
expression - iter ! a_list.end()
- The order of individual iterator values is not
meaningful.Thus iter lt a_list.end() is not a
meaningful test. - We increment the iterator using the expression
- iter
- The postfix increment operator saves the
previous value and then performs the increment.
Since this previous value is not used, it is
more efficient to use the prefix increment
operator.
51iterator versus const_iterator
- Each standard library container (e.g. the list)
provides both an - iterator
- and a
- const_iterator
- The operations on them are the same, except
- When a const_iterator is dereferenced
(operator), the value of the item referenced
cannot be changed.
52Iterator Hierarchy
53Iterator Functions
54The list class
- Part of the C Standard Library
55Implementing KWlist
- templatelttypename Item_Typegt
- class list
- private
- // Insert definition of nested class
DNode here. - include "DNode.h"
- public
- // Insert definition of nested class
iterator here. - include "list_iterator.h"
- // Give iterator access to private
members of list. - friend class iterator
- // Insert definition of nested class
const_iterator here. - include "list_const_iterator.h"
- // Give const_iterator access to private
members of list. - friend class const_iterator
- private
- // Data fields
- / A reference to the head of the list
/ - DNode head
- / A reference to the end of the list /
56The no-argument constructor
- / Construct an empty list. /
- list() head(NULL), tail(NULL), num_items(0)
57The Copy Constructor
- / Construct a copy of a list. /
- list(const listltItem_Typegt other) list()
head(NULL), - tail(NULL), num_items(0)
- for (const_iterator itr other.begin()
- itr ! other.end() itr)
- push_back(itr)
-
-
58The Destructor
- / Destroy a list. /
- list()
- while (head ! NULL)
- DNode current head
- head head-gtnext
- delete current
-
- tail NULL
- num_items 0
-
59The Assignment Operator
- / Assign the contents of one list to another.
/ - listltItem_Typegt operator(const listltItem_Typegt
other) - // Make a copy of the other list.
- listltItem_Typegt temp_copy(other)
- // Swap contents of self with the copy.
- swap(temp_copy)
- // Return -- upon return the old value will be
destroyed. - return this
-
60The push_front function
- void push_front(const Item_Type item)
- head new DNode(item, NULL, head) // Step 1
- if (head-gtnext ! NULL)
- head-gtnext-gtprev head // Step 2
- if (tail NULL) // List was empty.
- tail head
- num_items
-
61Adding to the Head of the list
62The push_back function
- void push_back(const Item_Type item)
- if (tail ! NULL)
- tail-gtnext new DNode(item, tail, NULL) //
Step 1 - tail tail-gtnext // Step 2
- num_items
- else // List was empty.
- push_front(item)
-
-
63Adding to the Tail of the list
64The insert function
- iterator insert(iterator pos, const Item_Type
item) - // Check for special cases
- if (pos.current head)
- push_front(item)
- return begin()
- else if (pos.current NULL) // Past the
last node. - push_back(item)
- return iterator(this, tail)
-
- // Create a new node linked before node
referenced by pos. - DNode new_node new DNode(item,
- pos.current-gtprev,
- pos.current) //
Step 1 - // Update links
- pos.current-gtprev-gtnext new_node // Step
2 - pos.current-gtprev new_node // Step
3 - num_items
- return iterator(this, new_node)
65Adding to the Middle of the list
66The pop_front Function
- void pop_front()
- if (head NULL)
- throw stdinvalid_argument
- ("Attempt to call pop_front() on an empty
list") - DNode removed_node head
- head head-gtnext
- delete removed_node
- if (head ! NULL)
- head-gtprev NULL
- else
- tail NULL
- num_items--
-
67The pop_back Function
- void pop_back()
- if (tail NULL)
- throw stdinvalid_argument
- ("Attempt to call pop_back() on an empty
list") - DNode removed_node tail
- tail tail-gtprev
- delete removed_node
- if (tail ! NULL)
- tail-gtnext NULL
- else
- head NULL
- num_items--
-
68The erase Function
- iterator erase(iterator pos)
- if (empty())
- throw stdinvalid_argument ("Attempt to call
erase on an empty list") - if (pos end())
- throw stdinvalid_argument ("Attempt to call
erase of end()") - // Create an iterator that references the
position following pos. - iterator return_value pos
- return_value
- // Check for special cases.
- if (pos.current head)
- pop_front()
- return return_value
- else if (pos.current tail)
- pop_back()
- return return_value
- else // Remove a node in the
Item_Typeerior of the list. - // Unlink current node.
- DNode removed_node pos.current
- removed_node-gtprev-gtnext removed_node-gtnext
69The kwlistltItem_Typegtiterator
70Data Fields and The Constructor
- class iterator
- // Give the parent class access to this class.
- friend class listltItem_Typegt
- private
- // Data fields
- / A reference to the parent list /
- listltItem_Typegt parent
- / A pointer to the current DNode /
- typename listltItem_TypegtDNode current
- // Member functions
- / Constructs an iterator that references a
specific DNode. - Note this constructor is private. Only the
list class - can create one from scratch.
- _at_param my_parent A reference to the list
- _at_param position A pointer to the current
DNode - /
- iterator(listltItem_Typegt my_parent, DNode
position) - parent(my_parent), current(position)
-
71Dereferencing Operators
- / Returns a reference to the currently
referenced item. - _at_return A reference to the currently
referenced item - _at_throws stdinvalid_argument if this
iterator is at end - /
- Item_Type operator() const
- if (current NULL)
- throw stdinvalid_argument("Attempt to
dereference end()") - return current-gtdata
-
- / Returns a pointer to the currently
referenced item. - Item_Type must be a class or struct. This
restriction - is enforced by the compiler.
- _at_return A pointer to the currently
referenced item - _at_throws stdinvalid_argument If this
iterator is at end - /
- Item_Type operator-gt() const
- if (current NULL)
- throw stdinvalid_argument("Attempt to
dereference end()")
72Prefix increment and decrement
- iterator operator()
- /ltsnippet id"5" omit"false"gt/
- if (current NULL)
- throw stdinvalid_argument("Attempt to
advance past end()") - current current-gtnext
- return this
-
- iterator operator--()
- if (current parent-gthead)
- throw stdinvalid_argument("Attempt to
move before begin()") - if (current NULL) // Past last element.
- current parent-gttail
- else
- current current-gtprev
- return this
-
73Postfix increment and decrement
- iterator operator(Item_Type)
- // Make a copy of the current value.
- iterator return_value this
- // Advance self forward.
- (this)
- // Return old value.
- return return_value // Return the value
prior to increment -
- iterator operator--(Item_Type)
- // Make a copy of the current value.
- iterator return_value this
- // Move self backward.
- --(this)
- // Return old value.
- return return_value // Return the value
prior to decrement -
74The const_iterator
- Identical to the iterator except that the
dereferencing operators are defined as follows - const Item_Type operator() const
- if (current NULL)
- throw stdinvalid_argument
- ("Attempt to dereference end()")
- return current-gtdata
-
- const Item_Type operator-gt() const
- if (current NULL)
- throw stdinvalid_argument
- ("Attempt to dereference end()")
- return (current-gtdata)
-
75An Application Ordered Lists
- Want to maItem_Typeain a list of names
- Want them in alphabetical order at all times
- Approach Develop an Ordered_List class
- For reuse, good if can work with other types
76Design of the Ordered_List class
77Algorithm for Insertion
- Find the first item in the list that is greater
than or equal to the item to be inserted. - Insert the new item before this one.
78Refinement of algorithm
- 1.1 Create an iterator that starts at the
beginning of the list - 1.2 while the iterator is not at the end, and the
item at the iterator position is less than the
item to be inserted - 1.3 Advance the iterator
- 2. Insert the new item before the current
iterator position
79Inserting "Bill" before "Caryn"
80Ordered_Listinsert
- void insert(const Item_Type an_item)
- typename stdlistltItem_Typegtiterator itr
- a_list.begin()
- while (itr ! a_list.end() itr lt an_item)
- itr
- // itr points to the first item gt an_item or
the end. - a_list.insert(itr, an_item)
-
81Standard Library Containers
82Common Requirements for Containers
83Common Requirements for Sequences
84Requirements Applicable to Some Sequences
85The Standard Library Algorithms
- Function to find an Item_Type in a
listltItem_Typegt - listltItem_Typegtiterator find(listltItem_Typegt
a_list, Item_Type target) - for (listltItem_Typegtiterator itr
a_list.begin() - itr ! a_list.end() itr)
- if (itr target)
- return itr
-
- return a_list.end()
-
- Observe that this function only works for
Item_Type.
86Template version of find
- template lttypename Item_Typegt
- listltItem_Typegtiterator find(listltItem_Typegt
a_list, - const Item_Type
target) - typedef typename listltItem_Typegtiterator
iterator - for (iterator itr a_list.begin() itr !
a_list.end() - itr)
- if (itr target)
- return itr
-
- return a_list.end()
-
- This only works for a vector.
- What if we want to search a list?
87General Version of find
- template lttypename Iterator, typename Item_Typegt
- Iterator find(Iterator first, Iterator last,
- const Item_Type target)
- while (first ! last)
- if (first target)
- return first
-
- return first
-
- first references the first item in the sequence
- last references one past the last item in the
sequence
88The algorithm library
- The standard library header ltalgorithmgt defines
several template functions. - These perform fairly standard operation on
sequences within containers - find an item (find)
- apply a function to each item (for_each)
- copy values from one container to another (copy)
- sort the contents of a container (sort)
89Why use algorithm library
- Most of the algorithms in ltalgorithmgt are fairly
simple. - Why not code them in-line in your program
yourself? - No need to reinvent the wheel
- While they are fairly simple, they have all been
thoroughly tested. - Compiler can generate more optimal code from
standard library
90Selected Members of ltalgorithmgt
91(No Transcript)
92(No Transcript)
93Copy one a list to a vector
- a_vector.resize(a_list.size())
- copy(a_list.begin(), a_list.end(),
a_vector.begin())
94A more complicated example
- // Sort a vector
- sort(a_vector.begin(), a_vector.end())
- // Accumulate the sum
- int sum accumulate(a_vector.begin(),
a_vector.end(), 0) - cout ltlt "Sum is " ltlt sum ltlt endl
- // Check first element to see if it is the
smallest - if (a_vector.begin() !
- min_element(a_vector.begin(),
a_vector.end()) - cerr ltlt "Error in sort\n"
- // Check last element to see if it is the largest
- if (--a_vector.end() !
- max_element(a_vector.begin(),
a_vector.end()) - cerr ltlt "Error in sort\n"
95Specializing the swap function
- Swap is defined as
- template lttypename Tgt
- void swap(T x, T y)
- T temp(x) // Make copy of x
- x y
- y temp
-
- What if we call this with
- swap(vector1, vector2)
- This will expand to
- vectorltintgt temp(vector1) //Make a copy of
vector 1 - vector1 vector2 //Make a copy of
vector 2 - vector2 temp //Make a copy of temp
96Specializing swap (2)
- The vector (and all other containers) define a
member function swap that swaps the contents
without copying. - Therefore, we need to define a swap function for
vector (and the other containers) that will call
this member function - templatelttypename Item_Typegt
- inline void swap(vectorltItem_Typegt x,
- vectorltItem_Type y)
- x.swap(y)
-
- Then the call
- swap(vector1, vector2)
- will expand to
- vector1.swap(vector2)
- This is called a template specialization
- The definition is placed in the ltvectorgt header.
- A specialization of swap is defined for each
container.
97Function Objects
- If f is a function, then the expression
- f(x)
- will call this function with the parameter x.
- A class may overload the function call operator
(operator()). - class Divisible_By
- private
- int divisor
- public
- Divisible_By(int x) divisor(x)
- bool operator()(int x)
- return x divisor 0
-
-
- A class that defines operator() is known as a
function class. - An object of a function class is known as a
function object. - A function object can be used like a function.
98The find_if function
- Recall the find function searched a container for
a target value. - The find_if funciton searches a container for a
value that satisfies a given condition
(predicate) - templatelttypename Iterator, typename Pgt
- Iterator find_if(Iterator first, Iterator last, P
pred) - while (first ! last)
- if (pred(first))
- return first
- first
-
- return first
-
99Using find_if and Divisible_By
- // Find the first number divisible by 3
- listltintgtiterator iter
- iter find_if(list_1.begin(), list_1.end(),
Divisible_By(3)) - if (iter ! list_1.end())
- cout ltlt "First number divisible by 3 is " ltlt
iter ltlt endl - else
- cout ltlt "No number is divisible by 3\n"
- // Find the first number divisible by 5
- iter find_if(list_1.begin(), list_1.end(),
Divisible_By(5)) - if (iter ! list_1.end())
- cout ltlt "First number divisible by 5 is " ltlt
iter ltlt endl - else
- cout ltlt "No number is divisible by 5\n"
- // Find the first number divisible by 3