/*********************************************************************** MEMORIAL UNIVERSITY OF NEWFOUNDLAND Faculty of Engineering and Applied Science Engineering 3891(Advanced Programming) Assignment #8 Instructor: Michael Bruce-Lockhart Date: 00.11.24 Due: 00.12.04 ***********************************************************************/ /* In this assignment, we are going to play around a little bit with how you might use hierarchy and inheritance to represent electronic devices. A WORD OF WARNING. This design essentially requires derived classes to register their class with the base class. It happens that simplifies a number of problems; it also gives the design a very particular flavour. It was not done for those reasons, but rather because the approach MAPS NATURALLY ONTO THE UNDERLYING PROBLEM. Circuit simulators like SPICE generally do type each device, using a single letter as we have done. The warning? Simple. This is the only design involving inheritance you will really see in this course. Later on, if you have to do your own, don't use this one as a template. Fit your design to your problem. */ // Typically simulation programs distinguish between inputs, outputs and // bidirectional (such as resistor) pins enum PinType{IN,OUT,BIDI}; // Here we represent a network by a simple number (the node number of // the net. 0 will be taken to mean ground, and any negative number // represents no connection. Using a typedef her means we can easily redefine // what we mean by a net later on if we want. typedef int net; /* Note that in C++, the ONLY difference between structures and classes is that the default access for structs is public, for classes, private. Here I am using pin like a C structure except that I have built a constructor which is useful for returning the structure from functions. */ struct Pin { PinType t; net n; // net number to which pin is connected. Pin(PinType p=BIDI,int i = -1){t=p;n = i;} // A simple constructor }; // to init struct /* A generic base class for electronics devices. It contains a one letter designator, an instance no. (to tell one device from another), the no. of pins and an array of those pins. In a CAD package used both for SCHEMATIC CAPTURE and for SIMULATION, basic graphical information such as the (x,y) location of the device could be bundled with the base class. */ class Device { public: // The constructor creates a device of n pins (where n > 1), with desig // d and instance number i. The array of pins should be initialized // to (BIDI,-1) - bidirectional, unconnected. // In the event that a device cannot be constructed (e.g. insufficient // memory for the pin array or an invalid arguement), a null device // should be created (desig = '\0', instance = pins = 0; Device(char d, int i, int n); ~Device(); // Kill off the pin array // Overload of the iterator to allow reading or connection of the i'th pin // Thus the statement d1[2] = pin(in,4) sets pin no. 2 of device d1 to an // input, connected to net #4. // NOTE that pins are numbered from 1 to pins. In the event of an index // being out of range, the routine should return a dummy pin that the user // can safely overwrite. Pin& operator[](int i); int numPins() {return mNumPins;} // The last function should not return a pointer to any part of a // Device object since the user could use that to overwrite the object. // Instead, it should create a static buffer and build a name there. The name // itself should consist of the designation character followed by the // characters representing the instance no. It should be in the form of // a proper string (i.e. null terminated). You may assume reference instance // numbers will not exceed four figures. When the string is built in the // buffer return a pointer to it. You might consider what problems there are // with this strategy. // Example strings would be "R12" for the 12th resistor and "U189" for the // 189th digital gate (see comments under individual derived classes below). // HINT: you are going to need the itoa() function, or something similar, // from stdlib.h char* who(); protected: char mDesignator; // reference designator int mInstance; // of that designator int mNumPins; // No. of pins Pin* mpPins; // Pin array - numbered 1 to pins static Pin dummy; // A dummy pin that can be safe to write to if improper reference }; // Now, some derived classes. class Simple:public Device { // The class of all two pin devices public: // Must construct a 2 pin device, designation d, instance i, value v and // tolerance tl Simple (char d,int i, double v=1.,double tl=0.); double value(); // Return value double tolerance(); // Return tolerance void set(double v,double t); // Change value & tolerance protected: double mValue; // e.g. 1000 for a 1 KOhm resistor double mTolerance; // e.g. .01 for a 1% resistor }; /* At last! - real devices. All of these classes use a static 'last' to track the last device number ASSIGNED TO THAT CLASS. Thus 'last' might be 4 for capacitors, 7 for resistors, and 3 for gates. It is used by the constructor to find the next instance of that device. For example, if a new capacitor is constructed, its designator should be 'C' and its instance should be 5 and 'last' for class capacitor should be advanced to 5. Note there is no destructor. If we destroy a capacitor, we will have one less capacitor; nevertheless, the next capacitor will be assigned the no. 6 since there is no guarantee that C5 was the one destroyed. For capacitors, value is the value in farads, for resistors it is in ohms. Don't forget, you must define the static variables in your implementation. They are only declared within the class (no space is set aside for them). This is because there is only 1 of a static variable for every object of the class. i.e. all resistor objects share the same last. Normally, space for member data is set aside only when an object of the class is created. Static data must effectively pre-exist all objects of the class nor is it tied to any one object of the class but shared by all. Thus space must be independently put aside for it ("defined") when the class itself is defined (i.e. implemented). It may be initialized at the same time. */ enum CapType {CERAMIC,ELECTROLYTIC,TANTALUM}; class Capacitor:public Simple { public: Capacitor(double c=1e-12, CapType t=CERAMIC,double m = .1); // Construct 2 pin device, designation 'C', instance last, // of value c, capType t, tolerance m CapType type(); private: static int last; // The last capacitor number assigned CapType mCapType; }; class Resistor:public Simple { public: // Construct simple device, designation 'R', instance last, // of value r, tolerance t Resistor(double r=1e3,double t=.05); private: static int last; // The last resistor number assigned }; /* Two input logic gates have three pins and are designated by a 'U'. Again the instance should be the current value of last. Pins 1 & 2 are always inputs while pin 3 is always the output. */ enum LogicType{OR,AND,NOR,NAND,XOR}; // What kind of logic function class Gate:public Device { public: Gate(LogicType t=OR); // Need NULL constructor for new LogicType logicType(); private: static int last; LogicType mLogicType; }; /* When you've implemented all this stuff, you should be able to test a resistor object for example by Resistor R1(10e3,.05); // Construct a 10K, 5% resistor R1[1].n = 4; // Connect pin 1 to net 4 R1[2].n = 0; // Connect pin 2 to ground cout << R1.who(); This works because a resistor is a device and the Device class overloads the operator[] to allow access to pins as well as providing a method for retrieving (safely) the name of the resistor. */