CS 204 Advanced Programming Pointers and Linked Lists

  • Slides: 37
Download presentation
CS 204 – Advanced Programming Pointers and Linked Lists Part 2: Advanced Issues

CS 204 – Advanced Programming Pointers and Linked Lists Part 2: Advanced Issues

Linked Lists: deleting. . . head //iterative void Delete. List (node *head) { node

Linked Lists: deleting. . . head //iterative void Delete. List (node *head) { node *temp; while (head != NULL) { temp = head->next; delete head; head = temp; } } //recursive void Delete. List (node *head) { if (head != NULL) { Delete. List(head->next); delete head; } } 2

Linked Lists: deleting. . . head //recursive void Delete. List (node *head) { if

Linked Lists: deleting. . . head //recursive void Delete. List (node *head) { if (head != NULL) { Delete. List(head->next); delete head; } } 1 2 3 Unrolling the execution steps would look like this with the above list (after the 1 st call that is with list’s head - ptr to node 1 - as argument): Delete. List(ptr to node 2) Delete. List(ptr to node 3) Delete. List(null) //returns immediately Delete ptr-to-node 3 //"delete head" line Delete ptr-to-node 2 Delete ptr-to-node 1 Notice that statements which are aligned occur in the same call to and execution order is top to bottom. 3

Adding a node to a sorted list You are given a linked list in

Adding a node to a sorted list You are given a linked list in which the elements are ordered. You want to add a node with a new element, but you want to keep the list still ordered. To do so, you have to spot the place to insert the new node by traversing the list. Here, there are some cases to consider: – What if the list is empty? – What if I need to add a node before the beginning of the list? – What if I need to add a node somewhere inside the list? • Caution you cannot go back! – What if I need to add a node to the end of the list? Now we are going to see some methods for this task. See ptrfunc. cpp for the implementation and a small demo 4

