Rational Class
rational.h Interface/Header File
// rational.h
#ifndef RATIONAL_H
#define RATIONAL_H
#include <iostream>
class Rational {
public:
Rational(int num, int denom=1);
Rational mul(const Rational &r) const;
Rational &mulInPlace(const Rational &r);
void print(std::ostream &os) const;
private:
int num, denom;
};
inline std::ostream &operator <<(std::ostream &os, const Rational &r) {r.print(os); return os;}
#endif
#ifndef RATIONAL_H
#define RATIONAL_H
…
inline std::ostream &operator <<(std::ostream &os, const Rational &r) {r.print(os); return os;}
#endif
cpp program). It associate a name with a fragment of text; when the preprocessor
encounters the name in text it is processing, it replaces it with the associated text
#ifdef tests the value of its conditional and returns true/false if the macro has / has-not been defined.
#endif
#pragma once
once directive tells the compiler to only include the cuurent
file once during this compilation
… Rational(int num, int denom=1); … };
.cpp file for purposes of illustration.
// above -- in the body of the class definition in the header file
Rationale(int num, int denom=1) : num(num), denom(denom) {}
1 here, allowing a simple
way of specifying integer-valued Rationals.
… Rational mul(const Rational &r) const; Rational &mulInPlace(const Rational &r); …
mul; and an immutable ('non-mutating', 'out-of-place')
function, mulInPlace.
Rational) type: r1.mul(r2) and r1.mulInPlace(r2)
mul returns a new Rational, it performs a return-by-value — it must return the result of the multiplication as a third Rational
object.
mulInPlace, on the other hand, can (and should) return a reference to the operand (typically the left one) whose value is being replaced.
r1.mulInPlace(r2).mulInPlace(r3)
const
mulInPlace function the receiver (the left hand operand) is modified; but it isn't in mul function, and we'd like to indicate that fact, but there is no textual evidence
for the receiver in the function header (the receiver is the implicit object upon which the function is called).
const keyword after the parameter list in the declaration/header.
print function; again the receiving object should not be modified by the function.
print Function and << Operator
…
class Rational {
…
void print(std::ostream &os) const;
…
};
inline std::ostream &operator <<(std::ostream &os, const Rational &r) {r.print(os); return os;}
print function declaration is a reference, rather than a const-reference, as the ostream object is modified as we print to it.
<< operator for your convenience. It allows you to write
cout << r1 << endl;
rather than:
r1.printt(cout); cout << endl;
rational_app.cpp Application.h file, we can begin writing our application (this can happen simultaneously with the coding of the implementation (rational.cpp) file
#include#include "rational.h" using namespace std; int main() { Rational r1(1, 2), r2 {3}; cout << "r1: "; r1.print(cout); cout << endl; cout << "r2: " << r2 << endl; Rational r3 = r1.mul(r2); cout << "r3: " << r3 << endl; r1.mulInPlace(r2); cout << "r1: " << r1 << endl; return 0; }
Rational objects are being directly declared and created as local objects. There is no creation on the heap.
r1, I showed how the object would be output in the absence of the << (insertion) operator;
for the rest I used the insertion operator to illustrate its convenience.
r1 uses the 'classical' constructor-style direct initialization. It is also equivalent to writing:
Rational r1 = Rational(1, 2);and is thus also called functional notation.
r2 uses the 1-arg constructor, and direct-list initialization
rational.cpp Implementation File
#include <iostream>
#include "rational.h"
#include "rational_exception.h"
using namespace std;
Rational::Rational(int num, int denom) : num(num), denom(denom) {
if (denom == 0) throw RationalException("0 denominator");
}
Rational Rational::mul(const Rational &r) const {
Rational result = *this;
return result.mulInPlace(r);
}
Rational &Rational::mulInPlace(const Rational &r) {
num = num * r.num;
denom = denom * r.denom;
return *this;
}
void Rational::print(ostream &os) const {
os << num;
if (denom != 1)
os << "/" << denom;
else
os << "";
}
Rational Rational::mul(const Rational &r) const {
Rational result = *this;
return result.mulInPlace(r);
}
Rational &Rational::mulInPlace(const Rational &r) {
num = num * r.num;
denom = denom * r.denom;
return *this;
}
mul leverages mulInPlace. The straightforward (i.e., unleveraged) way to code mul would be:
Rational Rational::mul(const Rational &r) const {
return Rational{num * r.num, denom * r.denom}; // or Rational(num * r.num, denom * r.denom);
}
but then we'd have to be concerned that we've maintained semantic consistency of the multiplicatios in the two operations
Rational Rational::mul(const Rational &r) const {
return Rational(num * r.num, denom * r.denom).normalize();
}
Rational& Rational::mulInPlace(const Rational &r) {
return *this = mul(r); // assign from the new object
}