• No results found

Having successfully defi ned the core methods, we will continue to defi ne the remaining ones in our class.

The method getFrequencyOf . To count the number of times a given object occurs in a bag, we

count the number of times the object occurs in the linked chain. To do so, we need to traverse the chain and compare each of its data items with the given object. Each time we fi nd a match, we incre- ment a counter. When the traversal ends, we return the value of the counter.

The loop we will use is like the one we wrote in the method toVector . Here is the defi nition of getFrequencyOf :

template< class ItemType>

int LinkedBag<ItemType>::getFrequencyOf( constItemType& anEntry)const {

int frequency = 0; int counter = 0;

Node<ItemType>* curPtr = headPtr;

while ((curPtr != nullptr) && (counter < itemCount)) { if (anEntry == curPtr->getItem()) { frequency++; } // end if counter ++; curPtr = curPtr->getNext(); } // end while return frequency; } // end getFrequencyOf

The method contains . The discussion in Chapter 3 about the method contains applies here as well. Although the method could call the method getFrequencyOf , which we just defi ned, doing so usually will involve more work than is necessary. Whereas getFrequencyOf must check every entry in the bag,contains exits as soon as it fi nds an entry in the bag that is the same as the given one.

We observed in the previous chapter that the methods contains and remove perform the same search for a specifi c entry. Thus, to avoid duplicate code, we perform this search in a private method that bothcontains and remove can call. We declared this private method in the header fi le, and its defi nition follows.

// Returns either a pointer to the node containing a given entry // or the null pointer if the entry is not in the bag.

template< class ItemType>

Node<ItemType>* LinkedBag<ItemType>::

getPointerTo( const ItemType& target) const {

bool found = false;

Node<ItemType>* curPtr = headPtr;

Question 4

Suppose that the ADT bag had an operation that displayed its contents. Write a C++ defi nition for such a method for the class LinkedBag .

CHECK POINT

Question 5

How many assignment operations does the method that you wrote for the previous question require?

while (!found && (curPtr != nullptr)) { if (target == curPtr->getItem()) found = true; else curPtr = curPtr->getNext(); } // end while return curPtr; } // end getPointerTo

The definition of the method contains is straightforward: template< class ItemType>

bool LinkedBag<ItemType>::contains( const ItemType& anEntry) const {

return (getPointerTo(anEntry) !=nullptr); } // end contains

The method remove. Recall that the method remove deletes one occurrence of a given entry and re- turns either true or false to indicate whether the removal was successful. Just as adding a new node to a linked chain is easiest at its beginning, so is removing the fi rst node. But the entry that we need to remove is not always in the chain’s fi rst node.

Suppose that we locate the entry to delete in node n . We can replace that entry with the entry in the fi rst node, and then delete the fi rst node. Thus, we can describe the logic for remove with the following pseudocode:

remove(anEntry)

Find the node that contains anEntry

Replace anEntry with the entry that is in the first node

Delete the first node

By using the private method getPointerTo to locate the entry that we want to delete, we can defi ne the method remove as follows:

template< class ItemType>

bool LinkedBag<ItemType>::remove( const ItemType& anEntry) {

Node<ItemType>* entryNodePtr = getPointerTo(anEntry);

bool canRemoveItem = !isEmpty() && (entryNodePtr != nullptr); if (canRemoveItem)

Question 7

Trace the execution of the method contains when the bag is empty.

Question 8

Revise the defi nition of the method getPointerTo so that the loop is control- led by a counter and the value of itemCount .

Question 9

What is a disadvantage of the defi nition of the method getPointerTo , as described in the previous question, when compared to its original defi nition?

Question 10

Why should the method getPointerTo not be made public?

Question 6

If the pointer variable curPtr becomes nullptr in the method getPointerTo , what value does the method contains return when the bag is not empty?

A Link-Based Implementation of the ADT Bag 145

{

// Copy data from first node to located node entryNodePtr->setItem(headPtr->getItem());

// Delete first node

Node<ItemType>* nodeToDeletePtr = headPtr; headPtr = headPtr->getNext();

// Return node to the system

nodeToDeletePtr->setNext( nullptr); delete nodeToDeletePtr; nodeToDeletePtr =nullptr; itemCount--; } // end if return canRemoveItem; } // end remove

After the method remove deletes a node, the system can use this returned memory and possibly even reallocate it to your program as a result of the new operator. Suppose that this reallocation actually occurs when you ask for a new node for your linked chain. You can be sure that your new node does not still point to your linked chain, because you executed the statement nodeToDeletePtr->setNext(nullptr) before you deallocated the node. Doing this and setting the variable nodeToDeletePtr to nullptr are examples of defensive programming that can avoid devastating, subtle errors later in the program. We take these steps, even though nodeToDeletePtr is a local variable that we do not use again, to clarify our intent to future programmers who revise our method.

Programming Tip:

Remember that any time you allocate memory by using new , you must eventually deallocate it by using delete .

Note:

For a pointer p , delete p deallocates the node to which p points; it does not deal- locatep . The pointer p still exists, but it contains an undefi ned value. You should not ref- erencep or any other pointer variable that still points to the deallocated node. To help you avoid this kind of error, you can assign nullptr to p after executing delete p . However, if variables other than p point to the deallocated node, the possibility of error still exists.

The method clear. The method clear cannot simply set ItemCount to zero, thereby ignoring all of the entries in the linked chain. Because the nodes in the chain were allocated dynamically, clear must deallocate them. Thus, we have the following defi nition for this method:

template< class ItemType>

void LinkedBag<ItemType>::clear()

Question 11

Given the previous defi nition of the method remove , which entry in a bag can be deleted in the least time? Why?

CHECK POINT

Question 12

Given the previous defi nition of the method remove , which entry in a bag takes the most time to delete? Why?

{

while (headPtr != nullptr) {

Node<ItemType>* nodeToDeletePtr = headPtr; headPtr = headPtr->getNext();

// Return node to the system

nodeToDeletePtr->setNext( nullptr); delete nodeToDeletePtr; } // end while // headPtr is nullptr nodeToDeletePtr = nullptr; itemCount = 0; } // end clear

The destructor. Each class has only one destructor. The destructor destroys an instance of the class,

that is, an object, when the object’s lifetime ends. Typically, the destructor is invoked implicitly at the end of the block in which the object was created.

Classes that use only statically allocated memory can depend on the compiler-generated destruc- tor, as was the case for the class ArrayBag in Chapter 3 . However, when a class uses dynamically allocated memory, as in the present link-based implementation, you need to write a destructor that deallocates this memory by using delete . The destructor for LinkedBag can simply call the method clear , as it uses delete to deallocate each node in the linked chain containing the bag’s entries. The destructor’s defi nition follows:

template< class ItemType>

LinkedBag<ItemType>::~LinkedBag() {

clear();

} // end destructor

A destructor’s name is a tilde (~) followed by the class name. A destructor cannot have argu- ments, has no return type—not even void —and cannot use return to return a value.

You must write a destructor if your class allocates memory dynamically

Question 13

Revise the destructor in the class LinkedBag so that it does not call clear , but it instead directly deletes each node of the underlying linked chain.

CHECK POINT

The copy constructor. The second constructor in LinkedBag is the copy constructor: LinkedBag(constLinkedBag<ItemType>& aBag);

The copy constructor makes a copy of an object. It is invoked implicitly when you either pass an object to a function by value, return an object from a valued function, or defi ne and initialize an object, as in

LinkedBag bag2(bag1); where bag1 exists already.

When copying an object involves only copying the values of its data members, the copy is called a

shallow copy . If a shallow copy is suffi cient, you can omit the copy constructor, in which case the com-

piler generates a copy constructor that performs a shallow copy. Such was the case in Chapter 3 for the classArrayBag , although we did not mention it. That array-based implementation of the ADT bag used a compiler-generated copy constructor to copy both the array of bag items and the number of items. Situations that

invoke the copy constructor A compiler- generated copy constructor performs a shallow copy

A Link-Based Implementation of the ADT Bag 147

For our new link-based implementation, a compiler-generated copy constructor would copy only the data members itemCount and headPtr . For example, Figure 4-8 a pictures a linked chain and the result of this shallow copy. Both the original pointer headPtr and its copy point to the same linked chain. In other words, the chain’s nodes are not copied. If you need to create a copy of the linked chain, you must write your own copy constructor. That is, a deep copy is needed, as

Figure 4-8 b illustrates.

Thus, the copy constructor appears as follows. template< class ItemType>

LinkedBag<ItemType>::LinkedBag( const LinkedBag<ItemType>& aBag) {

itemCount = aBag->itemCount;

Node<ItemType>* origChainPtr = aBag->headPtr if (origChainPtr ==nullptr)

headPtr = nullptr; // Original bag is empty; so is copy

else {

// Copy first node

headPtr = new Node<ItemType>();

headPtr->setItem(origChainPtr ->getItem()); // Copy remaining nodes

Node<ItemType>* newChainPtr = headPtr; // Last-node pointer

while (origPtr != nullptr) {

origChainPtr = origChainPtr ->getNext(); // Advance pointer // Get next item from original chain

ItemType nextItem = origChainPtr->getItem(); // Create a new node containing the next item

Node<ItemType>* newNodePtr = new Node<ItemType>(nextItem);

FIGURE 4-8 (a) A linked chain and its shallow copy; (b) a linked chain and its deep copy 4 4 itemCount headPtr Copy of itemCount Copy of headPtr (a) 4 itemCount headPtr (b) 4 Copy of

itemCount headPtrCopy of Copy of the linked chain

"ab" "cd" "ef" "gh" "ab" "ab" "cd" "cd" "ef" "ef" "gh" "gh"

// Link new node to end of new chain newChainPtr->setNext(newNodePtr);

// Advance pointer to new last node newChainPtr = newChainPtr->getNext(); } // end while

newChainPtr->setNext( nullptr); // Flag end of new chain } // end if

} // end copy constructor

As you can see, the copy constructor is an expensive operation. It requires traversing the original linked chain and duplicating each node visited.

4.3

Using Recursion in Link-Based Implementations

It is possible, and sometimes desirable, to process linked chains recursively. This section examines how to write the iterative methods given previously as recursive ones. Such recursive methods will require the chain’s head pointer as an argument. Therefore, they should not be public, because the head pointer is a private data member of the class and the client does not—and should not—have access to it. Otherwise, clients could access the linked nodes directly, thereby violating the ADT’s wall.

4.3.1

Recursive Defi nitions of Methods in

LinkedBag

As an introduction to this topic, we will revise two methods in the class LinkedBag to use recursion. These methods simply traverse a chain of linked nodes without making any changes to it.

The method toVector. We begin with the method toVector because it has a straightforward recur- sive implementation. This operation requires us to traverse the linked chain of nodes as we copy data from the nodes into a vector. Traversal of a linked chain is an operation that occurs in many situations. We have established that the method performing the recursion must be private and must have the head pointer as a parameter. Since the method will copy data into a vector as it traverses the linked chain, the vector must also be a parameter. We can declare this method in the private section of the classLinkedBag , as follows:

// F ills the vector bagContents with the data in the nodes of // the linked chain to which curPtr points.

void f illVector(vector<ItemType>& bagContents, Node<ItemType>* curPtr)const Given a defi nition of this method, we could implement toVector as follows:

template< class ItemType>

vector<ItemType> LinkedBag<ItemType>::toVector() const {

vector<ItemType> bagContents; f illVector(bagContents, headPtr);

return bagContents; } // end toVector

Thus, after creating a vector, toVector f ills it—by calling fillVector—with the data in the chain of linked nodes whose head pointer is headPtr . Finally, toVector returns the vector to the client.

To defi ne f illVector , we need to think recursively. If the chain is empty—that is, if curPtr is nullptr —we have nothing to do. This is the base case. In the recursive step, we fi rst add the data curPtr->getItem() to the vector and then recursively f ill the vector with the chain that begins at curPtr->getNext() . The method then has the following defi nition:

template< class ItemType>

Using Recursion in Link-Based Implementations 149 { if (curPtr !=nullptr) { bagContents.push_back(curPtr->getItem()); f illVector(bagContents, curPtr->getNext()); } // end if } // end toVector

Even though fillVector has a reference parameter, the method is safe because it is private. A similar situation occurs in Chapter 16 and is discussed further in a Note in Section 16.3.1.

The private method getPointerTo . The method getPointerTo locates a given entry within the linked chain. To do so, it traverses the linked chain, but unlike f illVector ’s traversal, this traversal stops if it locates the node that contains the given entry. The iterative version of getPointerTo has one parameter, which is the given entry. We could defi ne a private method to perform the recursion— as we did for toVector —that has two parameters; the given entry and a head pointer. Then get- PointerTo could call this new private method. However, since getPointerTo is itself a private meth- od, we can revise it to have the necessary two parameters. That is the approach we will take here. Of course, we also will have to revise the calls to the method that appear in the rest of the class.

We begin by replacing the declaration of getPointerTo in the header fi le for the class LinkedBag with the following statements:

// Locates a given entry within this bag.

// Returns either a pointer to the node containing a given entry or // the null pointer if the entry is not in the bag.

Node<ItemType>* getPointerTo( const ItemType& target,

Node<ItemType>* curPtr) const;

Before we forget to do so, let’s revise the calls to this method that occur in the methods remove and contains . The original calls are getPointerTo(anEntry) , but they now each need to be getPointerTo(anEntry, headPtr) .

The recursive defi nition of this method has two base cases. One case occurs when the chain is empty, causing the method to return nullptr . The other base case occurs when we locate the desired entry at curPtr->getItem() . In this case, the method returns curPtr . The recursive step searches the chain beginning at curPtr->getNext() . Thus, the method has the following defi nition:

template< class ItemType>

Node<ItemType>* getPointerTo( const ItemType& target, Node<ItemType>* curPtr) const {

Node<ItemType>* result =nullptr; if (curPtr !=nullptr)

{

if (target== curPtr->getItem()) result = curPtr;

else

result = getPointerTo(target, curPtr->getNext()); } // end if

return result; } // end getPointerTo

Other methods that can have a recursive defi nition are left for you as exercises.

Question 14

Revise the method clear so that it calls a recursive method to deallocate the nodes in the chain.