You are to implement a doubly-linked list class using dynamic memory and links. Your class should be similar to the doubly-linked-list class presented in class (dll), except that it is to be linear rather than circular and is not to use a sentinel link. This implementation is the one programmers usually use if they do not know about the circular implementation with a sentinel. It usually requires keeping track of the location of the first and last links in the list. The first link's pointer to the previous link and the last link's pointer to the next link should ``point nowhere''. In the following, we will call pointers that point nowhere null pointers. If the list is empty (has no elements), the pointers to its first and last links should be null pointers.
A doubly-linked list is a data structure with objects arranged in linear order and permitting easy access to both previous and next items. For example, the STL list class implements a doubly-linked list. (Interestingly, STL creator Alex Stepanov apparently also decided to use a circular implementation with a sentinel.)
Your implementation should support the operations listed in the following table.
Function prototype | Example use | Explanation |
---|---|---|
dll(void) | dll lst; | creates a list with no items. |
item_type | dll::item_type i = 'a'; | type specifying a list entry. |
link | dll::link * lnk = lst.erase(lnkPtr); | type holding a list element (i.e., a link). |
link * insert(link * const pos, const item_type & item) | lnkPtr = lst.insert(lnkPtr, 'b'); | adds the specified item before the specified position. |
link * erase(link * const linkPtr) | dll::link * lnk = lst.erase(lnkPtr); | removes the specified item from the list. |
link * begin(void) const | dll::link * lnkB = lst.begin(); | returns a pointer to the first link in the list. |
link * end(void) const | dll::link * lnkE = lst.end(); | returns a pointer past the last link in the list. |
link * pred(link * const lnk) const | dll::link * lnk = lst.pred(lnkE); | returns a pointer to the link just before the given link. |
link * succ(link * const lnk) const | dll::link * lnk = lst.succ(lnkB); | returns a pointer to the link just after the given link. |
When inserting an item into a list, the new link (for the inserted item) should be to the left of the user-specified position. If this position is a null pointer, the item should be placed at the right end of the list. Insertion at the left end of the list is accomplished by inserting the desired item before the leftmost link. To erase a link, the user need only specify the link to erase. Unlike the implementation presented in class, insert() should return a pointer to the inserted link. The erase() function returns the link to the right of the erased link, or a null pointer if the rightmost link was removed. Both functions may assume that their parameter values are valid. (That is, insert() can assume that its pointer parameter is either null or points to a valid link, and erase() can assume that its parameter points to a valid link.)
The last four functions permit the user to ``move'' through the items in the list. begin() and end() return pointers to a list's first link and one past its last link, respectively. (So, end() should return a null pointer.) Given a pointer into the list, pred() and succ() return pointers to its predecessor (the link to its left) and it successor (the link to its right), respectively. The predecessor of the leftmost link is a null pointer as is the successor of the rightmost link. When given a null pointer, pred() should assume it points to just past the end of the list and should return a pointer to the rightmost link. When given a null pointer, succ() should assume it points to just before the beginning of the list and should return a pointer to the leftmost link.
Please ensure that, if a list is copied, changing the original list's contents does not change the copy and vice versa. (Hint: Write an assignment operator and a copy constructor if necessary.) Be sure not to leak dynamic memory. (Hint: Write a destructor if necessary.)
For now, assume the list contains chars, but write your code using item_type for the type of the list items, so that it will be easy to templatize later.
In class, we presented algorithms and code (dll-1.h) for a circular implementation with a sentinel link. In this assignment, we will use a linear implementation with no sentinel. For example, here are
and
The linear implementation will be very similar to the circular implementation with a sentinel, except that the routines cannot always assume that there is a link to the left and right when inserting and erasing. For example, consider inserting a link at the right end of a list with one link. We are given a null pointer. Assume we have already created a link named n to hold the new item.
We need to know the link to the left. Thus, let us assume the dll object maintains a link * r always pointing to the list's rightmost link. The first step is to set n's pointers to the correct locations.
The next step is to change the surrounding links' pointers. Since there is no link to the right of n, we cannot change its link. Finally, we update the object's pointer to the rightmost link.
This implementation differs from the circular implementation with a sentinel in that (i) we needed a pointer to the list's rightmost link and (ii) we did not change the pointer of the link on the right.
Your implementation should support insertion and erasure anywhere in a list (at the beginning, middle, or end) and for a list of any length (empty, one link, or multiple links). Before writing code, I strongly recommend that you draw pictures of all possible cases and annotate with the associated code. Otherwise, the probability of obtaining a correct implementation is minimal.
The recommended strategy for implementing and testing your code is to interleave writing member functions, compiling, and testing. That is, choose an order for implementing member functions that minimizes the amount of code written between compilations and testing. (A revised version of the dll class, including all the functions you are required to write, can be found in sample program file dll-2.h. You are welcome to use this as a starting point, but I recommend that you begin by removing or commenting out everything except the function prototypes and then proceed as follows.)
After you have finished the implementation, try using it with the simple-minded (a.k.a. ``brain-dead'') string editor presented in class. To do this, copy files editor-2.cpp and buffer-2.h into your directory, be sure your definition of the dll class is in a file called dll-2.h, and compile editor-2.cpp.
Your implementation should correctly use dynamic memory. For example, dynamic memory should be deleted when no longer used, and deleted memory should not be used. If you wish to use the mtrace command to help find memory-use errors, here are the instructions again:
#include <mcheck.h>and, at the beginning of main(), add
mtrace();Let's assume the executable is named a.out.
declare -x MALLOC_TRACE=foo.txtin a shell. (You can replace foo.txt with any filename you choose.)
mtrace a.out $MALLOC_TRACEin the same shell.
Some caveats:
I suggest starting with the implementation presented in class and converting it to a linear implementation. These two files should be useful:
You may also want to get files editor-2.cpp and buffer-2.h, to test your code with the string-editor program.
Submit only the file containing your revised implementation of the doubly-linked-list class (dll.h or dll-2.h), as described in the Guidelines for Programming Assignments. For this assignment use a subject line of ``cs1321 hw 6''.