990 likes | 1k Views
Sequential Containers. Based on Koffmann and Wolfgang Chapter 4. Chapter 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
E N D
Sequential Containers Based on Koffmann and Wolfgang Chapter 4
Chapter 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 Chapter 4: Sequential Containers
Template 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: template<typename T>class some_container { … } • The parameter T is a placeholder for the actual data type. some_container<Item_Type> call_lengths; // holds Item_Type Some_container<People> people; // holds People Chapter 4: Sequential Containers
The Vector • An enhancement of the array. • Can be used like an array: • Access a value via an index x = v[i]; v[j] = z; • Additional capabilities: • Increase or decrease its length • Insert an element at a specified position • Remove an element from a specified position Chapter 4: Sequential Containers
Vector Example // Create a vector to hold strings. vector<string> 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"); Chapter 4: Sequential Containers
Vector Example (2) Had to slide [2],[3] to [3],[4] my_vector.insert(2, “Doc”); Chapter 4: Sequential Containers
Vector Example (3) my_vector.push_back("Dopey"); // add at end Cheap to add at end Chapter 4: Sequential Containers
Vector Example (4) my_vector.erase(1); Had to slide [2..5] to [1..4] lst.set(1, “Sneezy”); Replacement is cheap Chapter 4: Sequential Containers
Example Applications of vector vector<Item_Type> some_Item_Types; Item_Type nums[] = {5, 7, 2, 15}; for (size_t i = 0; i < 4; i++) some_Item_Types.push_back(nums[i]); Item_Type sum = 0; for (size_t i = 0; i < 4; i++) { sum += some_Item_Types[i]; } cout << "sum is " << sum << endl; Chapter 4: Sequential Containers
Using vector in PhoneDirectory vector<Directory_Entry> the_directory; /** Adds a new entry @param name The name of the person @param new_number The new number */ void add(string name, string number) { Directory_Entry new_entry(name, number); the_directory.push_back(new_entry); } Chapter 4: Sequential Containers
Using vector in PhoneDirectory (2) string Phone_Directory::add_or_change_entry(const string& name, const string& number) { string old_number = ""; Item_Type index = find(name); if (index != -1) { old_number = the_directory[index].get_number(); the_directory[index].set_number(number); } else { add(name, number); } modified = true; return old_number; } Chapter 4: Sequential Containers
Implementing a vector Class • KW::vector: simple implementation of std::vector • Physical size of array indicated by data field current_capacity • Number of data items indicated by the data field num_items Chapter 4: Sequential Containers
KW::vector Fields, Constructor template<typename Item_Type> 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 /** Constructs<i> </i>an empty vector with the default initial capacity. */ vector<Item_Type>() : current_capacity(INITIAL_CAPACITY), the_data(new Item_Type[INITIAL_CAPACITY]), num_items(0) { } Chapter 4: Sequential Containers
Implementing vector::push_back Chapter 4: Sequential Containers
Implementing vector::push_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_data[num_items] = the_value; num_items++; } Chapter 4: Sequential Containers
Implementing vector::reserve void reserve(size_t new_capacity) { if (new_capacity > current_capacity) { if (new_capacity > 2 * current_capacity) current_capacity = new_capacity; else current_capacity *= 2; // Double the capacity. Item_Type* new_data = new Item_Type[current_capacity]; // Copy the data over. for (size_t i = 0; i < num_items; i++) new_data[i] = the_data[i]; // Free the memory occupied by the old copy. delete[] the_data; // Now point to the new data. the_data = new_data; } } Chapter 4: Sequential Containers
Implementing vector::insert Chapter 4: Sequential Containers
Implementing vector::insert (2) void insert(size_t index, const Item_Type& the_value) { // Validate index. if (index > num_items) { throw std::out_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 > index; i--) { the_data[i] = the_data[i - 1]; } // Insert the new item. the_data[index] = the_value; num_items++; } Chapter 4: Sequential Containers
Implementing vector::erase Chapter 4: Sequential Containers
Implementing vector::erase (2) void erase(size_t index) { // Validate index. if (index > num_items) { throw std::out_of_range ("index to insert is out of range"); } // Move items below the removed one up. for (size_t i = index + 1; i < num_items; i++) { the_data[i - 1] = the_data[i]; } num_items--; } Chapter 4: Sequential Containers
Implementing vector::operator[] Item_Type& operator[](size_t index) { // Verify that the index is legal. if (index < 0 || index >= num_items) { throw std::out_of_range ("index to operator[] is out of range"); } return the_data[index]; } Chapter 4: Sequential Containers
The 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. vector<Item_Type> v2(v1); will construct the vector v2 to be a copy of v1. Chapter 4: Sequential Containers
Shallow 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. Chapter 4: Sequential Containers
Shallow Copy after v1.push_back(20) Chapter 4: Sequential Containers
Deep Copy of a Vector Chapter 4: Sequential Containers
Copy Constructor That Makes Deep Copy vector<Item_Type>(const vector<Item_Type>& other) : current_capacity(other.capacity), num_items(other.num_items), the_data(new Item_Type[other.current_capacity]) { for (size_t i = 0; i < num_items; i++) the_data[i] = other.the_data[i]; } Chapter 4: Sequential Containers
Destructor • 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 memoryleak 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 destructor’s responsibility to free any allocated memory. Chapter 4: Sequential Containers
The vector’s destructor virtual ~vector<Item_Type>() { delete[] the_data; } Chapter 4: Sequential Containers
The swap function • The swap function swaps the data fields of two vectors. void swap(vector<Item_Type>& other) { std::swap(num_items, other.num_items); std::swap(current_capacity, other.current_capacity); std::swap(the_data, other.the_data); } Chapter 4: Sequential Containers
The 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. vector<Item_Type>& operator=(const vector<Item_Type>& other) { // Make a copy of the other vector. vector<Item_Type> the_copy(other); // Swap contents of self with the copy. swap(the_copy); // Return -- upon return the old value will be destroyed. return *this; } Chapter 4: Sequential Containers
The 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. Chapter 4: Sequential Containers
Performance 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 Chapter 4: Sequential Containers
Singly-Linked Lists and Doubly-Linked Lists • The vectorinsert 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) Chapter 4: Sequential Containers
A 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 Chapter 4: Sequential Containers
List Nodes for Singly-Linked Lists Chapter 4: Sequential Containers
List 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. @param data_item The data stored @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) {} }; #endif Chapter 4: Sequential Containers
struct 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. Chapter 4: Sequential Containers
Inserting a Node into a Single Linked List Node* bob = new Node("Bob"); bob->next = harry->next; // step 1 harry->next = bob; // step 2 Chapter 4: Sequential Containers
Removing a node from a single-linked list Node* ptr = tom->next; tom->next = tom->next->next; delete ptr; Chapter 4: Sequential Containers
Doubly-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 Chapter 4: Sequential Containers
Doubly-Linked Lists, The Diagrams Chapter 4: Sequential Containers
Inserting into a Double-Linked List DNode* sharon = new DNode("Sharon"); // Link new DNode to its neighbors sharon->next = sam; // Step 1 sharon->prev = sam->prev; // Step 2 Chapter 4: Sequential Containers
Inserting into a Double-Linked List (2) // Link old predicessor of sam to new predicessor. sam->prev->next = sharon; // Step 3 // Link to new predicessor. sam->prev = sharon; // Step 4 Chapter 4: Sequential Containers
Removal from a Double-Linked List harry->prev->next = harry->next; // Step 1 harry->next->prev = harry->prev; // Step 2 delete harry; Chapter 4: Sequential Containers
Circular 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 Chapter 4: Sequential Containers
Implementing a Circular List Chapter 4: Sequential Containers
The iterator • Suppose we want to access each element of a list in a loop: for (Item_Type index = 0; index < a_list.size(); index++) { // Do something with next_element, the element // at position index Item_Type next_element = a_list[index]; // not valid } • The subscripting operator (operator[]) is not defined for the list class. • Instead an iterator is used to access elements in a list. Chapter 4: Sequential Containers
The iterator (2) Chapter 4: Sequential Containers
Using an Iterator • We use an iterator like a pointer. // Access each list element and process it for (list<Item_Type>::iterator iter = a_list.begin(); iter != a_list.end(); ++iter) { // Do something with next element (*iter) Item_Type next_element = *iter; . . . } Chapter 4: Sequential Containers
Notes 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 < 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. Chapter 4: Sequential Containers