250 likes | 403 Views
Case Study - Fractions. Timothy Budd Oregon State University. Objectives. To easily create new instances of the rational number abstraction. To manipulate rational numbers using arithmetic operations, yielding new rational number results.
E N D
Case Study - Fractions Timothy Budd Oregon State University
Objectives • To easily create new instances of the rational number abstraction. • To manipulate rational numbers using arithmetic operations, yielding new rational number results. • It should be possible to mix rationals and other arithmeticquantities in the same expression. • It should be possible to assign a rational number value to arational number variable. • The modification forms of assignment should be supported. • To compare one rational number to another. • It should be possible to perform input and output operations with rational numbers.
Example of Rational Numbers // probability one is 1 in 8rational p1 (1,8); // probability two is 2 in 3rational p2 (2, 3); // probably of both together is their productrational p3 = p1 * p2; // probably of either independent is their sumrational p4 = p1 + p2; // what is this probability?cout << "combined probability is " << p3 << endl;cout << "independent probability is " << p4 << endl
// class rational// rational number data abstraction class rational {public: // constructors rational () : top(0), bottom(1) { } rational (int t) : top(t), bottom(1) { } rational (int t, int b) : top(t), bottom(b) { normalize(); } rational (const rational & r) : top(r.top), bottom(r.bottom) { } // accesser functions int numerator () const { return top; } int denominator () const { return bottom; } // assignments void operator = (const rational & r) {top = r.top; bottom = r.bottom;} void operator += (const rational &); // other operations operator double () const { return top / (double) bottom; } const rational & operator++() { top += bottom; return this; } const rational operator++(int);private: int top; // data areas int bottom; void normalize (); // operation used internally };
Interface and Implementation • The code associated with a component is separated into two files. • The interface file describes how to use the component. • The implementation file contains the actual code to perform the actions. • The class description is divided into two parts. • Public: behaviors and data fields that users of the data abstraction can access. • Private: behavior and fields that are accessible only within the component and are off-limits to other users.
Constructors • Every class should define a default constructor. • Use initializes whenever possible. • The declaration that uses an empty parentheses list is not syntactically incorrect; simply not doing what the programmer expects. • Every class should define a copy constructor.
Member Function • Anaccessorfunction provides access to the internal state of an object. left.numerator() * right.denominator() + right.numerator() * left.denominator() • A function defined as part of the behavior of a class is called amember function. • Theconstkeyword in C++ is not the same as thefinalkeyword in Java.
Operators • In C++, operators can potentially have overloaded meanings. • Requirement: the definition must not require arguments that match any existing definition. • Can be achieved if one or both arguments are a new data type, since no existing definition can be using these types.
Operators const rational operator + (const rational & left, const rational & right) { // return sum of two rational numbers rational result ( left.numerator() * right.denominator() + right.numerator() * left.denominator(), left.denominator() * right.denominator()); return result; } • The address operator in the argument list indicates that the left and right values will be passedby reference.
Operators • A list of prototypes for the arithmetic operations. // prototypes for arithmetic operations, including unary negationconst rational operator + (const rational &, const rational &);const rational operator - (const rational &, const rational &);const rational operator * (const rational &, const rational &);const rational operator / (const rational &, const rational &);const rational operator - (const rational &);bool operator < (const rational &, const rational &);bool operator == (const rational &, const rational &);
Unary Negation Operator const rational operator - (const rational & val) { // return negation of argument value return rational (- val.numerator(), val.denominator()); }
Comparison Operators bool operator < (const rational & left, const rational & right) { // less than comparison of two rational numbers return left.numerator() * right.denominator() < right.numerator() * left.denominator(); } bool operator == (const rational & left, const rational & right) { return left.numerator() * right.denominator() == right.numerator() * left.denominator(); }
Increment and Decrement • The variablethisis a value in Java but is a pointer in C++. class rational { ... const rational & operator ++ () { top += bottom; return *this;} ... }; const rational rational::operator++ (int) { // increment fraction, but return original value, make clone rational clone(*this); top += bottom; // make change return clone; // return clone }
Functions • Functions cannot access the internal (private) structure of any class, unless declared as afriend. const rational abs (const rational & num) { // return the absolute value of a rational number int newtop; int newbottom = num.denominator(); // get non-negative numerator part if (num.numerator() < 0) newtop = - num.numerator(); else newtop = num.numerator(); // create and return result return rational(newtop, newbottom);}
Member Function Operators void rational::operator += (const rational & right) { // modify by adding right hand side top = top * right.denominator() + bottom * right.numerator(); bottom *= right.denominator(); // normalize the result, ensuring lowest denominator form normalize(); }
void rational::normalize() { // normalize rational by (a) moving sign to numerator :// b) making sure numerator and denominator have no common divisorsint sign = 1; // sign is 1 if non-negative, -1 if negativeif (top < 0) { sign = -1; top = - top;}if (bottom < 0) { sign = - sign; bottom = - bottom;}// make sure we are not dividing by zeroif (bottom == 0)throw range_error("fraction with zero numerator");// find greatest common divisor int d = gcd(top, bottom);// move sign to top and divide by gcdtop = sign * (top / d);bottom = bottom / d; }
Member Function Operators try { ... // computation involving rationals} catch (range_error & e) { printf("got exception %s", e.what()); }
Conversion Operations • Constuctors are also used implicitly. rational x, y; ...x = y * 3; • Temporary values can also be created directly by the programmer, by invoking the constructor as if it were an ordinary function. x = y * rational(3, 4);
Conversion Operations • Conversions the other direction, from an object type to another type, are defined using a conversion operator. • A conversion operator is an operator whose name is a type. class rational { ... operator double () const { return top / (double) bottom; }};
Conversion Operations • Avoidcastif possible, but if unavoidable use astaticordynamiccast. rational x(3, 4);cout << "3/4 of pi is " << (3.14 * double(x)) << '\n';
Input and Output Streams • Output is easily accommodated by redefining the left shift operator <<, and providing a new meaning. ostream & operator << (ostream & out, const rational & value) { // print representation of rational number on // an output stream out << value.numerator() << '/' << value.denominator(); return out; }
Input and Output Streams • Predefined precedence is low enough to allow arithmetic expressions to appear in output without using parenthesis, as in: cout << "a + b * c is " << a + b * c << '\n'; • The left shift operator with its conventional meaning can be used in anoutput statement, by surrounding it with parenthesis: cout << " a left shift by 3 is " << (a << 3) << '\n';
Stream Input • A loop that would read values repeatedlyfrom the input until end of file could be written as: while (cin >> intval) { // process intval ...}// reach this point on end of input... • An easy way to remember, the stream I/O operations is to visualize them as arrows. • The input operator, >> x, points data into x, whilethe output operator, << x, copies data out of x.
istream & operator >> (istream & in, rational & r) { // read a rational number from an input streamint t, b;// read the topin >> t;// if there is a slash, read the next numberchar c;in >> c;if (c == '/') in >> b; // read bottom partelse { in.putback(c); b = 1;}// do the assignmentrational newValue(t, b);r = newValue;// return the streamreturn in; }