Adding a node to a sorted list node * Add. In. Order(node * head,

Adding a node to a sorted list node * Add. In. Order(node * head, int newkey) // pre: list is sorted // post: add newkey to list, keep list sorted, return head of new list { node * ptr = head; // loop variable // if new node should be first, handle this case and return // in this case, we return address of new node since it is new head if (head == NULL || newkey < head->info) { node * temp = new node; //node to be inserted temp->info = newkey; temp->next = head; //connect the rest return temp; } // check node one ahead so we don't pass! while (ptr->next != NULL && ptr->next->info < newkey) { ptr = ptr->next; } // postcondition: new node to be inserted just after the node ptr points //now insert new node with newkey after where ptr points to node * temp = new node; //node to be inserted temp->info = newkey; temp->next = ptr->next; //connect the rest ptr->next = temp; } return head; 5

Alternative using the constructor: Adding a node to a sorted list node * Add.

Alternative using the constructor: Adding a node to a sorted list node * Add. In. Order(node * head, int newkey) // pre: list is sorted // post: add newkey to list, keep list sorted, return head of new list { node * ptr = head; // loop variable // if new node should be first, handle this case and return // in this case, we return address of new node since it is new head if (head == NULL || newkey < head->info) { node * temp = new node; //node to be inserted temp->info = newkey; temp->next = head; //connect the rest return new node(newkey, head); return temp; } // check node one ahead so we don't pass! while (ptr->next != NULL && ptr->next->info < newkey) { ptr = ptr->next; } // postcondition: new node to be inserted just after the node ptr points //now insert new node with newkey after where ptr points to node * temp = new node; //node to be inserted temp->info = newkey; temp->next = ptr->next; //connect the rest ptr->next = temp; } return head; ptr->next = new node(newkey, ptr->next); 6

Adding a node to a sorted list – Another alternative method node * Add.

Adding a node to a sorted list – Another alternative method node * Add. In. Order(node * head, int newkey) // pre: list is sorted // post: add newkey to list, keep list sorted, return head of new list { node * ptr = head; // loop variable // if new node should be first, handle this case and return // in this case, we return address of new node since it is new head if (head == NULL || newkey < head->info) { return new node(newkey, head); } node * prev; //to point to the previous node while (ptr != NULL && ptr->info < newkey) { prev = ptr; //hold onto previous node so we do not pass too far ptr = ptr->next; } // postcondition: new node to be inserted between prev and ptr //now insert node with newkey prev->next = new node(newkey, ptr); return head; } 7

Deleting a single node head to. Be. Deleted • Several algorithms exist • We

Deleting a single node head to. Be. Deleted • Several algorithms exist • We will cover one – to. Be. Deleted points to the node to be deleted – Need to keep another pointer for the previous node – Special case if the first node to be deleted • No previous node • Head must be updated • See the next slide for the code – Also in ptrfunc. cpp 9

Deleting a single node head to. Be. Deleted void Delete. One. Node (node *

Deleting a single node head to. Be. Deleted void Delete. One. Node (node * to. Be. Deleted, node * & head) /* pre: to. Be. Deleted points to the node to be deleted from the list post: deletes the node pointed by to. Be. Deleted, updates head if changes */ { node * ptr; if (to. Be. Deleted == head) //if the node to be deleted is the first node { head = head->next; delete to. Be. Deleted; } else //if the node to be deleted is in the middle or at the end { ptr = head; while (ptr->next != to. Be. Deleted) ptr = ptr->next; //after while, ptr points to the node just before to. Be. Deleted //connect the previous node to the next node and delete ptr->next = to. Be. Deleted->next; delete to. Be. Deleted; } } 10

Circular lists last • Last node does not point to NULL, but points to

Circular lists last • Last node does not point to NULL, but points to the first node. • No change in the node struct definition, but processing the list is different now • Actually there is no head or tail – All nodes are semantically same – Keeping a pointer for any node would work – However, for the sake of visualization and ease of coding this pointer can be for the pseudo-last node of the list. • (Pseudo) First node of the list is next of last! • For traversal the entire list, no sentinel check such as NULL – instead stop where you started – Of course, if the list is not empty; if empty, last is NULL • See next slide for an example (also see ptrfunc. cpp) 11

Circular lists last • Count number of elements in a circularly linked list. int

Circular lists last • Count number of elements in a circularly linked list. int count. Circular (node * last) //pre: list is a circularly linked one, last points to the last node //post: returns number of nodes in the list { if (last == NULL) return 0; //list is empty int count = 0; node * ptr = last; do { count++; ptr = ptr->next; //advance to the next line } while (ptr != last); //loop until you reach where started return count; } 12

Doubly linked lists head • Each node does not only keep a pointer for

Doubly linked lists head • Each node does not only keep a pointer for the next node, but also for the previous node. • Implications – In this way you can traverse the list in both directions – Need to keep not only head, but also tail – Insert, delete operations are a bit more complex • Convention – Next of tail is NULL (as in regular linked list) – Prev of head is also NULL tail struct node { int info; node *next; node *prev; }; Think of routines for building, insertion, deletion, etc. 13

Other type of linked lists • Circular and doubly linked lists • Two dimensional

Other type of linked lists • Circular and doubly linked lists • Two dimensional linked lists • Multidimensional linked lists • Hybrid lists – e. g. Head is a struct of a different type and includes a pointer for our list – Two dimensional version of the above • Circular and/or doubly linked versions of the above • As you see, sky is the limit : -) 14

Linklist class with pointers An example class Class definition (in the header file –

Linklist class with pointers An example class Class definition (in the header file – say linkedlist. h) Definition of struct node can be either in the same header file or in another included header file class linkedlist { private: node * head; public: linkedlist (); void print. List(); void add. To. Beginning(int n); }; 15

Linklist class with pointers Class implementation (in a cpp file - say linkedlist. cpp):

Linklist class with pointers Class implementation (in a cpp file - say linkedlist. cpp): linkedlist: : linkedlist () { head = NULL; } void linkedlist: : add. To. Beginning (int n) { node *ptr = new node(n, head); head = ptr; } void linkedlist: : print. List () { node * ptr = head; while (ptr != NULL) { cout << ptr ->info << endl; ptr = ptr->next; } cout << endl; } Notice here that head is updated, but we did not need add. To. Beginning to return a parameter or use reference variables, because member functions already access the fields of the current object. in main cpp file (linkedlistdemo. cpp): linkedlist mylist, list 2; mylist. Add. To. Beginning(5); list 2. Add. To. Beginning(1); 16

Using Pointers: Be Careful Remember to guard every pointer dereference so as not to

Using Pointers: Be Careful Remember to guard every pointer dereference so as not to access the contents of a NULL pointer. e. g. : while (p != NULL && p->info < key). . . Thanks to short-circuit evaluation rule of C++; when p is null, p != NULL is evaluated to false and the rest of the boolean expression is not evaluated. On the other hand, the followings would crash your program when p is null pointer. while (p->info < key). . . while (p->info < key && p != NULL). . .

Using Pointers: good practices C++ allows programmers to define new types (actually an alias

Using Pointers: good practices C++ allows programmers to define new types (actually an alias for a type) using the typedef statement: typedef int* Int. Ptr; Int. Ptr p 1, p 2; //same as int * p 1, p 2; Avoids mistake of forgetting the * character Simpler and more intuitive to use pointer reference parameter: void get_space(Int. Ptr & ptr); same as void get_space(int* & ptr); 18

Using pointers for pass-by-reference • You can use a pointer variable for the same

Using pointers for pass-by-reference • You can use a pointer variable for the same job of a reference parameter – As the argument, you pass the address of a regular variable – So that when you use dereferencing within the function, you reach and change the argument's value. – This is more of C style, but know it! void swap (int & x, int & y) { int temp = x; x = y; y = temp; } Using reference parameters void swap (int * x, int * y) { int temp = *x; *x = *y; *y = temp; } Using pointers This is called as: int num 1=13, num 2=4; . . . swap (&num 1, &num 2); //num 1 becomes 4, num 2 becomes 13 19

C style dynamic memory management • malloc (size) – A function that dynamically allocates

C style dynamic memory management • malloc (size) – A function that dynamically allocates size bytes – Returns void pointer (to be cast to other types of pointers) • calloc (num, length) – A function that dynamically allocates num*length bytes • In order to allocate an array of num elements, length is calculated using sizeof function. • sizeof (type_name) returns the number of bytes used by type_name. – Returns void pointer (to be cast to other types of pointers) • free(ptr) – Deallocates (or frees) a memory block pointed by ptr. • The memory block to be freed should have been previously allocated with malloc or calloc. • You may need to include <stdlib. h> and <malloc. h> • The usage these memory blocks is mostly the same as the ones allocated with new. • Let's see an example use at malloc_calloc. cpp 20

Pointers and Dynamic Arrays

Pointers and Dynamic Arrays

1 D arrays with the new operator To dynamically allocate an array, specify the

1 D arrays with the new operator To dynamically allocate an array, specify the array size in square brackets [] after the type: int *ptr; ptr = new int [20]; // dynamically allocate enough // memory for an array of 20 ints. // ptr now points to the // 0 th element of the array. ptr Instead of 20 (size of the allocated array), you can also use a user-defined variable: cin >> size; ptr = new int [size]; Here be careful that memory allocation is dynamic, but the size of the array cannot be enlarged or reduced once the memory allocation is done To free the memory allocated to a dynamically allocated array, you must include a pair of empty square brackets in the delete statement, just after the delete command: delete [] ptr; 22

Memory allocation with the new operator Points to be careful What happens if you

Memory allocation with the new operator Points to be careful What happens if you don't have enough memory? NULL pointer is returned You need to check for that possibility int * ptr; ptr = new int [100000]; if ( ptr != NULL). . . 23

Pointers and 2 D Dynamic Arrays 24

Pointers and 2 D Dynamic Arrays 24

When we need 2 -dimensional data for matrices, images etc, we have two option:

When we need 2 -dimensional data for matrices, images etc, we have two option: 1) Create a 1 -dimensional data, by using: data = new int [rows * columns]; Now data represents the 2 D structure implicitly. For instance the element M[x, y] is found in data[x*columns+y]. [0…columns-1] indices belong to first row, [columns … 2*columns-1] indices belong to 2 nd row, [2*columns … 3*columns-1] indices belong to 3 rd row, etc. 2) Create a truly 2 -dimensional data. In this case array indexing is easier. Will see both cases 25

Case 1) 1 d data w/ class (See matrixclass. h, matrixclass. cpp, matrixclassdemo. cpp)

Case 1) 1 d data w/ class (See matrixclass. h, matrixclass. cpp, matrixclassdemo. cpp) class Matrix 1 D { private: int rows, cols; int * data; public: Matrix 1 D(int r, int c); //constructor int Get. Index(int i, int j); void Set. Index(int i, int j, int val); }; Matrix 1 D: : Matrix 1 D(int r, int c) { rows = r; cols = c; int size = rows * cols; data = new int [size]; //this is a one long array of ints – 1 D } int Matrix 1 D: : Get. Index(int i, int j) { return data[i*cols+j]; } Usage in main: Matrix 1 D m(5, 10); //a 5 x 10 matrix m. Set. Index(0, 5, 33); cout << m. Get. Index(0, 5) << endl; void Matrix 1 D: : Set. Index(int i, int j, int value) { data[i*cols+j] = value; } 26

Pointer to pointer • Syntax Type ** variable; • Example int ** p; p

Pointer to pointer • Syntax Type ** variable; • Example int ** p; p ? ? p = new int *; p ? *p = new int; p ? **p = 12; p 12 cout << **p; //Displays 12 • We will make use of pointer to pointer for the implementation of 2 D arrays (see next) 27

Case 2) 2 d data allocation – w/o classes int ** M; int rows,

