Dynamic Memory and Pointers Lecture Notes PDF
Document Details
Uploaded by FervidDune
ETH Zurich
Tags
Summary
These lecture notes cover dynamic memory allocation and pointers in C++. The material discusses addresses, constant pointers, and the use of the "new" expression. Concepts and code examples related to these aspects are included.
Full Transcript
19. Dynamic Memory and Pointers Addresses and Pointers, Const-Pointer, new expression 485 Wagons Goal: link together arbitrarily many wagons Idea: objects of type wagon that store the next wagon...
19. Dynamic Memory and Pointers Addresses and Pointers, Const-Pointer, new expression 485 Wagons Goal: link together arbitrarily many wagons Idea: objects of type wagon that store the next wagon 486 Wagons: 1st Try struct wagon { unsigned int id; wagon next; }; field 'next' has incomplete type 'wagon' ??? id next struct cannot have member variable of the same type (infinite recursion) 487 Wagons: 2nd Try struct wagon { unsigned int id; wagon& next; wagon(unsigned int id) : id(id) {} }; id next wagon w = wagon(1); uninitialized reference member in 'struct wagon&' references need to be initialized when declared 488 Pointer pointer are like references that are allowed to point nowhere! wagon&: reference to a wagon wagon*: pointer to a wagon Pointers can be modified after their creation! 489 Pointer Types T* Pointer type for base type T An expression of type T* is called pointer (to T) int* p =...; // Pointer to an int std::string* q =...; // Pointer to a std::string 490 Pointer Types T* Pointer type for base type T A T* must actually point to a T int* p =...; std::string* q = p; // compiler error! 491 Pointer Types int (e.g. 5) addr addr p (e.g. 0x7ffd89d5f7cc) int* p =...; std::cout n; assert(n >= 1); wagon first = wagon(1); wagon* last = &first; for (unsigned int i = 2; i next = new_wagon; last = new_wagon; } std::cout id; // outputs 2 std::cout next->id; // outputs 3 507 20. Dynamic Data Structures Linked List std::list (simplified) 508 Motivation Deletion (from a vector) is cumbersome – if the order of elements is important and holes must be avoided. For insertion, an analog statement holds. 509 Different Memory Layout: Linked List No contiguous area of memory and no random access Each element points to its successor Insertion and deletion of arbitrary elements is simple 1 5 6 3 8 8 9 pointer ⇒ std::list vector can be implemented as a linked list 510 Linked List: Problem Solved 511 Linked List: Zoom element (type struct lnode) 1 5 6 value (type int) next (type lnode*) struct lnode { int value; lnode* next; lnode(int v, lnode* n): value(v), next(n) {} // Constructor }; 512 Liste = Pointer to the First Element element (type struct lnode) 1 5 6 value (type int) next (type lnode*) class our_list { lnode* head; public: our_list(int size); int size() const;... }; 513 Function our_list::print() struct lnode { int value; lnode* next;... }; void our_list::print() const { for (lnode* n = head; Pointer to first element n != nullptr; Abort if end reached n = n->next) Advance pointer element-wise { std::cout Output value next) { std::cout value next; return n->value; Return ith element } 516 Function our_list::push_front() Advantage our_list: Prepending elements is very easy: void our_list::push_front(int e) { head = new lnode(e, head); } head 4 1 5 6 Attention: If the new lnode weren’t allocated dynamically, then it would be deleted (= memory deallocated) as soon as push_front terminates 517 Function our_list::our_list() Constructor can be implemented using push_front(): our_list::our_list(int size) { head = nullptr; head initially points to nowhere for (int i = 0; i < size; ++i) Prepend 0 size times push_front(0); } Use case: our_list v = our_list(3); v.print(std::cout); // 0 0 0 518 Function our_list::push_back() v1 Simple, but inefficient: traverse linked list to its end and append new element. void our_list::push_back(int e) { lnode* n = head; Start at first element...... and go to the last element for (; n->next != nullptr; n = n->next); n->next = new lnode(e, nullptr);Append new element to cur- rently last } What happens, if the list is empty? 519 Function our_list::push_back() v1 What happens, if the list is empty? → then head == nullptr... void our_list::push_back(int e) { lnode* n = head; for (; n->next != nullptr; n = n->next);...... and we dereference nullptr → undefined behavior! } 520 Function our_list::push_back() v2 Final version, without previous undefined behavior: void our_list::push_back(int e) { if (head == nullptr) { // Case 1: empty list head = new lnode(e, nullptr); } else { // Case 2: non-empty list (code as before) lnode* n = head; for (; n->next != nullptr; n = n->next); n->next = new lnode(e, nullptr); } } 521 Function our_list::push_back() More efficient, but also slightly more complex: 1. Second pointer, pointing to the last element: tail 2. Using this pointer, it is possible to append to the end directly 1 5 6 4 head tail But: Several corner cases, e.g. vector still empty, must be accounted for 522 Function our_list::size() Simple, but inefficient: compute size by counting unsigned int our_list::size() const { unsigned int c = 0; Count initially 0 for (lnode* n = head; n != nullptr; Count linked-list length n = n->next) ++c; return c; Return count } 524 Function our_list::size() More efficient, but also slightly more complex: maintain size as member variable 1. Add member variable unsigned int count to class our_list 2. count must now be updated each time an operation (such as push_front) affects the vector’s size 525 An iterator for our_list We need: 1. An our_list-specific iterator with at least the following functionality: Access current element: operator* Advance iterator: operator++ End-reached check: operator!= (or operator==) 2. Member functions begin() and end() for our_list to get an iterator to the beginning and past the end, respectively 526 Iterator our_list::iterator (Step 1/2) class our_list {... public: class iterator {... };... } The iterator belongs to our list, that’s why iterator is a public inner class of our_list Instances of our iterator are of type our_list::iterator 527 Iterator our_list::iterator (Step 1/2) class iterator { lnode* node; Pointer to current vector element public: iterator(lnode* n); Create iterator to specific element iterator& operator++(); Advance iterator by one element int& operator*() const; Access current element bool operator!=(const iterator& other) const; }; Compare with other iterator 528 Iterator our_list::iterator (Step 1/2) // Constructor our_list::iterator::iterator(lnode* n): node(n) {} Let iterator point to n initially // Pre-increment our_list::iterator& our_list::iterator::operator++() { assert(node != nullptr); node = node->next; Advance iterator by one element return *this; Return reference to advanced iterator } 529 Iterator our_list::iterator (Step 1/2) // Element access int& our_list::iterator::operator*() const { return node->value; Access current element } // Comparison: when are two iterators not equal? bool our_list::iterator::operator!=( const our_list::iterator& other) const { return node != other.node; } this iterator different from other if they point to different element 530 An iterator for our_list (Repetition) We need: 1. An our_list-specific iterator with at least the following functionality: Access current element: operator* Advance iterator: operator++ ✓ End-reached check: operator!= (or operator==) 2. Member functions begin() and end() for our_list to get an iterator to the beginning and past the end, respectively 531 Iterator our_list::iterator (Step 2/2) class our_list {... public: class iterator {...}; iterator begin(); iterator end();... } our_list needs member functions to issue iterators pointing to the beginning and past the end, respectively, of the vector 532 Iterator our_list::iterator (Step 2/2) our_list::iterator our_list::begin() { return our_list::iterator(head); } Iterator to first vector element our_list::iterator our_list::end() { return our_list::iterator(nullptr); } Iterator past last vector element 533 Random Access with our_list::operator[]? Accessing ith Element int& our_list::operator[](unsigned int i) { lnode* n = head; for (int j = 0; j < i; ++j) n = n->next; return n->value; } needs to iterate through the first i elements... How do we get random access? 534