briand@infmx.UUCP (brian donat) (05/26/90)
// // 1 of 4 // // I thought that some of you might be curious as to what kind of // critcisms my linked list class recieved and further since the // criticism I did recieve was somewhat instructive, and further // still, since nobody flamed me for posting a heap of code, I've // decided to post version 2.00, with explanations attached // regarding improvements gleened from the critiques I recieved. // // First, I'd decided on-my-own, to break the original code out into // separate '.c' and '.h' files and achieved this just before the // first helpful advice arrived. // // Still further, some of that advice recommended creating STACK and // QUEUE classes, instead of writing functions which used class list // as a stack or a queue. // // So, the following files are now extant: // // vlist.h a virtural base class for list // vlist.c members for class VLIST // list.h class LIST derived from VLIST // stack.h class STACK derived from VLIST // stack.c members for class STACK // queue.h class QUEUE derived from VLIST // queue.c members for class QUEUE // testmain.c main() for testing purposes as before. // // Discussion continued below. ---------------------------------Cut HERE--------------------------------- /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // \\ // vlist.h Version 2.00 \\ // A linked-list object: May 25, 1990, Brian L. Donat \\ // \\ /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // // The following classes compose a very general doubly linked list, // which may be added to, inserted into, deleted from, and read from. // This size of list is maintained, allowing external algorithms to // perform finite operations on the list data. // /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ typedef int ELEMENT; ////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\ // NODE is a base class which allows for the // creation of a doubly linked list. class NODE { friend class VLIST; NODE *next, *previous; ELEMENT data; }; ////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\ // VLIST is the actual doubly linked list class // // [ NOTE: original comments deleted for brevity.] class VLIST { int listsize; NODE *head, *tail; NODE *cnode; // current NODE or copy NODE. public: VLIST(); ~VLIST(); virtual void headend(); virtual void tailend(); virtual void insert(ELEMENT E); virtual void add(ELEMENT E) { cnode = tail; insert(E); } virtual void remove(); virtual void step(); virtual void backstep(); virtual int find(ELEMENT E); virtual ELEMENT getval() const; virtual int getsize() const { return listsize; } }; ---------------------------------Cut HERE--------------------------------- // A lot more concise, eh? // One of the things I got hit for here was my style of 'commenting'. // /////\\\\\ <- One person believes that some compilers // might view the last '\' as an escape which // doom the next line to being commented out. // I tested this and can't prove it as a concern with Cfront 2.0 // Anybody got any insight on this? // Next, the whole class has been redefined as a virtual base class // so that others may use it flexibly via inheritance. // the previous definition had the following line: // // void add(ELEMENT E) { cnode = tail; list::insert(E); } // The 'list::' qualifier would of course not helped people // were they to copy this class via parameterization. // Beyond this, there were some suggestions about 'inlines'. // Is a line such as // foo() { cnode = (head->next) ? head->next : nptr; } // really too big to be an inline? // // What's a good rule of thumb for predicting the fulcrum // between memory usage for a function's stack // and that used by actual code inserted inline? // Does a programmer have to calculate it byte by byte? // // And there's another question, notice the member add(), -- // it has a function call within its declaration, is // the overhead for that call expanded inline too? // -- at what cost? // // Further, isn't the use of 'inline' a matter of determined choice // based on trade-offs in memory usage vs. run-time? // // -- infmx!briand@pyramid
briand@infmx.UUCP (brian donat) (05/26/90)
// // 2 of 4 // // Ready for bite number 2? It deals with splitting up things // over files. // // Believe me! This was fun to do the first time through. There // was a point in time, when I really didn't think I knew at // all what I was doing. // // First off, it appears that enum types can not be multiply // declared in more than one header file that will later be // #included in one source or linked object. You can't // extern the blasted things either! // So, don't get sucked into thinking that they'll do you a // good turn if you use them in a class! // After about an hour of fiddling around with enum types inside // a class, I concluded emphatically that they should be // totally banned from inclusion within class scope. // // Dr. Cline from Clarkston U. in NY emphasized this hazard // and also demonstrated that code can be written more compactly // without using 'boolean' as an enumeration type. // // Notice however, how I've used #define below. // // Dr. Cline also pointed out how my constructor could be compacted // and be made more readable by using the initialization list // feature. // // Discussion continued below. ---------------------------------Cut HERE--------------------------------- /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // \\ // vlist.c Version 2.00 \\ // VLIST class member functions: May 25, 1990, Brian L. Donat \\ // \\ /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ #include <stdlib.h> #include "vlist.h" #define FALSE 0 #define TRUE 1 int listError = FALSE; VLIST::VLIST() : listsize(0), head(new NODE), tail(new NODE), cnode(head) { if(!head || !tail) abort(); // No Memory?! head->previous = tail->next = 0; head->next = tail; tail->previous = head; } VLIST::~VLIST() { headend(); while(listsize) remove(); delete head; delete tail; } void VLIST::headend() { cnode = listsize ? head->next : head; } void VLIST::tailend() { cnode = listsize ? tail->previous : tail; } int VLIST::find(ELEMENT E) { for(NODE *nptr = head; nptr->next && nptr->data != E; nptr = nptr->next); cnode = (nptr->next) ? nptr : cnode; return nptr->next != 0; } void VLIST::insert(ELEMENT E) { NODE *pptr = cnode->previous; // If at the head of an empty list, fake an add(). if(!listsize && !cnode->previous) { cnode = tail; pptr = head; } // build new NODE NODE *newptr = new NODE; newptr->data = E; newptr->next = cnode; newptr->previous = cnode->previous; // Update Adjacent Nodes pptr->next = newptr; cnode->previous = newptr; cnode = newptr; listsize++; } void VLIST::remove(void) { listError = FALSE; if(!listsize) listError = TRUE; else if(!cnode->previous) listError = TRUE; else if(!cnode->next) listError = TRUE; else { NODE *pptr = cnode->previous; NODE *nptr = cnode->next; // let the previous and next NODEs reference each other. pptr->next = cnode->next; nptr->previous = cnode->previous; // remove the current node. delete cnode; // If the next pointer is not tail, make it current, // else make the previous pointer the current NODE. cnode = (nptr->next) ? nptr : pptr; listsize--; } } void VLIST::step() { cnode = cnode->next ? cnode->next : cnode; cnode = cnode->next ? cnode : cnode->previous; } void VLIST::backstep() { cnode = (cnode->previous) ? cnode->previous : cnode; cnode = (cnode->previous) ? cnode : cnode->next; } ELEMENT VLIST::getval(void) const { listError = FALSE; if(!cnode->previous || !cnode->next) listError = TRUE; return cnode->data; } ---------------------------------Cut HERE--------------------------------- // One of the only other odd things I noticed here is the following // return nptr->next != 0; // The above tidbit of code is a rather sneaky way of insuring an // integer return type. // True! nptr->next could be 0 or some lvalue. // If it had been written return nptr->next however, the compiler // bellyaches about an incompatible return type and a (int) cast // would have to be used. // Note too, that in the find() member that awful boolean enum type // is blasted to kingdom come! // // One thing that raises questions in my mind at this point is // error reporting. I originally defined some of these class // members with built in error messages, but Yeccchhh! Of course, // I didn't want to leave 'em in there. // // However, as an alternative, I stuck up a global 'listError' to // communicate the error outside the class. // Now it occurs to me that I could have used a static listError // private to the class and provided members to get at it, but // Yechhhh!!! // Notice too, how I'm having to initialize listError at the top // of every member that needs to use it. // I suppose the best thing might be to have an internal escape, // perhaps with setjmp() and longjmp() stuck in there, operating // on either a static listError or still, the global. // Anybody got a good idea on how to monitor member errors safely, // without having to abort() or exit(), or without having to // play with added overhead, etc... // -- infmx!briand@pyramid
briand@infmx.UUCP (brian donat) (05/26/90)
// // 3 of 4 // // Time for the redefined STACK and QUEUE classes. // // Well hell, the new LIST class was easy enough! // Discussion follows below. ---------------------------------Cut HERE--------------------------------- /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // \\ // list.h \\ // LIST class derived from VLIST: May 25, 1990, Brian L. Donat \\ // \\ /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ extern int listError; extern class VLIST; class LIST : public VLIST { }; ---------------------------------Cut HERE--------------------------------- // Instant derived class. A complete alias of VLIST. // Is this an idiotic thing to do or what? // // You should note that there is no #include "vlist.h" here. // Don't do it! You'll be sorry. // Use extern class VLIST instead! And make sure that there's // nothing in either file that's going to cause multiply defined // compiler errors. Unless that is, you like to spin your wheels // for no good reason! // Now, below is the new STACK class. // Pretty simple? Notice how I had to get at listError? // Helper functions are private and the definition of STACK // has VLIST being private also to prevent usage of VLIST's // members directly. // // What's interesting here is that I could have done this // thisaway: // // class STACK { // VLIST list; // public: // // and maybe achieved the same thing. // Would it be the same? // Again, you won't find any of them blased enum booleans in here. // // More Discussion follows below. ---------------------------------Cut HERE--------------------------------- /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // \\ // stack.h Version 1.00 \\ // STACK class derived from VLIST: May 25, 1990, Brian L. Donat \\ // \\ /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ #define MAXSIZE 20 #define stackError listError extern int listError; extern class VLIST; class STACK : private VLIST { int overflow(); int underflow(); public: void push(ELEMENT); ELEMENT pop(); }; ---------------------------------Cut HERE--------------------------------- // And here below, for your perusal are STACK's members. // I've made life a bit easier for genericity by declaring an ELEMENT // EDUMMY which is a default return from pop(), when nothing should // be returned. // And hey! It's OK to use enum boolean here!! // But man o' man, don't get caught with your shorts down by having // it in a '.h' file with a class definition! // // I declared overflow() and underflow() inline. Are these good // inline? I'd think so. They're only used once each! // // More Discussion follows below. ---------------------------------Cut HERE--------------------------------- /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // \\ // stack.c Version 1.00 \\ // STACK member functions : May 25, 1990, Brian L. Donat \\ // \\ /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ #include "vlist.h" #include "stack.h" const ELEMENT EDUMMY = 0; enum boolean { FALSE, TRUE }; // Note that this code is written so that // the stack can never overflow or // underflow. These tests is done during // push() or pop() to see if it will // overflow overflow or underflow will // occur prior to actual execution. inline int STACK::overflow() { return (boolean)(getsize() >= MAXSIZE); } inline int STACK::underflow() { return (boolean)!getsize(); } void STACK::push(ELEMENT E) { stackError = FALSE; if(overflow()) stackError = TRUE; else add(E); } ELEMENT STACK::pop() { ELEMENT E = EDUMMY; stackError = FALSE; if(underflow()) stackError = TRUE; else { E = getval(); remove(); } return E; } ---------------------------------Cut HERE--------------------------------- // Just the Queue stuff follows. You can punch out now if you like. ---------------------------------Cut HERE--------------------------------- /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // \\ // queue.h Version 1.00 \\ // STACK class derived from VLIST: May 25, 1990, Brian L. Donat \\ // \\ /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ #define MAXSIZE 20 #define queueError listError extern int listError; extern class VLIST; class QUEUE : private VLIST { public: int overflow(); int underflow(); void enqueue(ELEMENT); ELEMENT dequeue(); }; ---------------------------------Cut HERE--------------------------------- /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // \\ // queue.c Version 1.00 \\ // QUEUE member functions : May 25, 1990, Brian L. Donat \\ // \\ /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ #include "vlist.h" #include "queue.h" const ELEMENT EDUMMY = 0; enum boolean { FALSE, TRUE }; // Note that this code is written so that // the queue can never overflow or // underflow. These tests is done during // enqueue() or dequeue() to see if it // will overflow overflow or underflow // will occur prior to actual execution. int QUEUE::overflow() { return (boolean)(getsize() >= MAXSIZE); } int QUEUE::underflow() { return (boolean)!getsize(); } void QUEUE::enqueue(ELEMENT E) { queueError = FALSE; if(overflow()) queueError = TRUE; else { headend(); insert(E); } // In this queue, we feed the headend. } ELEMENT QUEUE::dequeue() { ELEMENT E = EDUMMY; queueError = FALSE; if(underflow()) queueError = TRUE; else { tailend(); // In this queue, we ... (No Jokes Please!) E = getval(); remove(); } return E; } ---------------------------------Cut HERE--------------------------------- -- infmx!briand@pyramid
briand@infmx.UUCP (brian donat) (05/26/90)
// // 4 of 4 // // Finally! End of discussion is immenent! // // Nothing much is changed in main(). Afterall it's only used to // test and demonstrate. // I've just added an idiotic error test VError(). I believe I // stated that it seems rather awkward getting error info out of // a class and across header file boundaries, didn't I? // Yes, I did. // // So, I do have one or two observances at this point though. // // 1. As promised, I found that I could tinker and change // class definitions without making significant changes to // my main(). // // 2. My reorganizing of STACK and QUEUE in classes was // reflected by what seems a longer compile time overall. // Keep those '.o's around. ar 'em -> lib.xxx. // // Thanks to those of you who took part in this little exercise. ---------------------------------Cut HERE--------------------------------- /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // \\ // testmain.c Version 2.00 \\ // tests for VLIST class : May 25, 1990, Brian L. Donat \\ // \\ // CC testmain.c stack.c queue.c vlist.c -o <progname> \\ // progname | more \\ // \\ /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // // This program demonstrates and provides testing for the VLIST class // and its derivatives, STACK and QUEUE. // /////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ #include <stream.h> #include "vlist.h" #include "list.h" #include "stack.h" #include "queue.h" enum boolean { FALSE, TRUE }; enum errtype { STACKFULL, STACKEMPT, QUEUEFULL, QUEUEEMPT }; void listTest(), listScan(LIST &), stackTest(), queueTest(); int VError(int, int); main() { listTest(); stackTest(); cout << "\n\n" << flush; queueTest(); cout << "\n\n" << flush; } void listTest() { LIST intList; // Note the following does not test out // the error return global listError, but // this has been validated elsewhere as // either stackError or queueError. cout << "\nLINKED LIST TEST:\n"; intList.add(9); intList.insert(3); intList.step(); intList.insert(2); intList.backstep(); intList.remove(); intList.step(); if(intList.find(2)) intList.insert(23); else intList.tailend(); intList.insert(88); intList.insert(55); intList.add(22); intList.headend(); intList.insert(100); listScan(intList); cout << "\n\n"; } // Output the LIST, head -> tail. void listScan(LIST &intList) { intList.headend(); if(intList.getsize()) { cout << "\n" << intList.getval(); intList.step(); for(int i = intList.getsize() - 1; i > 0; --i) { cout << ", " << intList.getval(); intList.step(); } } else cout << "\nList is Empty!!\n\n"; } // Build a stack from the LIST class void stackTest(void) { STACK intStack; ELEMENT E; cout << "\nSTACK TEST:\n"; intStack.push(1); if(VError(STACKFULL, stackError)) return; intStack.push(2); if(VError(STACKFULL, stackError)) return; E = intStack.pop(); if(VError(STACKEMPT, stackError)) return; cout << "\n" << E; intStack.push(3); if(VError(STACKFULL, stackError)) return; intStack.push(4); if(VError(STACKFULL, stackError)) return; E = intStack.pop(); if(VError(STACKEMPT, stackError)) return; cout << "\n" << E; E = intStack.pop(); if(VError(STACKEMPT, stackError)) return; cout << "\n" << E; E = intStack.pop(); if(VError(STACKEMPT, stackError)) return; cout << "\n" << E; intStack.push(5); if(VError(STACKFULL, stackError)) return; E = intStack.pop(); if(VError(STACKEMPT, stackError)) return; cout << "\n" << E; E = intStack.pop(); if(VError(STACKEMPT, stackError)) return; cout << "\n" << E << "\n\n"; } // Build a queue from the LIST class void queueTest(void) { QUEUE intQueue; ELEMENT E; cout << "\nQUEUE TEST:\n"; intQueue.enqueue(1); if(VError(QUEUEFULL, queueError)) return; intQueue.enqueue(2); if(VError(QUEUEFULL, queueError)) return; E = intQueue.dequeue(); if(VError(QUEUEEMPT, queueError)) return; cout << "\n" << E; intQueue.enqueue(3); if(VError(QUEUEFULL, queueError)) return; intQueue.enqueue(4); if(VError(QUEUEFULL, queueError)) return; E = intQueue.dequeue(); if(VError(QUEUEEMPT, queueError)) return; cout << "\n" << E; E = intQueue.dequeue(); if(VError(QUEUEEMPT, queueError)) return; cout << "\n" << E; E = intQueue.dequeue(); if(VError(QUEUEEMPT, queueError)) return; cout << "\n" << E; intQueue.enqueue(5); if(VError(QUEUEFULL, queueError)) return; E = intQueue.dequeue(); if(VError(QUEUEEMPT, queueError)) return; cout << "\n" << E; E = intQueue.dequeue(); if(VError(QUEUEEMPT, queueError)) return; cout << "\n" << E << "\n\n"; } // Yes, Yes, I agree! This is a horrible way of doing things. // But for brevity, I'm keeping things simple and therefore // somewhat awkward too! int VError(int errtype, int err = listError) { if(err) switch(errtype) { case STACKFULL: cout << "\nError! Stack is Full!"; break; case STACKEMPT: cout << "\nError! Stack is Empty!"; break; case QUEUEFULL: cout << "\nError! Queue is Full!"; break; case QUEUEEMPT: cout << "\nError! Queue is Empty!"; break; default: cout << "\nError of Unkown Type!"; break; } return err; } -- infmx!briand@pyramid