Case 2) 2 d data allocation – w/o classes int ** M; int rows, columns, i, j; cout<<"Enter the number of rows: "; cin >> rows; cout<<"Enter the number of columns: "; cin >> columns; // M is an array of int pointers M = new int* [rows]; //each M[i] is a pointer (array of int) for (i = 0; i<rows; i++) M[i] = new int [columns]; cout << "Enter the elements" << endl; for (i = 0; i < rows; i++) for (j= 0 ; j< columns ; j++) { cout<< '[' << i << ', ' << j << "]: "; cin >> M[i][j]; cout<< endl; } print_table(M, rows, columns); for (i = 0; i< rows; i++) // Returning memory to free heap for reuse delete [] M[i]; delete [] M; 28

Case 2) 2 d data allocation – w/o classes void print_table(int** values, int num_rows,

Case 2) 2 d data allocation – w/o classes void print_table(int** values, int num_rows, int num_cols) { int i, j; for (i = 0; i < num_rows; i++) { for (j= 0 ; j< num_cols ; j++) cout << values[i][j] << " "; cout << endl; } } • Question: What happens if we change the values of the matrix elements in the above function? – e. g. values[2][1] = 100; • The above code (this and prev. slide) is shown in matrixnoclass. cpp 29

Case 2) 2 d data allocation w/ class (See matrixclass. h, matrixclass. cpp, matrixclassdemo.

Case 2) 2 d data allocation w/ class (See matrixclass. h, matrixclass. cpp, matrixclassdemo. cpp) class Matrix 2 D { private: int rows, cols; int ** data; int Matrix 2 D: : Get. Index(int i, int j) { return data[i][j]; } public: Matrix 2 D(int r, int c); int Get. Index(int i, int j); void Set. Index(int i, int j, int val); }; void Matrix 2 D: : Set. Index(int i, int j, int value) { data[i][j] = value; } Matrix 2 D: : Matrix 2 D(int r, int c) { rows = r; cols = c; data = new int* [r]; for (int i = 0; i<rows; i++) data[i] = new int[cols]; } Full implementation is in matrixclass. h, matrixclass. cpp. Usage in main: Matrix 2 D m(5, 10); //a 5 x 10 matrix m. Set. Index(0, 5, 33); cout << m. Get. Index(0, 5) << endl; 30

