116095596 -1- CHAPTER 2 INTRODUCTION TO : Phases of the software life cycle Differences between structured approach and OOP Runtime error Exception Constructor Design Operator Overloading 116095596 -2- Object-Oriented Program Design Software Development Methodology Problem Analysis/Program Definition - The programmer analyzes the problem with the client - Determines what form the input and output should take - Determines algorithms Design Phase - Object oriented design differs greatly from the traditional structured design - Traditional structured design is modular and hierarchical – top down. - Object oriented designs use Objects as the primary building blocks of the program - Control modules direct the interaction among the objects - The programmer writes a declaration for each class - Class methods are tested INDIVIDUALLY outside the realm of the large application, under controlled conditions ( major advantage ) - Top-down design consists of a main program and subprograms to control the interaction of the objects - The main program and subprograms from the design framework. Coding ( Implementation Phase) - The main program and subprograms that implement the program design framework are written in the coding phase Testing Phase We can verify a program’s coding by testing each object’s interaction with the control modules in the design framework. Maintenance Phase This phase begins as soon as the client installs the system and software systems are regularly upgraded 116095596 -3- Handling Runtime Errors: - Terminate Program – ex: call exit() function - Set a Flag Setting a flag in object oriented programming has its problems. For example: If the error occurs in a method contained in the object – what should the object do? Handle the error? Return a return value to the client? Use e reference argument? -C++ Exceptions Are the most flexible for of error reporting. An exception is an object that contains information that is transmitted without using the normal function return process. The function that encounters the error instantiates an object and the exception is reported back to the block of code that is designed to catch the exception. This block of code is called the exception handler. See hand out for details and examples. 116095596 -4- Object Composition The term composition refers to a condition that exists when a class (client class) contains one or more data members that are objects of class (supplier class) type. #ifndef TIMECARD_CLASS #define TIMECARD_CLASS #include <iostream> #include "d_time24.h" #include "d_except.h" #include "d_util.h" using namespace std; class timeCard { public: timeCard(const string& ssno, double rate, int punchInHour, int punchInMinute); void punchOut(const time24& t); // assign t to punchOutTime and set hasPunched to true void writeSalaryInfo(); // output a log that includes the beginning and ending times for // the day's work, the amount of time worked, the pay rate // and the earnings. // precondition: throw a rangeError exception if worker has not // punched out (hasPunched == false) private: string workerID; time24 punchInTime, punchOutTime; double payrate; bool hasPunched; }; // supplier-class objects 116095596 -5- Constructor with Composition: // use initialization list to initialize data members timeCard::timeCard ( const string& ssno, double rate, int punchInHour, int punchInMinute): workerID(ssno), payrate(rate), hasPunched(false), punchInTime(punchInHour, punchInMinute) { } // alternative implementation // this technique works only because time24 have default constructors // in general use initialization list timeCard::timeCard(const string& ssno, double rate, int punchInHour, int punchInMinute): { workerID = ssno; payrate = rate; hasPunched = false; punchInTime = time24(punchInHour, punchInMinute); } void timeCard::punchOut(const time24& t) { punchOutTime = t; hasPunched = true; } void timeCard::writeSalaryInfo() { // throw an exception if the worker did not punch out if (hasPunched == false) throw rangeError("timeCard: writeSalaryInfo() called before " "punching out"); // total time worked time24 timeWorked = punchInTime.duration(punchOutTime); // hours and fraction of an hour worked double hoursWorked = timeWorked.getHour() + timeWorked.getMinute()/60.0; // format the output cout << "Worker: " << workerID << endl; cout << "Start time: "; punchInTime.writeTime(); cout << " End time: "; punchOutTime.writeTime(); cout << endl; cout << "Total time: " << setreal(1,2) << hoursWorked << " hours" << endl; cout << "At $" << setreal(1,2) << payrate << " per hour, the days earnings are $" << setreal(1,2) << payrate*hoursWorked << endl; } #endif // TIMECARD_CLASS 116095596 -6- Program example with composition and exception handler: // // // // // // // // File: prg2_2.cpp the program simulates a temporary employees arriving at work and leaving when the plant closes at 5:00 PM.it prompts for the hour and minute of arrival at work and the social security number. it uses the timeCard class to determine the employee pay for the day. The program uses a random number to simulate that 3 out of every 4 employees punch out. in this situation, the program must handle a rangeError exception thrown by the timeCard class when an employee does not punch out. The catch block simulates the supervisor punching out for the employee #include <iostream> #include "d_tcard.h" #include "d_random.h" // use timeCard class // randomNumber class using namespace std; int main() { const double PAYRATE = 12.50; // posted pay rate const time24 CHECKOUT(17,0); // work ends at 5:00 PM string id; // employee data input from the keyboard int inHour, inMinute; randomNumber rnd; // simulate 1/4 of employees forgetting to punch out cout << "Enter hour and minute of arrival at work: "; cin >> inHour >> inMinute; cout << "Enter social security number: "; cin >> id; // declare a timeCard object for the employee timeCard employee(id, PAYRATE, inHour, inMinute); // represents 3 out of 4 employees punching out if (rnd.frandom() > .25) employee.punchOut(CHECKOUT); // punch out // include writeSalary() call in a try block. it // throws the rangeError exception if the employee // has not punched out try { employee.writeSalaryInfo(); } catch (const rangeError& re) { // output the error string in the rangeError object re cerr << re.what() << endl; // supervisor punches out the employee. display the salary information employee.punchOut(CHECKOUT); employee.writeSalaryInfo(); } return 0; } 116095596 -7- Program example Output: /* Run 1: Enter hour and minute of arrival at work: 8 00 Enter social security number: 345-27-8156 Worker: 345-27-8156 Start time: 8:00 End time: 17:00 Total time: 9.0 hours At $12.50 per hour, the days earnings are $112.50 Run 2: Enter hour and minute of arrival at work: 9 15 Enter social security number: 766-25-6728 timeCard: writeSalaryInfo() called before punching out Worker: 766-25-6728 Start time: 9:15 End time: 17:00 Total time: 7.75 hours At $12.50 per hour, the days earnings are $96.88 116095596 -8- Operator Overloading Operator Overloading- i.e. redefining standard operator symbols ( e.g. + , [ ] , ... ), is a feature that allows us to implement operations for an abstract data type using standard language operators (with their precedence and associativity properties) as class methods. Standard Class Method Overloaded Operators R = P. AddMat ( Q ); S = R . MultMat ( Q.AddMat ( P ) ); R = P + Q; S = R * ( Q + P ); Date ( 6, 6, 44 ) < ( Date ( 12, 25, 80 ); // compare two date objects PrintDate Method: Overloaded “<<” operator cout << “ The last day ...... “; D. PrintDate(); cout << “ the last day .. “ << D; C++ allows overloading most of its native operators, including the stream operators. New operators cannot be created. The term operator overloading implies that language operators such as +, !=, [ ] , and = can be redefined for a class type. C++ provides a variety of techniques for redefining operators. C++ requires that at least one argument of an overloaded operator must be an object or an object reference. Techniques include: Client-defined external ( free) functions, class members, and friends. 116095596 -9- Operator Overloading (continued ) Client defined external (free ) function: // Note: == operator tied to data type not class int operator== ( const DataType& a, const DataType& b ); struct Employee { ... int ID; } int operator== (const Employee& a, const Employee& b ) { return a.ID == b.ID; // compare ID fields } int SeqList : : Find ( DataType& item ) const { int i = 0; if ( ListEmpty() ) return 0; // the relational equal operator compares list items while ( i < size && ! ( item == listitem[i] ) ) i ++; if ( i < size ) { item = listitem[i]; return 1; } else return 0; } 116095596 - 10 - Operator Overloading ( continued ) Class Members: A class may have methods that combine objects in an expression using infix notation. Example Vector Addition, Multiplication and Negation: class Vec2d { private: double x1, x2; // private data members public: Vec2d ( double h = 0.0, double v = 0.0 ); // constructor default // values // member functions Vec2d operator- (void); // negation Vec2d operator+ (const Vec2d& V ); // addition double operator* ( const Vec2d& V ); // dot product // friend functions friend Vec2d operator* ( const double c, const Vec2d& V ); friend ostream& operator<< ( ostream& os, const vec2d& U ); } for instance, with the objects Vec2d U ( 1,2 ), V ( 2,3 ); U + V = ( 1,2 ) + ( 2,3 ) = ( 3,5 ) -U = -(1,2) = (-1, -2 ) U * V = (1,2) * ( 2,3 ) = 8 116095596 - 11 - Operator overloading requires: 1. Operators must obey the precedence, associativity, and number of operands dictated by the built-in definition of the operator. For example, ‘ * ‘ is a binary operator and must always have two parameters when it is overloaded. 2. All operators in C++ can be overloaded, with the exception of the following: , ( comma operator ) sizeof : : ( class scope operator ) ? : ( conditional expression ) 3. Overloaded operators cannot have default arguments; all operands for the operator must be present. 4. When an operator is overloaded as a member function, the object associated with the operator is always the left-most operand. 5. As member functions, the unary operators take no arguments, and binary operators take one argument. 116095596 - 12 - FRIEND FUNCTIONS: Typically, only member functions of a class have access to the private data of a class. Sometimes it may be necessary for non-members to have access to private data. This can be accomplished by using friend functions. Friend functions are defined outside of the class but can have access to private data members. A friend function is declared by preceding the function declaration in the class with the keyword friend. Example: Scalar multiplication ( each component of a vector is multiplied by a numeric value.) friend Vec2d operator* ( const double c, const vec2d& U ); The definition of the function is outside the scope of the class and is implemented as a standard function. Both arguments are passed to ‘*’ and scalar is left hand operand. Vec2d operator* ( double c, Vec2d U ) { return Vec2d ( c * U.x , c * U.y ); // accesses private members of object } Rules : 1. Place the function declaration inside the class preceded by the keyword friend . The function is defined outside the class and is not a member of the class. 2. They keyword friend is used only within the function declaration in the class and not with the function definition. 3. A friend function has access to private data members. 4. A friend function gains access to members of the class only by being passed objects as parameters and using the dot notation to reference a member. 5. In order to overload a unary operator as a friend, pass the operand as a parameter. When overloading binary operators, pass both operands as parameters. 116095596 - 13 - Overloading Stream Operators The file <iostream.h> contains declarations for two classes named ostream and istream. The I/O stream systems provides the definitions for operators “>>” and “ <<”. The user can overload the stream operators to implement I/O of a user-defined type. Ex: Date D; cin >> D; cout << D; In would not be practical to explicitly declare the overloaded operators in <iostream.h> . Overloading the stream operators uses friend functions . class CL { ... public: ... friend istream& operator>> ( istream& istr, CL& Variable ); friend ostream& operator<< ( ostream& ostr, const CL& Value ) }; 116095596 - 14 - Implementation: - Overloading stream input operator // - reading a rational number ( ex: 3/4 ) istream& operator >> ( istream& istr, Rational& x ) { char c; istr >> x.num >> c >> x.den; // reads 3/4 if ( x.den = = 0 ) { cerr << “ A zero denominator is invalid\n”; exit (1); } x.Standardize ( ) ; return istr ; } Note: - The Data item Variable “x” is passed by reference since it is assigned a value. - The parameter “istr” represents an input stream such as cin and since the I/O process alters the state of the stream it must be passed by reference - The function returns a reference to an istream so that the operator can be used in a chain. cin >> m >> n ; Implementation: Overloading stream output operator ostream& operator << ( ostream& ostr, const Rational& x ) { ostr << x.num << ‘/’ << x.den; // access private data members // because overloaded stream operator // is a friend function of the class return ostr; } 116095596 - 15 - Conversion from Object Type C++ language defines explicit conversions ( ex: cast ) or implicit conversion (ex: assigning double to a long )between primitive types. A class may contain member function(s) that convert an object to a value of another data type. Ex: class CL { ........ operator NewType ( void ); }; NewType a; // Instantiate object of type NewType CL obj; // Instantiate object of type CL a = NewType(obj); // Explicit conversion a = obj; // Implicit conversion The operator NewType() takes an object and returns a value of type NewType. The target type NewType, is often a standard type such as int or float. the operator NewType is a unary operator and therefore does not contain a parameter. There is now return type - it is implicit in the name NewType. 116095596 - 16 - Complete program 2-3 – illustrates operator overloading #ifndef TIME24_CLASS #define TIME24_CLASS #include <iostream> #include <iomanip> #include "d_except.h" // for rangeError exception using namespace std; #ifdef _MSC_VER class time24; time24 operator+ (const time24& lhs, const time24& rhs); time24 operator+ (const time24& lhs, int min); time24 operator+ (int min, const time24& rhs); time24 operator- (const time24& lhs, const time24& rhs); bool operator== (const time24& lhs, const time24& rhs); bool operator< (const time24& lhs, const time24& rhs); istream& operator>> (istream& istr, time24 &r); ostream& operator<< (ostream& ostr, const time24& r); #endif // _MSC_VER class time24 { public: time24(int h = 0, int m = 0); // constructor initializes hour and minute void addTime(int m); // update time by adding m minutes to the current time // Precondition: m must be >= 0 // Postcondition: The new time is m minutes later time24 duration(const time24& t); // return the length of time from the current time to some later // time t as a time24 value // Precondition: time t must not be earlier than the current time. // if it is, throw a rangeError exception void readTime(); // input from the keyboard time in the form hh:mm // Postcondition: Assign value hh to hour and mm to minute and // adjust units to the proper range. void writeTime() const; // display on the screen the current time in the form hh:mm int getHour() const; // return the hour value for the current time int getMinute() const; // return the minute value for the current time 116095596 Program 2-3 ( continued ) - 17 - // THESE FUNCTIONS ARE DISCUSSED IN CHAPTER 2 ON OPERATOR // OVERLOADING friend bool operator== (const time24& lhs, const time24& rhs); friend bool operator< (const time24& lhs, const time24& rhs); friend time24 operator+ (const time24& lhs, const time24& rhs); // form and return lhs + rhs friend time24 operator+ (const time24& lhs, int min); // form and return lhs + min // Precondition: min must be >= 0 friend time24 operator+ (int min, const time24& rhs); // form and return min + rhs // Precondition: min must be >= 0 friend time24 operator- (const time24& lhs, const time24& rhs); // form and return lhs - rhs // Precondition: lhs >= rhs. if not, throw a rangeError exception time24& operator+= (const time24& rhs); // current object = current object + rhs // Postcondition: the time increases by the value of rhs time24& operator+= (int min); // current object = current object + min // Precondition: min must be >= 0 // Postcondition: the time increases by min minutes friend istream& operator>> (istream& istr, time24& t); // input t in the format hh:mm. may omit the leading digit // if hours or minutes < 10 friend ostream& operator<< (ostream& ostr, const time24& t); // output t in the format hh:mm. always include two digits // for the minute (e.g. 15:07). hours before 12 use 1 digit // and precede the hour by a blank (e.g. " 7:15") private: int hour, minute; // data members // utility function sets the hour value in the range 0 to 23 // and the minute value in the range 0 to 50 void normalizeTime(); }; 116095596 Program 2-3 ( continued) - 18 - // *********************************************************** // time24 class implementation // *********************************************************** // set minute and hour within their proper ranges void time24::normalizeTime() { int extraHours = minute / 60; // set minute in range 0 to 59 minute %= 60; // update hour. set in range 0 to 23 hour = (hour + extraHours) % 24; } // constructor. initialize time data time24::time24(int h, int m) : hour(h), minute(m) { // put hour and minute in correct range normalizeTime(); } // add m minutes to the time void time24::addTime(int m) { // add m to minute. minute may exceed 59, so normalize minute += m; normalizeTime(); } time24 time24::duration(const time24& t) { // convert current time and time t to minutes int currTime = hour * 60 + minute; int tTime = t.hour * 60 + t.minute; // if t is earlier than the current time, throw an exception if (tTime < currTime) throw rangeError( "time24 duration(): argument is an earlier time"); else // create an anonymous object as the return value return time24(0, tTime-currTime); } void time24::readTime() { char colonSeparator; cin >> hour >> colonSeparator >> minute; // make sure hour and minute are in range normalizeTime(); } 116095596 Program 2-3 continued - 19 - // output time in the format <hour>:<minute> void time24::writeTime() const { // the implementation uses stream handling functions // not discussed in the book. consult your compiler // help system for details // save current format flags and fill character long currentFlags = cout.flags(); char currentFill = cout.fill(); // set fill char to ' ' and enable right justification cout.fill(' '); cout.setf(ios::right,ios::adjustfield); // output the hour cout << setw(2) << hour << ':'; // set fill char to '0' and output the minute cout.fill('0'); cout << setw(2) << minute << " "; // restore the fill char and the format flags cout.fill(currentFill); cout.setf(currentFlags); } int time24::getHour() const { return hour; } int time24::getMinute() const { return minute; } // compare hours and minutes bool operator== (const time24& lhs, const time24& rhs) { return lhs.hour == rhs.hour && lhs.minute == rhs.minute; } // convert the hour and minute values for each operand to // minutes. compare lhs in minutes with rhs in minutes bool operator< (const time24& lhs, const time24& rhs) { return (lhs.hour*60 + lhs.minute) < (rhs.hour*60 + rhs.minute); } // create an anonymous object with hour = lhs.hour + rhs.hour // and minute = lhs.minute+rhs.minute. time24 operator+ (const time24& lhs, const time24& rhs) { return time24(lhs.hour+rhs.hour, lhs.minute+rhs.minute); } 116095596 Program2-3 continued - 20 - // create an anonymous object with hour = lhs.hour and // minute = lhs.minute + min. time24 operator+ (const time24& lhs, int min){ return time24(lhs.hour, lhs.minute + min); } // return the value rhs + min that is computed by // time24 operator+ (const time24& lhs, int min) time24 operator+ (int min, const time24& rhs) { return rhs + min; } // using the < operator, check whether lhs < rhs is true. if so // terminate; otherwise return a time24 result built // using the constructor time24 operator- (const time24& lhs, const time24& rhs) { if (lhs < rhs) throw rangeError("time24 operator-: Cannot subtract later from earlier time"); // convert each object to minutes and compute the // difference in minutes. return the time24 object // having 0 as hours and the value of the difference // as minutes return time24(0, (lhs.hour*60 + lhs.minute) (rhs.hour*60 + rhs.minute)); } // implement += by using addition with operands *this and rhs time24& time24::operator+= (const time24& rhs) { // add *this and rhs using overloaded + operator *this = *this + rhs; // return a reference to the current object return *this; } // implement += by using addition with operands *this and min time24& time24::operator+= (int min) { // add *this and min using overloaded + operator *this = *this + min; // return a reference to the current object return *this; } 116095596 Program 2-3 continued - 21 - // overload stream operator >>. input has the form // hour:minute istream& operator>> (istream& istr, time24& t) { char separatorChar; istr >> t.hour >> separatorChar >> t.minute; // make sure hour and minute are in range t.normalizeTime(); // return the stream return istr; } // overload stream operator <<. output in the form // hour:minute ostream& operator<< (ostream& ostr, const time24& t) { // the implementation uses stream handling functions // not discussed in the book. consult your compiler // help system for details // save current format flags and fill character long currentFlags = ostr.flags(); char currentFill = ostr.fill(); // set fill char to ' ' and enable right justification ostr.fill(' '); ostr.setf(ios::right,ios::adjustfield); // output the hour ostr << setw(2) << t.hour << ':'; // set fill char to '0' and output the minute ostr.fill('0'); ostr << setw(2) << t.minute; // restore the fill char and the format flags ostr.fill(currentFill); ostr.setf(currentFlags); return ostr; } #endif // TIME24_CLASS 116095596 - 22 // File: prg2_3.cpp // prompt the user for the time a movie starts and the // length of the movie. use the time24 + operator to determine // when the movie ends. a bus always arrives in front of // the theater at 22:45. use the time24 - operator to compute // the waiting time at the bus stop. output the time the movie // ends and the waiting time #include <iostream> #include "d_time24.h" using namespace std; int main() { time24 startMovie, movieLength, endMovie, busArrival(22,45), waitingTime; // prompt and input times for movie to start and its length cout << "Enter the time the movie starts and its length: "; cin >> startMovie >> movieLength; // compute the time the movie ends and the waiting time for the bus endMovie = startMovie + movieLength; waitingTime = busArrival - endMovie; cout << "Movie ends at " << endMovie << endl; cout << "Waiting time for the bus is " << waitingTime << endl; return 0; } /* Run: Enter the time the movie starts and its length: 20:15 1:50 Movie ends at 22:05 Waiting time for the bus is 0:40 */