SVVRL IM NTU LinkBased Implementations The ADT Bag
SVVRL @ IM. NTU Link-Based Implementations The ADT Bag as an Example Yih-Kuen Tsay Dept. of Information Management National Taiwan University Based on [Carrano and Henry 2013] With Contributions by Ling-Chieh Kung 1 / 90
SVVRL @ IM. NTU Outline n n n Nodes Implementations of Member Functions Constructors and Destructor Recursion and Some Remarks Operator Overloading Exception Handling Yih-Kuen Tsay DS 2017: Link-Based Implementations 2 / 90
Link-Based Implementations n n Last time we implemented the ADT Bag with arrays. There are some drawbacks: q q n SVVRL @ IM. NTU Static arrays: The bag size is fixed. Dynamic arrays: Doubling the bag size requires a lot of copying and pasting. Here we will use pointers to do a link-based implementation. q q Bag items will be put on a “list. ” On the list, each item is “linked” to the next item. Yih-Kuen Tsay DS 2017: Link-Based Implementations 3 / 90
SVVRL @ IM. NTU Nodes n The type of an item is Item. Type. template<typename Item. Type> class Bag. Interface { public: virtual bool add(const Item. Type& new. Entry) = 0; // all other functions }; n n Source: Figure 4 -1 [Carrano and Henry 2015] For each item, we will associate a pointer pointing to the next item-pointer pair. The combination of the item and pointer is often called a node. q The pointer points to the next node, not the next item! Yih-Kuen Tsay DS 2017: Link-Based Implementations 4 / 90
SVVRL @ IM. NTU List of Nodes n In effect, a list of nodes will be created to store items. Source: Figure 4 -2 [Carrano and Henry 2015] n Each node is linked to the next node. q The pointer next points to the next node. Yih-Kuen Tsay DS 2017: Link-Based Implementations 5 / 90
The Class Node (1/4) template<class Item. Type> class Node { private: Item. Type item; Node<Item. Type>* next; public: Node(); Node(const Item. Type& an. Item, Node<Item. Type>* next. Node. Ptr); void set. Item(const Item. Type& an. Item); void set. Next(Node<Item. Type>* next. Node. Ptr); Item. Type get. Item() const ; Node<Item. Type>* get. Next() const ; }; Yih-Kuen Tsay DS 2017: Link-Based Implementations SVVRL @ IM. NTU Source: Figure 4 -1 [Carrano and Henry 2015] 6 / 90
The Class Node (2/4) n SVVRL @ IM. NTU Constructors of Node: template<class Item. Type> Node<Item. Type>: : Node() : next(nullptr) {} template<class Item. Type> Node<Item. Type>: : Node(const Item. Type& an. Item) : item(an. Item), next(nullptr) {} template<class Item. Type> Node<Item. Type>: : Node(const Item. Type& an. Item, Node<Item. Type>* next. Node. Ptr) : item(an. Item), next(next. Node. Ptr) {} Yih-Kuen Tsay DS 2017: Link-Based Implementations 7 / 90
The Class Node (3/4) n SVVRL @ IM. NTU Getters and setters of Node: template<class Item. Type> void Node<Item. Type>: : set. Item(const Item. Type& an. Item) { item = an. Item; } template<class Item. Type> void Node<Item. Type>: : set. Next(Node<Item. Type>* next. Node. Ptr) { next = next. Node. Ptr; } template<class Item. Type> Item. Type Node<Item. Type>: : get. Item() const { return item; } template<class Item. Type> Node<Item. Type>* Node<Item. Type>: : get. Next() const { return next; } Yih-Kuen Tsay DS 2017: Link-Based Implementations 8 / 90
The Class Node (4/4) n The class Node has getters and setters for all instance variables. q n It does no data hiding at all. Sometimes people implement Node as a structure. q q n SVVRL @ IM. NTU Member variables of a structure are by default public. Though they can be set to be private. Sometimes a better way is to set Linked. Bag a friend of Node. Yih-Kuen Tsay DS 2017: Link-Based Implementations 9 / 90
SVVRL @ IM. NTU Outline n n n Nodes Implementations of Member Functions Constructors and Destructor Recursion and Some Remarks Operator Overloading Exception Handling Yih-Kuen Tsay DS 2017: Link-Based Implementations 10 / 90
The Class Linked. Bag n n What should be contained in the class Linked. Bag? Let’s look at Array. Bag again: q q n item. Count is still needed. Capacity-related members are not needed. Where to store our nodes? q q n SVVRL @ IM. NTU The 1 st node links to the 2 nd. The ith node linkes to the (i + 1)th. class Array. Bag : public Bag. Interface { private: static const int DEFAULT_CAPACITY = 6; string items[DEFAULT_CAPACITY]; int item. Count; int max. Items; //. . . public: //. . . }; All we need is to store the 1 st node, i. e. , the head. Yih-Kuen Tsay DS 2017: Link-Based Implementations 11 / 90
The Head Pointer head. Ptr n An object of Linked. Bag will have an item counter item. Count. q n SVVRL @ IM. NTU Its type is int. An object of Linked. Bag will have a head pointer head. Ptr. Source: Figure 4 -5 [Carrano and Henry 2015] q q Its type is Node<item. Type>*. It is a pointer, not a node. head. Ptr is a static member; all other nodes are “dynamic members. ” Yih-Kuen Tsay DS 2017: Link-Based Implementations 12 / 90
The Class Linked. Bag n SVVRL @ IM. NTU The basic elements of Linked. Bag: template<class Item. Type> class Linked. Bag : public Bag. Interface<Item. Type> { private: Node<Item. Type>* head. Ptr; int item. Count; // something else public: // some constructors and destructor // functions defined in Bag. Interface, such as get. Current. Size(), // is. Empty(), add(), remove(), etc. }; n Let’s implement those functions first. Yih-Kuen Tsay DS 2017: Link-Based Implementations 13 / 90
SVVRL @ IM. NTU is. Empty() and get. Current. Size() n Some member functions are straightforward. template<class Item. Type> bool Linked. Bag<Item. Type>: : is. Empty() const { return item. Count == 0; } template<class Item. Type> int Linked. Bag<Item. Type>: : get. Current. Size() const { return item. Count; } Yih-Kuen Tsay DS 2017: Link-Based Implementations 14 / 90
SVVRL @ IM. NTU add() n We define add() to add an item at the beginning of the list. q q Why? What if we add it at the end of the list? template<class Item. Type> bool Linked. Bag<Item. Type>: : add(const Item. Type& new. Entry) { Node<Item. Type>* new. Node. Ptr = new Node<Item. Type>(); new. Node. Ptr->set. Item(new. Entry); new. Node. Ptr->set. Next(head. Ptr); head. Ptr = new. Node. Ptr; item. Count++; return true; } Source: Figure 4 -6 [Carrano and Henry 2015] Yih-Kuen Tsay DS 2017: Link-Based Implementations 15 / 90
SVVRL @ IM. NTU to. Vector() and List Traversal (1/2) n We still want to put all items into a vector. We need to traverse the list. q q q n With head. Ptr, we have the address of the first node. With head. Ptr->get. Next(), we have the address of the second node. How to proceed? We need a location pointer cur. Ptr to store a node address in each iteration. Let cur. Ptr point to the first node in the list while cur. Ptr is not a null pointer { Assign the data portion of the current node to the next element in a vector Set cur. Ptr to the next pointer portion of the current node } Yih-Kuen Tsay DS 2017: Link-Based Implementations 16 / 90
SVVRL @ IM. NTU to. Vector() and List Traversal (2/2) template<class Item. Type> vector<Item. Type> Linked. Bag<Item. Type>: : to. Vector() const { vector<Item. Type> bag. Contents; Node<Item. Type>* cur. Ptr = head. Ptr; while(cur. Ptr != nullptr) { bag. Contents. push_back(cur. Ptr->get. Item()); cur. Ptr = cur. Ptr->get. Next(); // the above line is equivalent to: // Node<Item. Type>* temp = cur. Ptr->get. Next(); // cur. Ptr = temp } return bag. Contents; } Yih-Kuen Tsay DS 2017: Link-Based Implementations 17 / 90
SVVRL @ IM. NTU cur. Ptr = cur. Ptr->get. Next() Source: Figure 4 -7 [Carrano and Henry 2015] Yih-Kuen Tsay DS 2017: Link-Based Implementations 18 / 90
remove() (1/2) n SVVRL @ IM. NTU To remove an item, we will remove the first copy (the one closest to the head). q q q First we locate that copy (if there is one). Then we rewrite the data portion of that node as that of the first node. Finally, we redirect head. Ptr to the second node and release the first node. Node<Item. Type>* node. To. Delete. Ptr = head. Ptr; head. Ptr = head. Ptr->get. Next(); // redirect delete node. To. Delete. Ptr; // release n We will create a private function get. Pointer. To() to do the locating task. q Recall that for Array. Bag, we have get. Index. Of(). Yih-Kuen Tsay DS 2017: Link-Based Implementations 19 / 90
remove() (2/2) SVVRL @ IM. NTU template<class Item. Type> bool Linked. Bag<Item. Type>: : remove(const Item. Type& an. Entry) { Node<Item. Type>* entry. Node. Ptr = get. Pointer. To(an. Entry); // step 1 bool can. Remove. Item = !is. Empty() && (entry. Node. Ptr != nullptr); if(can. Remove. Item) { entry. Node. Ptr->set. Item(head. Ptr->get. Item()); // step 2 Node<Item. Type>* node. To. Delete. Ptr = head. Ptr; // step 3 head. Ptr = head. Ptr->get. Next(); delete node. To. Delete. Ptr; node. To. Delete. Ptr = nullptr; item. Count--; } return can. Remove. Item; } Yih-Kuen Tsay DS 2017: Link-Based Implementations 20 / 90
get. Pointer. To() (1/2) n Given an item, we want to find the location of the first copy of this item. q q n SVVRL @ IM. NTU In Array. Bag, the location is represented by an array index. In Linked. Bag, the location is represented by an address. We want to define the following private function (why private? ): template<class Item. Type> Node<Item. Type>* Linked. Bag<Item. Type> : : get. Pointer. To(const Item. Type& an. Entry) const If the item does not exist, return nullptr. q Note that returning a pointer is nothing but returning an address. The idea: Traverse the list, stop when a node contains the given item, and then return the address. q n Yih-Kuen Tsay DS 2017: Link-Based Implementations 21 / 90
get. Pointer. To() (2/2) n n The implementation: If the bag is empty, cur. Ptr will be set to nullptr at initialization. If the item does not exist, cur. Ptr will be set to nullptr when we assign cur. Ptr->get. Next() to cur. Ptr for the last time. In either case, nullptr will be returned. Yih-Kuen Tsay SVVRL @ IM. NTU template<class Item. Type> Node<Item. Type>* Linked. Bag<Item. Type> : : get. Pointer. To(const Item. Type& an. Entry) const { bool found = false; Node<Item. Type>* cur. Ptr = head. Ptr; while(!found && (cur. Ptr != nullptr)) { if(an. Entry == cur. Ptr->get. Item()) found = true; else cur. Ptr = cur. Ptr->get. Next(); } return cur. Ptr; } DS 2017: Link-Based Implementations 22 / 90
SVVRL @ IM. NTU contains() • With get. Pointer. To(), contains() is easy. template<class Item. Type> bool Linked. Bag<Item. Type>: : contains(const Item. Type& an. Entry) const { return (get. Pointer. To(an. Entry) != nullptr); } Yih-Kuen Tsay DS 2017: Link-Based Implementations 23 / 90
SVVRL @ IM. NTU get. Frequency. Of() n A single traversal determines the frequency of a given item: template<class Item. Type> int Linked. Bag<Item. Type>: : get. Frequency. Of(const Item. Type& an. Entry) const { int frequency = 0; Node<Item. Type>* cur. Ptr = head. Ptr; while(cur. Ptr != nullptr) { if(an. Entry == cur. Ptr->get. Item()) frequency++; cur. Ptr = cur. Ptr->get. Next(); } return frequency; } Yih-Kuen Tsay DS 2017: Link-Based Implementations 24 / 90
SVVRL @ IM. NTU clear() n The function clear() releases all the dynamically allocated spaces: template<class Item. Type> void Linked. Bag<Item. Type>: : clear() { Node<Item. Type>* node. To. Delete. Ptr = head. Ptr; while(head. Ptr != nullptr) { head. Ptr = head. Ptr->get. Next(); delete node. To. Delete. Ptr; node. To. Delete. Ptr = head. Ptr; } item. Count = 0; } Yih-Kuen Tsay DS 2017: Link-Based Implementations 25 / 90
SVVRL @ IM. NTU Outline n n n Nodes Implementations of Member Functions Constructors and Destructor Recursion and Some Remarks Operator Overloading Exception Handling Yih-Kuen Tsay DS 2017: Link-Based Implementations 26 / 90
SVVRL @ IM. NTU Constructors and the Destructor n As always, the class Linked. Bag should have some constructors. q q n It should have a default constructor (and probably some other constructors with various parameters). It should have a copy constructor to do deep copy. It should have a destructor to deallocate dynamically allocated nodes. Yih-Kuen Tsay DS 2017: Link-Based Implementations 27 / 90
The Class Linked. Bag n SVVRL @ IM. NTU The basic elements of Linked. Bag: template<class Item. Type> class Linked. Bag : public Bag. Interface<Item. Type> { private: Node<Item. Type>* head. Ptr; int item. Count; //. . . public: Linked. Bag(); // default constructor Linked. Bag(const Linked. Bag<Item. Type>& a. Bag); // copy constructor virtual ~Linked. Bag(); // destructor //. . . }; Yih-Kuen Tsay DS 2017: Link-Based Implementations 28 / 90
SVVRL @ IM. NTU Default Constructor and Destructor n The default constructors and destructor of Linked. Bag: template<class Item. Type> Linked. Bag<Item. Type>: : Linked. Bag() : head. Ptr(nullptr), item. Count(0) { } template<class Item. Type> Linked. Bag<Item. Type>: : ~Linked. Bag() { clear(); } n The destructor is set virtual. Why? Yih-Kuen Tsay DS 2017: Link-Based Implementations 29 / 90
Virtual Destructor n SVVRL @ IM. NTU Suppose the destructor is not virtual: class Linked. Bag { //. . . ~Linked. Bag() { // some cleaning } }; n If we use polymorphism on the Derived. LB : public Linked. Bag-Derived. LB relationship: class { //. . . q When we delete b to “release the ~Derived. LB() { // more cleaning space, ” the behavior is undefined. } }inked. Bag* b = new Derived. LB(); q Linked. Bag’s destructor may be called. L // use b to do something delete b; // What will happen? n To ensure the invocation of q If Derived. LB has its own dynamic contents, it needs to have its own destructor to clean them up. Derived. LB’s destructor, set Linked. Bag’s destructor to be virtual. Yih-Kuen Tsay DS 2017: Link-Based Implementations 30 / 90
SVVRL @ IM. NTU Copy Constructor: Shallow Copy n If we do not define a copy constructor by ourselves, the default copy constructor of Linked. Bag will be like: template<class Item. Type> Linked. Bag<Item. Type>: : Linked. Bag (const Linked. Bag<Item. Type>& a. Bag) { item. Count = a. Bag->item. Count; head. Ptr = a. Bag->head. Ptr; } Source: Figure 4 -8 (a) [Carrano and Henry 2015] Yih-Kuen Tsay DS 2017: Link-Based Implementations 31 / 90
SVVRL @ IM. NTU Copy Constructor: Deep Copy (1/3) n What we want is deep copy: A new list of items should be dynamically created. Source: Figure 4 -8 (b) [Carrano and Henry 2015] n To do so, we need to define our own copy constructor for Linked. Bag. Yih-Kuen Tsay DS 2017: Link-Based Implementations 32 / 90
SVVRL @ IM. NTU Copy Constructor: Deep Copy (2/3) n The copy constructor of Linked. Bag: template<class Item. Type> Linked. Bag<Item. Type>: : Linked. Bag(const Linked. Bag<Item. Type>& a. Bag) { item. Count = a. Bag->item. Count; Node<Item. Type>* orig. Chain. Ptr = a. Bag->head. Ptr; if(orig. Chain. Ptr == nullptr) head. Ptr = nullptr; else { head. Ptr = new Node<Item. Type>(); head. Ptr->set. Item(orig. Chain. Ptr->get. Item()); // continued on the next page Yih-Kuen Tsay DS 2017: Link-Based Implementations 33 / 90
SVVRL @ IM. NTU Copy Constructor: Deep Copy (3/3) n The copy constructor of Linked. Bag: // continued from the previous page Node<Item. Type>* new. Chain. Ptr = head. Ptr; while(orig. Chain. Ptr != nullptr) { orig. Chain. Ptr = orig. Chain. Ptr->get. Next(); Item. Type next. Item = orig. Chain. Ptr->get. Item(); Node<Item. Type>* new. Node. Ptr = new Node<Item. Type>(next. Item); new. Chain. Ptr->set. Next(new. Node. Ptr); new. Chain. Ptr = new. Chain. Ptr->get. Next(); } new. Chain. Ptr->set. Next(nullptr); } } Yih-Kuen Tsay DS 2017: Link-Based Implementations 34 / 90
SVVRL @ IM. NTU Outline n n n Nodes Implementations of Member Functions Constructors and Destructor Recursion and Some Remarks Operator Overloading Exception Handling Yih-Kuen Tsay DS 2017: Link-Based Implementations 35 / 90
SVVRL @ IM. NTU Recursion n Recursion can again be applied to implement member functions. q n E. g. , when the function does a list traversal. Let’s use get. Pointer. To() and to. Vector() to illustrate the idea. q Other functions may also be implemented with recursion. Yih-Kuen Tsay DS 2017: Link-Based Implementations 36 / 90
SVVRL @ IM. NTU Recursive get. Pointer. To() n get. Pointer. To( ) may be redefined as a recursive function. q A new argument is added. Yih-Kuen Tsay template<class Item. Type> Node<Item. Type>* Linked. Bag<Item. Type> : : get. Pointer. To(const Item. Type& target, Node<Item. Type>* cur. Ptr) const { // pass head. Ptr as the 2 nd argument // to traverse the whole bag Node<Item. Type>* result = nullptr; if(cur. Ptr != nullptr) { if(target == cur. Ptr->get. Item()) result = cur. Ptr; else result = get. Pointer. To(target, cur. Ptr->get. Next()); } return result; } DS 2017: Link-Based Implementations 37 / 90
Recursive to. Vector() (1/2) n SVVRL @ IM. NTU Similarly, to. Vector() can be re-implemented: q We add a recursive member function fill. Vector (). template<class Item. Type> vector<Item. Type> Linked. Bag<Item. Type> : : to. Vector() const { vector<Item. Type> bag. Contents; fill. Vector(bag. Contents, head. Ptr); return bag. Contents; } Yih-Kuen Tsay DS 2017: Link-Based Implementations 38 / 90
Recursive to. Vector() (2/2) SVVRL @ IM. NTU template<class Item. Type> void Linked. Bag<Item. Type> : : fill. Vector(vector<Item. Type>& bag. Contents, Node<Item. Type>* cur. Ptr) const { if (cur. Ptr != nullptr) { bag. Contents. push_back(cur. Ptr->get. Item()); fill. Vector(bag. Contents, cur. Ptr->get. Next()); } } n n n Why do we define fill. Vector()? How about get. Pointer. To()? Should fill. Vector() be private, public, or protected? Does recursion improve the efficiency of the program? Yih-Kuen Tsay DS 2017: Link-Based Implementations 39 / 90
SVVRL @ IM. NTU Polymorphism Regarding Bag (1/2) n n We have defined a pure virtual base class Bag. Interface. We have implemented two concrete derived classes Array. Bag and Linked. Bag. Interface Array. Bag n Linked. Bag Thanks to polymorphism, we may now dynamically determine which implementation to use at run time. Yih-Kuen Tsay DS 2017: Link-Based Implementations 40 / 90
SVVRL @ IM. NTU Polymorphism Regarding Bag (2/2) int main() { Bag. Interface<string>* bag. Ptr = nullptr; char user. Choice = 0; cin >> user. Choice; if(user. Choice == 'A') bag. Ptr = new Array. Bag<string>(); else bag. Ptr = new Linked. Bag<string>(); bag. Tester(bag. Ptr); delete bag. Ptr; bag. Ptr = nullptr; return 0; } Yih-Kuen Tsay void bag. Tester (Bag. Interface<string>* bag. Ptr) { string items[] = {"aa", "bb", "cc"}; for(int i = 0; i < 3; i++) bag. Ptr->add(items[i]); cout << bag. Ptr->is. Empty(); cout << bag. Ptr->get. Current. Size(); bag. Ptr->remove("two"); cout << bag. Ptr->is. Empty(); cout << bag. Ptr->get. Current. Size(); } DS 2017: Link-Based Implementations 41 / 90
SVVRL @ IM. NTU Benefits of Link-Based Implementations Array-based Implementations Link-based Implementations A static array has a size limit Does not have a size limit A dynamic array requires a lot of time to increase the size Does not have a size limit May need to predict the maximum number of items No need to predict this May waste space Use space when you need it Yih-Kuen Tsay DS 2017: Link-Based Implementations 42 / 90
SVVRL @ IM. NTU Benefits of Array-Based Implementations Array-based Implementations Link-based Implementations Require less space to store an item Require more space to store an item One may directly access any item A traversal is needed to access an item Accessing each item requires a constant time Accessing the ith items takes more time for larger i Easier to write the program Harder to write the program Yih-Kuen Tsay DS 2017: Link-Based Implementations 43 / 90
SVVRL @ IM. NTU Comparisons n Use arrays if: q q q n The maximum number of items is fixed and known. The maximum number of items may vary but will be small. Accessing efficiency is critical for you. Use links if: q q The number of items varies a lot. The data portion occupies huge space. Yih-Kuen Tsay DS 2017: Link-Based Implementations 44 / 90
SVVRL @ IM. NTU Outline n n n Nodes Implementations of Member Functions Constructors and Destructor Recursion and Some Remarks Operator Overloading Exception Handling Yih-Kuen Tsay DS 2017: Link-Based Implementations 45 / 90
Recall Our My. Vector Class class My. Vector { private: int n; double* m; public: My. Vector() : n(0), m(NULL) { }; My. Vector(int n, double m[]); My. Vector(const My. Vector& v); ~My. Vector() { delete [] m; } void print() const; }; Yih-Kuen Tsay SVVRL @ IM. NTU My. Vector: : My. Vector(int n, double m[]) { this->n = n; this->m = new double[n]; for(int i = 0; i < n; i++) this->m[i] = m[i]; } My. Vector: : My. Vector(const My. Vector& v) { this->n = v. n; this->m = new double[n]; for(int i = 0; i < n; i++) this->m[i] = v. m[i]; } void My. Vector: : print() const { cout << "("; for(int i = 0; i < n - 1; i++) cout << m[i] << ", "; cout << m[n-1] << ")n"; } DS 2017: Link-Based Implementations 46 / 90
SVVRL @ IM. NTU Comparing My. Vector Objects n n When we have many vectors, we may need to compare them. For vectors u and v: q q q n u = v if their dimensions are equal and ui = vi for all i. u < v if their dimensions are equal and ui < vi for all i. u ≤ v if their dimensions are equal and ui ≤ vi for all i. How to add member functions that do comparisons? q Naturally, they should be instance rather than static functions. Yih-Kuen Tsay DS 2017: Link-Based Implementations 47 / 90
SVVRL @ IM. NTU Member Function is. Equal() class My. Vector { private: int n; double* m; public: My. Vector() : n(0), m(NULL) { }; My. Vector(int n, double m[]); My. Vector(const My. Vector& v); ~My. Vector() { delete [] m; } void print() const; bool is. Equal(const My. Vector& v) const; }; Yih-Kuen Tsay bool My. Vector: : is. Equal(const My. Vector& v) const { if(this->n != v. n) return false; else { for(int i = 0; i < n; i++) { if(this->m[i] != v. m[i]) return false; } } return true; } DS 2017: Link-Based Implementations 48 / 90
is. Equal() Is Fine, But … n Adding the instance function is. Equal() is fine. q q n q The compiler does not know what to do to this statement. We need to define == for My. Vector just as we define member functions. In fact, == has been overloaded for different data types. q q n But it is not intuitive. If we could write if(a 1 == a 2), it would be great! Of course we cannot (without further definitions). q n SVVRL @ IM. NTU We may compare two ints, two doubles, one int and one double, etc. We will now define how == should compare two My. Vectors. This is called operator overloading. Yih-Kuen Tsay DS 2017: Link-Based Implementations 49 / 90
Operator Overloading (1/2) n Most operators (if not all) have been overloaded in the C++ standard. q q n SVVRL @ IM. NTU E. g. , the division operator / has been overloaded. Divisions between integers is just different from divisions fractional values! Overloading operators for self-defined classes are not required. q q Each overloaded operator can be replaced by an instance function. However, it often makes programs clearer and the class easier to use. Yih-Kuen Tsay DS 2017: Link-Based Implementations 50 / 90
Operator Overloading (2/2) n SVVRL @ IM. NTU Some restrictions: q q q Not all operators can be overloaded (see your textbook). The number of operands for an operator cannot be modified. New operators cannot be created. Yih-Kuen Tsay DS 2017: Link-Based Implementations 51 / 90
Overloading an Operator n An operator is overloaded by “implementing a special instance function”. q n SVVRL @ IM. NTU It cannot be implemented as a static function. Let op be the operator to be overloaded, the “special instance function” is always named operatorop q n The keyword operator is used for overloading operators. Let’s overload == for My. Vector. Yih-Kuen Tsay DS 2017: Link-Based Implementations 52 / 90
Overloading == (1/2) n SVVRL @ IM. NTU Recall that we defined is. Equal(): class My. Vector { private: int n; double* m; public: My. Vector() : n(0), m(NULL) { }; My. Vector(int n, double m[]); My. Vector(const My. Vector& v); ~My. Vector() { delete [] m; } void print() const; bool is. Equal(const My. Vector& v) const; }; Yih-Kuen Tsay bool My. Vector: : is. Equal(const My. Vector& v) const { if(this->n != v. n) return false; else { for(int i = 0; i < n; i++) { if(this->m[i] != v. m[i]) return false; } } return true; } DS 2017: Link-Based Implementations 53 / 90
Overloading == (2/2) n SVVRL @ IM. NTU To overload ==, simply do this: class My. Vector { private: int n; double* m; public: My. Vector() : n(0), m(NULL) { }; My. Vector(int n, double m[]); My. Vector(const My. Vector& v); ~My. Vector() { delete [] m; } void print() const; bool operator==(const My. Vector& v) const; }; Yih-Kuen Tsay bool My. Vector: : operator==(const My. Vector& v) const { if(this->n != v. n) return false; else { for(int i = 0; i < n; i++) { if(this->m[i] != v. m[i]) return false; } } return true; } DS 2017: Link-Based Implementations 54 / 90
SVVRL @ IM. NTU Invoking Overloaded Operators (1/2) n n We are indeed implementing instance functions with special names. Regarding invoking these instance functions, int main() // without operator overloading { double d 1[5] = {1, 2, 3, 4, 5}; const My. Vector a 1(5, d 1); int main() // with operator overloading { double d 1[5] = {1, 2, 3, 4, 5}; const My. Vector a 1(5, d 1); double d 2[4] = {1, 2, 3, 4}; const My. Vector a 2(4, d 2); const My. Vector a 3(a 1); a 1. is. Equal(a 2) ? cout << "Yn" : cout << "Nn"; a 1. is. Equal(a 3) ? cout << "Yn" : cout << "Nn"; a 1 == a 2 ? cout << "Yn" : cout << "Nn"; a 1 == a 3 ? cout << "Yn" : cout << "Nn"; return 0; } } Yih-Kuen Tsay DS 2017: Link-Based Implementations 55 / 90
SVVRL @ IM. NTU Invoking Overloaded Operators (2/2) n Interestingly, we may also do: int main() // with operator overloading { double d 1[5] = {1, 2, 3, 4, 5}; const My. Vector a 1(5, d 1); double d 2[4] = {1, 2, 3, 4}; const My. Vector a 2(4, d 2); const My. Vector a 3(a 1); a 1. operator==(a 2) ? cout << "Yn" : cout << "Nn"; a 1. operator==(a 3) ? cout << "Yn" : cout << "Nn"; return 0; } n Other comparison operations (<, !=, etc. ) can all be overloaded similarly. Yih-Kuen Tsay DS 2017: Link-Based Implementations 56 / 90
SVVRL @ IM. NTU Parameters for Overloaded Operators n The number of parameters is restricted for overloaded operators. q q q n The types of parameters are not restricted. The return type is not restricted. What is done is not restricted. Always avoid unintuitive implementations! Yih-Kuen Tsay class My. Vector { //. . . bool operator==(const My. Vector& v) const; bool operator==(My. Vector v) const; void operator==(int i) const { cout << ". . . n"; } // no error but never do this! bool operator==(int i, int j); // error }; DS 2017: Link-Based Implementations 57 / 90
SVVRL @ IM. NTU Overloading the Indexing Operator n Another natural operation that is common for vectors is indexing. q n Given vector v, we want to know/modify the element vi. For C++ arrays, we use the indexing operator []. class My. Vector { //. . . double operator[](int i) const; }; Yih-Kuen Tsay double My. Vector: : operator[](int i) const { if (i < 0 || i >= n) exit(1); // terminate the program! // require <cstdlib> return m[i]; } DS 2017: Link-Based Implementations 58 / 90
More Are Needed for [] n SVVRL @ IM. NTU Compiling the program with the main function below results in an error! int main() { double d 1[5] = {1, 2, 3, 4, 5}; My. Vector a 1(5, d 1); // non-const cout << a 1[3] << endl; // good a 1[1] = 4; // error! return 0; } n Error: a 1[1] is just a literal, not a variable. q q A literal cannot be put on the LHS of an assignment operation! Just like 3 = 5 resulting in an error. Yih-Kuen Tsay DS 2017: Link-Based Implementations 59 / 90
Another Overloaded [] n Let’s overload [] into another version: class My. Vector { //. . . double operator[](int i) const; double& operator[](int i); }; n SVVRL @ IM. NTU double My. Vector: : operator[](int i) const { if(i < 0 || i >= n) exit(1); return m[i]; } double& My. Vector: : operator[](int i) { if(i < 0 || i >= n) // same exit(1); // implementation! return m[i]; } The second implementation returns a reference of a member variable. q Modifying that reference modifies the variable. Yih-Kuen Tsay DS 2017: Link-Based Implementations 60 / 90
SVVRL @ IM. NTU Two Different [] n Now the program runs successfully! int main() { double d 1[5] = {1, 2, 3, 4, 5}; My. Vector a 1(5, d 1); cout << a 1[1] << endl; // 2 a 1[1] = 4; // good cout << a 1[1] << endl; // 4 return 0; } n Which [] is invoked? q The const after the function prototype is the key. Yih-Kuen Tsay class My. Vector { //. . . double operator[](int i) const; double& operator[](int i); }; double My. Vector: : operator[](int i) const { if(i < 0 || i >= n) exit(1); return m[i]; } double& My. Vector: : operator[](int i) { if(i < 0 || i >= n) exit(1); return m[i]; } DS 2017: Link-Based Implementations 61 / 90
Default Assignment Operator n n When we do an assignment, what do we expect? In fact, the assignment operator has been overloaded! q q q n The compiler adds a default assignment operator to each class. It simply copies each instance variable to its corresponding one. Just like the default copy constructor. SVVRL @ IM. NTU int main() { double d 1[5] = double d 2[4] = My. Vector a 1(5, My. Vector a 2(4, {1, 2, 3, 4, 5}; {1, 2, 3, 4}; d 1); d 2); a 2. print(); a 2 = a 1; // dangerous! a 2. print(); return 0; } What may be wrong when we run the main function with the default assignment operator? Yih-Kuen Tsay DS 2017: Link-Based Implementations 62 / 90
SVVRL @ IM. NTU Overloading the Assignment Operator (1/3) n n The assignment operator may need to be manually overloaded. Our first implementation: class My. Vector { //. . . void operator=(const My. Vector& v); }; n How about a 1 = a 1? Yih-Kuen Tsay void My. Vector: : operator=(const My. Vector& v) { if(this->n != v. n) { delete [] this->m; this->n = v. n; this->m = new double[this->n]; } for(int i = 0; i < n; i++) this->m[i] = v. m[i]; } DS 2017: Link-Based Implementations 63 / 90
SVVRL @ IM. NTU Overloading the Assignment Operator (2/3) n Our second implementation: class My. Vector { //. . . void operator=(const My. Vector& v); }; n How about a 1 = a 2 = a 3? Yih-Kuen Tsay void My. Vector: : operator=(const My. Vector& v) { if(this != &v) { if(this->n != v. n) { delete [] this->m; this->n = v. n; this->m = new double[this->n]; } for(int i = 0; i < n; i++) this->m[i] = v. m[i]; } } DS 2017: Link-Based Implementations 64 / 90
SVVRL @ IM. NTU Overloading the Assignment Operator (3/3) n Our third implementation: class My. Vector { //. . . My. Vector& operator=(const My. Vector& v); }; n To avoid (a 1 = a 2) = a 3, we may return const My. Vector&. Yih-Kuen Tsay My. Vector& My. Vector: : operator=(const My. Vector& v) { if(this != &v) { if(this->n != v. n) { delete [] this->m; this->n = v. n; this->m = new double[this->n]; } for(int i = 0; i < n; i++) this->m[i] = v. m[i]; } return *this; } DS 2017: Link-Based Implementations 65 / 90
SVVRL @ IM. NTU Preventing Assignments and Copying n In some cases, we disallow assignments between objects of a certain class. q n In some cases, we disallow creating an object by copying another object. q n To do so, overload the assignment operator as private or protected. To do so, implement the copy constructor as private or protected. The copy constructor, assignment operator, and destructor form a group. q Either you need none of them, or you need all of them. Yih-Kuen Tsay DS 2017: Link-Based Implementations 66 / 90
Self-Assignment Operators n For vectors, one often wants to do arithmetic and assignments. q n n Given vectors u and v of the same dimension, the operation u += v makes ui become ui + vi for all i. Let’s overload +=: q n SVVRL @ IM. NTU Why returning const My. Vector&? Returning My. Vector& allows (a 1 += a 3)[i]. Returning const My. Vector& disallows (a 1 += a 3) = a 2. Yih-Kuen Tsay class My. Vector { //. . . const My. Vector& operator+=(const My. Vector& v); }; const My. Vector& My. Vector: : operator+=(const My. Vector& v) { if(this->n == v. n) { for(int i = 0; i < n; i++) this->m[i] += v. m[i]; } return *this; } DS 2017: Link-Based Implementations 67 / 90
Arithmetic Operators n n SVVRL @ IM. NTU Overloading an arithmetic operator is not hard. Consider the addition operator + as an example. q q Take const My. Vector& as a parameter. Add each pair of elements one by one. Do not modify the parameter object. Return const My. Vector to allow a 1 + a 2 + a 3 but disallow (a 1 + a 2) = a 3. Yih-Kuen Tsay DS 2017: Link-Based Implementations 68 / 90
SVVRL @ IM. NTU Overloading the Addition Operator (1/2) n Let’s try to do it. class My. Vector { //. . . const My. Vector operator+(const My. Vector& v); }; const My. Vector: : operator+(const My. Vector& v) { My. Vector sum(*this); // creating a local variable sum += v; // using the overloaded += return sum; } n Why not returning const My. Vector&? q Hint: What will sum have after the function call is finished? Yih-Kuen Tsay DS 2017: Link-Based Implementations 69 / 90
SVVRL @ IM. NTU Overloading the Addition Operator (2/2) n We may overload it for another parameter type: int main() { double d 1[5] = {1, 2, 3, 4, 5}; My. Vector a 1(5, d 1); My. Vector a 2(5, d 1); a 1 = a 1 + a 2; // good a 1. print(); a 1 = a 2 + 4. 2; // good a 1. print(); return 0; } Yih-Kuen Tsay class My. Vector { //. . . const My. Vector operator+(const My. Vector& v); const My. Vector operator+(double d); }; const My. Vector: : operator+(const My. Vector& v) { My. Vector sum(*this); // creating a local variable sum += v; // using the overloaded += return sum; } const My. Vector: : operator+(double d) { My. Vector sum(*this); for(int i = 0; i < n; i++) sum[i] += d; return sum; } DS 2017: Link-Based Implementations 70 / 90
SVVRL @ IM. NTU Instance Function vs. Global Function n One last issue: addition is commutative, but the program below does not run! int main() { double d 1[5] = {1, 2, 3, 4, 5}; My. Vector a 1(5, d 1); a 1 = 4. 2 + a 1; // bad! a 1. print(); return 0; } n n We cannot let a double variable invoke our “instance function operator+”. We should overload + as a global function. Yih-Kuen Tsay DS 2017: Link-Based Implementations 71 / 90
SVVRL @ IM. NTU A Global-Function Version (1/2) n To overload + as global functions, we need to handle three combinations: const My. Vector operator+(const My. Vector& v, double d) { My. Vector sum(v); for(int i = 0; i < v. n; i++) // What do we need for this? sum[i] += d; // pairwise addition return sum; } const My. Vector operator+(double d, const My. Vector& v) { return v + d; // using the previous definition } const My. Vector operator+(const My. Vector& v 1, const My. Vector& v 2) { My. Vector sum(v 1); return sum += v 2; // using the overloaded += } Yih-Kuen Tsay DS 2017: Link-Based Implementations 72 / 90
SVVRL @ IM. NTU A Global-Function Version (2/2) n Now all kinds of addition may be performed: int main() { double d 1[5] = {1, 2, 3, 4, 5}; My. Vector a 1(5, d 1); My. Vector a 3(a 1); a 3 = 3 + a 1 + 4 + a 3; a 3. print(); return 0; } n Each operator needs a separate consideration. Yih-Kuen Tsay DS 2017: Link-Based Implementations 73 / 90
SVVRL @ IM. NTU Outline n n n Nodes Implementations of Member Functions Constructors and Destructor Recursion and Some Remarks Operator Overloading Exception Handling Yih-Kuen Tsay DS 2017: Link-Based Implementations 74 / 90
SVVRL @ IM. NTU Exceptions (1/3) n Exceptions are those things that are not expected to happen. q n When one writes a program, that typically refers to logic or run-time errors. Consider the following example: #include <iostream> using namespace std; void f(int a[], int n) { int i = 0; cin >> i; a[i] = 1; // logic error? } Yih-Kuen Tsay int main() { int a[5] = {0}; f(a, 5); for(int i = 0; i < 5; i++) cout << a[i] << " "; return 0; } DS 2017: Link-Based Implementations 75 / 90
SVVRL @ IM. NTU Exceptions (2/3) n Some checks can be helpful: #include <iostream> using namespace std; bool f(int a[], int n) { int i = 0; cin >> i; if (i < 0 || i > n) return false; a[i] = 1; return true; } n int main() { int a[5] = {0}; f(a, 5); for (int i = 0; i < 5; i++) cout << a[i] << " "; return 0; } The client can check the value returned by f() to make appropriate responses. Yih-Kuen Tsay DS 2017: Link-Based Implementations 76 / 90
Exceptions (3/3) n Some checks may not be enough. q q q n SVVRL @ IM. NTU Even if the function has multiple reasons to return false, the client will not see the reason. We cannot send messages to the client about the error (do not print out an error message on the screen!). We cannot enforce the client to respond to the returned value. C++ (and many other modern languages) offers exception handling. q q q A mechanism for handling logic or run-time error. A function can report the occurrence of an error by throwing an exception. One catches an exception and then respond accordingly. Yih-Kuen Tsay DS 2017: Link-Based Implementations 77 / 90
Try and Catch (1/3) n SVVRL @ IM. NTU In C++, we use a try block and catch blocks. try { // statements that may throw exceptions } catch(Exception. Class identifier) // this kind? { // responses } catch(Another. Exception. Class identifier) // that kind? { // other responses } n n A try block must be followed by at least one catch block. One should include only statements that may throw exceptions in a try block. Yih-Kuen Tsay DS 2017: Link-Based Implementations 78 / 90
Try and Catch (2/3) n When a statement (function or method) in a try block causes an exception: q q q n SVVRL @ IM. NTU Control ignores the rest statements in the try block. Control passes to the catch block corresponding to the exception (if any). After the catch block executes, control passes to statements after this try-catch block. If there is no applicable catch block for an exception, abnormal program termination usually occurs. Yih-Kuen Tsay DS 2017: Link-Based Implementations 79 / 90
Try and Catch (3/3) n SVVRL @ IM. NTU If an exception occurs in the middle of a try block, the destructors of all (static) objects local to that block are called. q q This is to ensure that all resources allocated in that block are released. It is suggested not to dynamically allocate anything inside a try block. Yih-Kuen Tsay DS 2017: Link-Based Implementations 80 / 90
SVVRL @ IM. NTU Example: string: : replace() (1/3) n The replace() function of C++ strings is easy to use. #include <iostream> #include <string> #include <stdexcept> using namespace std; void g(string& s, int i) { s. replace(i, 1, ". "); } n int main() { string s = "12345"; int i = 0; cin >> i; g(s, i); cout << s << endl; return 0; } It is defined to be able to throw an out_of_range exception. q q out_of_range is a class defined in <stdexcept>. If we do not respond to the exception, the program terminates abnormally. Yih-Kuen Tsay DS 2017: Link-Based Implementations 81 / 90
SVVRL @ IM. NTU Example: string: : replace() (2/3) n Let’s try and catch! #include <iostream> #include <string> #include <stdexcept> using namespace std; void g(string& s, int i) { try { s. replace(i, 1, ". "); } catch(out_of_range e) { cout << ". . . n"; } } Yih-Kuen Tsay int main() { string s = "12345"; int i = 0; cin >> i; g(s, i); cout << s << endl; return 0; } DS 2017: Link-Based Implementations 82 / 90
SVVRL @ IM. NTU Example: string: : replace() (3/3) n We may also try and catch in the client: #include <iostream> #include <string> #include <stdexcept> using namespace std; void g(string& s, int i) { s. replace(i, 1, ". "); } n int main() { string s = "12345"; int i = 0; cin >> I; try { g(s, i); } catch(out_of_range e) { cout << ". . . n"; } cout << s << endl; return 0; } A thrown exception will be passed to callers until one catches it. q If no one catches it, the program terminates abnormally. Yih-Kuen Tsay DS 2017: Link-Based Implementations 83 / 90
Standard Exception Classes n SVVRL @ IM. NTU In the C++ standard library, we have the following standard exception classes: q q exception logic_error domain_error try { invalid_argument g(s, i); } length_error // this also works out_of_range catch(logic_error e) { cout << ". . . n"; runtime_error } range_error overflow_error Include <stdexcept> to use them. underflow_error Inheritance and polymorphism! Yih-Kuen Tsay DS 2017: Link-Based Implementations 84 / 90
Throwing an Exception (1/2) n We may also throw an exception by ourselves. #include <iostream> #include <stdexcept> using namespace std; void f(int a[], int n) throw(logic_error) { int i = 0; cin >> i; if(i < 0 || i > n) throw logic_error(". . . "); a[i] = 1; } n SVVRL @ IM. NTU int main() { int a[5] = {0}; f(a, 5); for(int i = 0; i < 5; i++) cout << a[i] << " "; return 0; } This forces the client to catch the exception! Yih-Kuen Tsay DS 2017: Link-Based Implementations 85 / 90
Throwing an Exception (2/2) n Let the client catch the exception: #include <iostream> #include <stdexcept> using namespace std; void f(int a[], int n) throw(logic_error) { int i = 0; cin >> i; if(i < 0 || i > n) throw logic_error(". . . "); a[i] = 1; } n SVVRL @ IM. NTU int main() { int a[5] = {0}; try { f(a, 5); } catch(logic_error e) { cout << e. what(); } for(int i = 0; i < 5; i++) cout << a[i] << " "; return 0; } what() returns the message generated when throwing an exception. Yih-Kuen Tsay DS 2017: Link-Based Implementations 86 / 90
SVVRL @ IM. NTU Modifying the Function Header n Functions that throw an exception have a throw clause at the end of their headers. q q n This restricts the exceptions that a function can throw. Omitting a throw clause allows a function to throw any exception. To allow multiple types of exceptions, write like: #include <iostream> #include <stdexcept> using namespace std; void f(int a[], int n) throw(logic_error) { int i = 0; cin >> i; if(i < 0 || i > n) throw logic_error(". . . "); a[i] = 1; } void f(int a[], int n) throw(type 1, type 2) n The documentation of a function (or method) should indicate any exception it might throw. Yih-Kuen Tsay DS 2017: Link-Based Implementations 87 / 90
SVVRL @ IM. NTU Defining Your Own Exception Classes (1/2) n n C++ Standard Library supplies a number of exception classes. You may also want to define your own exception class. q q This helps your program communicate better to your clients. Your own exception classes should inherit from standard exception classes for a standardized exception working interface. Yih-Kuen Tsay #include <stdexcept> using namespace std; class My. Exception : public exception { public: My. Exception(const string& msg = "") : exception(msg. c_str()) {} }; DS 2017: Link-Based Implementations 88 / 90
SVVRL @ IM. NTU Defining Your Own Exception Classes (2/2) n Let’s use our own exception class: #include <iostream> #include <stdexcept> using namespace std; void f(int a[], int n) throw(My. Exception) { int i = 0; cin >> i; if(i < 0 || i > n) throw My. Exception(". . . "); a[i] = 1; } Yih-Kuen Tsay int main() { int a[5] = {0}; try { f(a, 5); } catch(My. Exception e) { cout << e. what(); } for(int i = 0; i < 5; i++) cout << a[i] << " "; return 0; } DS 2017: Link-Based Implementations 89 / 90
Applying These Techniques n Operator overloading may be applied to Array. Bag and Linked. Bag: q q q n SVVRL @ IM. NTU One may “add” two bags to create one bag. One may “subtract” one bag from another bag. One may “compare” two bags. Exception handling may be applied to Array. Bag and Linked. Bag: q q When the bag size limit is reached, throw an exception. When a non-existing item is asked to be removed, throw an exception. Yih-Kuen Tsay DS 2017: Link-Based Implementations 90 / 90
- Slides: 90