Pointers and Static Arrays Efficiency Issues

Pointers and Static Arrays Efficiency Issues

Pointers and Arrays int myarray[10]; //static (a. k. a. built-in) array definition int *ptr;

Pointers and Arrays int myarray[10]; //static (a. k. a. built-in) array definition int *ptr; ptr = myarray; //address stored in pointer is the address of the // first element in array OR ptr = &myarray[0]; //equivalent and more clear After that, you can refer to myarray[i] also as ptr[i] See ptr_staticarrays. cpp for an example 32

Pointers and Arrays: Increment int students[10]; int *ptr; ptr = students; //ptr points to

Pointers and Arrays: Increment int students[10]; int *ptr; ptr = students; //ptr points to students [0] ptr = &students[0]; //same as above ptr++; //ptr points to students[1] • ptr increments by the size of the type pointed by the pointer – in this case the pointer points to 4 bytes ahead (not 1 byte) to the next integer 33

Pointer Increment/Decrement Depends on Pointed Type char c = 'z'; char *p; p =

Pointer Increment/Decrement Depends on Pointed Type char c = 'z'; char *p; p = &c; p--; p++; //p points nowhere // p now points to c. // p now points to the address of the BYTE in the runtime stack before c // p points to c again. If the pointer pointed to integers: int myintarr [20]; int * p = myintarr; p++; // p now points to 4 BYTES ahead (when integers take 4 bytes) This is the reason why we don’t just say: pointer p; but we call the pointers by their types We want the compiler to move the pointer by the size of the data type it points to. Actually, not only increment/decrement, any integer addition/subtraction works in the same manner 34

