This assignment is an exercise in implementing the list ADT using a doubly-linked list. You will need to write a template structure to represent a list node, and a template class to represent the list.
Log in to Unix.
Run the setup
script for Assignment 10 by typing:
setup 10
You will need to write one template structure and one template class for this assignment. A main program to test your class will be provided.
Since this is a C++ template, all of the code for both the structure and the class should be placed in a single file, mylist.h
. That includes both structure / class definitions as well as the definitions for all of the functions. See the Hints section for an outline of how to structure this file and the order that things need to be coded.
node
structureData members
This template structure should have three data members: a member of the template parameter type T
used to store a value to be inserted into the list, a pointer to the previous node<T>
in the list, and a pointer to the next node<T>
in the list.
Member functions
Constructor
The structure should have a constructor that can be used to initialize the data members of the list node.
mylist
classData members
This class should have three data members. The first should be a pointer to a node<T>
that will point to the front node in the list (or be nullptr
if the list is empty). The second should be a pointer to a node<T>
that will point to the back node in the list (or be nullptr
if the list is empty). The third should be a size_t
variable that will be used to store the list size, the number of elements or values currently stored in the list.
Member functions and friend functions
The mylist
class should implement the following member functions and friend functions.
template <class T>
mylist<T>::mylist()
The class should have a default constructor that sets both pointer data members of the list to nullptr
and the list size to 0.
template <class T>
mylist<T>::~mylist()
The class should have a destructor. The destructor can simply call the clear()
method.
template <class T>
mylist<T>::mylist(const mylist<T>& x)
The class should have a proper copy constructor. Making a copy of the list nodes can be done in a similar fashion to a linked queue; all you need to do is traverse the list in the existing mylist
object and use push_back
to add each value to the back of the new mylist
object. If you use this technique, make sure that you set both the list front and list back pointers for the new mylist
to nullptr
and the list size to 0 before you attempt to insert any nodes into it.
template <class T>
mylist<T>& mylist<T>::operator=(const mylist<T>& x)
The assignment operator should be properly overloaded.
template <class T>
void mylist<T>::clear()
This member function should properly set the list back to the empty state, deleting all of the nodes in the linked list and setting the size back to 0.
template <class T>
size_t mylist<T>::size() const
Returns the list size.
template <class T>
bool mylist<T>::empty() const
Returns true if the list size is 0; otherwise, returns false.
template <class T>
const T& mylist<T>::front() const
This member function should return the value of the front node in the list. If the list is empty, throw an underflow_error
instead.
template <class T>
T& mylist<T>::front()
This member function should return the value of the front node in the list. If the list is empty, throw an underflow_error
instead.
template <class T>
const T& mylist<T>::back() const
This member function should return the value of the back node in the list. If the list is empty, throw an underflow_error
instead.
template <class T>
T& mylist<T>::back()
This member function should return the value of the back node in the list. If the list is empty, throw an underflow_error
instead.
template <class T>
void mylist<T>::push_front(const T& value)
This member function should insert an item at the front of the list. See the Hints section below for help on writing this function.
template <class T>
void mylist<T>::push_back(const T& value)
This member function should insert an item at the back of the list. See the Hints section below for help on writing this function.
template <class T>
void mylist<T>::pop_front()
This member function should remove the item at the front of the list. If the list is empty, throw an underflow_error
instead. See the Hints section below for help on writing this function.
template <class T>
void mylist<T>::pop_back()
This member function should remove the item at the back of the list. If the list is empty, throw an underflow_error
instead. See the Hints section below for help on writing this function.
template <class T>
bool mylist<T>::operator==(const mylist<T>& rhs) const
The equality operator should be overloaded to allow the comparison of two mylist
objects. This member function returns true if 1) the two lists contain the same number of nodes, and 2) if each element stored in the left-hand-side list is equal to the corresponding element of the right-hand-side list. Otherwise, the method returns false.
template <class T>
bool mylist<T>::operator<(const mylist<T>& rhs) const
The less than operator should be overloaded to allow the comparison of two mylist
objects. This member function will perform a "lexicographical" comparison of the two lists.
Lexicographical comparison means "dictionary" (element-by-element) ordering. That is, list1 is less than list2 if the first value of list1 is less than the first value of list2, and greater if the first value of list1 is greater than the first value of list2. If the two first value elements are equivalent then the member function compares the two second value elements, and so on.
The first list is also considered to be less than the second if every value in the first list is equal to the corresponding value in the second but the second list contains more elements.
template <class T>
std::ostream& operator<<(std::ostream& os, const mylist<T>& obj)
The stream insertion operator should be overloaded so that an entire mylist
object can be sent to standard output. This function will need to be a friend
rather than a member function. Start at the front of the list and traverse the list, printing the value in each node followed by a single space until you reach the end of the list.
The setup script will create the directory Assign10
under your csci241
directory. It will copy a makefile named makefile
to the assignment directory.
You will also receive a driver program named assign10.cpp
which contains a main()
function that will test your mylist
class.
The correct output for this assignment is shown below:
*** Testing default constructor *** *** Testing size(), operator<<(), and empty() with empty list *** l1 (size 0): l1 is empty *** Testing push_back() into empty list *** *** Testing size(), operator<<(), and empty() with non-empty list *** l1 (size 1): 40 l1 is not empty *** Testing push_back() into non-empty list *** l1 (size 3): 40 50 60 *** Testing push_front() into empty list *** l2 (size 1): c *** Testing push_front() into non-empty list *** l2 (size 3): a b c *** Testing push_front() and push_back() *** l1 (size 6): 10 20 30 40 50 60 l2 (size 6): a b c d e f *** Testing read version of front() and back() *** l1 front: 10 l1 back: 60 *** Testing write version of front() and back() *** l1 front: 5 l1 back: 65 *** Testing pop_back() *** l2 (size 4): a b c d *** Testing pop_front() *** l2 (size 2): c d *** Testing pop to empty *** l2 (size 0): *** Testing copy constructor *** l1 (size 6): 5 20 30 40 50 65 l3 (size 6): 5 20 30 40 50 65 *** Testing for shallow copy *** l1 (size 6): 10 20 30 40 50 60 l3 (size 6): 5 20 30 40 50 65 *** Testing copy assignment operator *** l1 (size 8): 10 20 30 40 50 60 70 80 l3 (size 8): 10 20 30 40 50 60 70 80 *** Testing for shallow copy *** l1 (size 9): 10 20 30 40 50 60 70 80 90 l3 (size 7): 20 30 40 50 60 70 80 *** Testing self-assignment *** l1 (size 9): 10 20 30 40 50 60 70 80 90 *** Testing chained assignment *** l1 (size 9): 10 20 30 40 50 60 70 80 90 l3 (size 9): 10 20 30 40 50 60 70 80 90 l4 (size 9): 10 20 30 40 50 60 70 80 90 *** Testing equality operator *** l1 and l4 are equal l1 and l3 are not equal l1 and l4 are not equal *** Testing less than operator *** l1 is less than l4 l4 is not less than l1 l1 is not less than l1 l1 is not less than l3 l3 is less than l1 l1 is not less than l3 l3 is less than l1 *** Testing clear() *** l1 (size 0): *** Testing for const correctness *** l4 (size 8): 20 30 40 50 60 70 80 90 l4 is not empty l4 front: 20 l4 back: 90 l4 and l1 are not equal l4 is not less than l3 l4 is not less than l1 l1 is less than l4 *** Testing exception handling *** Caught underflow exception on call to front() Caught underflow exception on call to back() Caught underflow exception on call to pop_front() Caught underflow exception on call to pop_back() Caught underflow exception on call to front() Caught underflow exception on call to back()
If you would like a copy of this output to compare against your own program's output using the diff
command, it is available on Unix at the pathname /home/turing/t90kjm1/CS241/Output/Spring2020/Assign10/output10.txt
.
The driver program is designed to make this assignment easy to develop incrementally. Simply comment out all of the lines of the driver program that call functions that you haven't written yet. You should be able to write, test, and debug function at a time.
For some hints on implementing push_back()
, push_front()
, pop_back()
, and pop_front()
, see the notes on Doubly-Linked List Insertion and Deletion on the course web site.
The logic for member functions such as the destructor, clear()
, the copy constructor, and the overloaded copy assignment operator are similar to what is described in the notes for singly-linked queues on the course web site.
To throw an underflow_error
when the list is empty, you can use code like this:
if (empty()) throw std::underflow_error("underflow exception on call to front()");
This creates an unnamed underflow_error
object and throws it. The string literal passed to the underflow_error
constructor can then be accessed by calling the object's what()
member function.
The class underflow_error
is defined in the header file <stdexcept>
.
Declaring a template function to be a friend
of a template class is one of the classic "gotcha's" in C++. We are trying to declare a function to be a friend
of all specializations that might be instantiated from the mylist
template class, and most C++ compilers will refuse to do that without some special syntax.
The friend
declaration must contain an extra set of <>
to indicate that it is a template function (however, do not code this in the actual function definition - only the friend
declaration). You'll also usually need to forward declare both the template class and the template function, as shown below.
How much of this you actually need to do can vary from compiler to compiler, but the code shown below works in Dev-C++ and with g++
on turing/hopper
.
#ifndef MYLIST_H #define MYLIST_H #include <iostream> #include <stdexcept> // Forward declaration of the mylist template class template <class T> class mylist; // Forward declaration of the operator<< template function template <class T> std::ostream& operator<<(std::ostream&, const Queue<T>&); template <class T> struct node { ... }; template <class T> class mylist { // friend declaration for the template function - note the special syntax friend std::ostream& operator<< <>(std::ostream&, const mylist<T>&); ... }; // Function definitions for the mylist class #endif