Pointers and 2 D static arrays int my 2 d [3][5]; //2 D static

Pointers and 2 D static arrays int my 2 d [3][5]; //2 D static array definition //to have pointer for this array, we need an array of ptrs for each row //and assign each row of 2 D array to each element of ptr array int * ptr 2[3]; ptr 2[0] = my 2 d[0]; // &my 2 d[0][0] also works ptr 2[1] = my 2 d[1]; ptr 2[2] = my 2 d[2]; int **ptr 3 = ptr 2; //ptr 3 points to the pointer array // &ptr 2[0] also works After that, you can refer to my 2 d[i, j] also as ptr 3[i, j] See ptr_staticarrays. cpp for an example 35

Pointer use for efficiency - 1 const int NUM_STUDENTS 10; int students[NUM_STUDENTS]; int *p;

Pointer use for efficiency - 1 const int NUM_STUDENTS 10; int students[NUM_STUDENTS]; int *p; for (i=0; i < NUM_STUDENTS; i++) cout << students[i]; or p = &students[0]; for (i=0; i < NUM_STUDENTS; i++) cout << *p++; 2 nd one is direct access to memory. 1 st one requires addition to calculate the address of array element each time. Thus 2 nd one is more efficient, use it!! 36

Ptr use for efficiency - 2 • Assume we have a static 1 D

Ptr use for efficiency - 2 • Assume we have a static 1 D array which actually holds 2 D data: char img [width*height]; //grayscale image with pixel values 0 -255 size = width*height; for (i=0; i < size; i++) { if (img[i] > 200) img[i] = 255; } char * ptr = &img[0]; for (i=0; i < size; i++) { if (*ptr > 200) *ptr=255; ptr++; } //assign a new gray value //How much do you save? ? //Lets say size is 10, 000. //2*10, 000 additions for array indexing //are replaced with 10, 000 increments 37

Ptr use for efficiency - 3 Typical char array/string copying routine Note that memory

Ptr use for efficiency - 3 Typical char array/string copying routine Note that memory for destination has already been allocated in the calling program void my_strcpy (char * destination, char * source) { char *p = destination; while (*source != '') { *p++ = *source++; //I had told you not to use cryptic code //but this piece of code is very common that everyone can understand it easily } *p = ''; //why do we need this? } 38