A fraction comprises two integers, a numerator and a denominator. Fractions support all arithmetic operations, but this example only considers four: addition, subtraction, multiplication, and division. These variables and functions are just enough detail to make fractions interesting demonstrations of classes and objects. The fraction example will consist of three files that follow the same pattern as the class version of the Time example. Together, fraction.h and fraction.cpp form a general server or supplier that any class needing basic fraction operations may use. The calc.cpp file demonstrates a client program that uses the fraction class.
![]() |
class fraction
{
private:
int numerator;
int denominator;
public:
fraction(int n = 0, int d = 1);
fraction add(fraction f2) const;
fraction sub(fraction f2) const;
fraction mult(fraction f2) const;
fraction div(fraction f2) const;
void print() const;
void read();
}; |
| (a) | (b) |
We can write even small, simple programs in many ways. As the size and complexity of programs increase, so does the number of ways we can write them. Adding to this complexity is a contemporary practice of building large, complex systems from individual programs - that means that many programs must work together to solve a large, complex problem. Sometimes, when we write a program, we can choose how to implement it, but other times, we create programs for customers with additional requirements, including integrating them with existing software. The additional constraints may seem arbitrary and stifling our creativity - we may even feel micromanaged. Nevertheless, the requirements are necessary to ensure that our new code integrates with the existing code or behaves as the customer expects.
A requirements document is a formal statement of what a program or system of programs must do. It generally does not specify how the program or system accomplishes its tasks. This separation of "what" from "how" is another way of looking at the separation of a class's public interface from its implementation. Large projects or systems may consist of many individual but interconnected programs; in these cases, the requirements may form rather large documents. The requirements for individual programs are generally much smaller, but we must study them carefully to ensure that the program completes all of its tasks accurately. The following requirements are arbitrary, but they give us much-needed practice in reading, understanding, and fulfilling a set of requirements.
fraction f;fraction f(5);fraction f(2, 3);, which represents the fraction 2/32/3 or 5/3Following best practices for class implementation, the constructor will use an initializer list to initialize the numerator and the denominator. We can make one constructor serve all three roles (requirement 1) by using default arguments. The constructor can reduce the newly created fraction to the lowest terms (requirement 5) by finding the greatest common divisor and dividing the numerator and denominator by that value.
| fraction.h | fraction.cpp |
|---|---|
fraction(int n = 0, int d = 1); |
fraction::fraction(int n, int d) : numerator(n), denominator(d)
{
int common = gcd(numerator, denominator);
numerator /= common;
denominator /= common;
} |
The four arithmetic operations are long-established and well-defined for fractions, so you undoubtedly learned them long ago. The next figure presents the formulas for fraction arithmetic using a familiar notation suitable as a beginning point for programming the fraction class.
The formulas describe how to calculate the arithmetic operations. Our main task at this point is translating those formulas into C++ code. We have translated much more complex formulas in previous chapters, but for this problem, we must also translate them into object-oriented C++. So, our next step is to form a conceptual translation - that is, not to the final C++ functions but to an intermediate form intended to help bridge the gap between the formulas and the code.
![]() |
| (a) |
f3.numerator = f1.numerator * f2.denominator + f1.denominator * f2.numerator; f3.denominator = f1.denominator * f2.denominator; |
| (b) |
Depending on its complexity, there are generally many ways to write a function. The example uses the fraction add and mult functions to illustrate two slightly different ways. The fraction.cpp file linked at the bottom of the page contains all four arithmetic functions.
fraction fraction::add(fraction f2) const
{
int n = numerator * f2.denominator + f2.numerator * denominator;
int d = denominator * f2.denominator;
return fraction(n, d);
}
fraction fraction::mult(fraction f2) const
{
return fraction(numerator * f2.numerator, denominator * f2.denominator);
}
const keyword makes the implicit fraction object, the object bound to the functions through the this pointer, unchangeable as well. Making both objects unchangeable satisfies requirement 2 above.
The two remaining fraction functions are small and relatively simple.
void fraction::print() const
{
cout << endl << numerator << "/" << denominator << endl;
}
void fraction::read()
{
cout << "Please enter the numerator: ";
cin >> numerator;
cout << "Please enter the denominator: ";
cin >> denominator;
}
const keyword prevents the function from making any changes.
Similarly, requirement 7 specifies the behavior of the input or read function, which must change the object, so the const keyword is not used here. Again, compare the requirement to the C++ code. There wasn't a requirement for the read function to reduce the fraction to the lowest terms or check for a zero denominator. However, it's fairly straightforward to do both. Adding a private helper function and moving the reduction code from the constructor to it makes it easy to reduce the fraction to the lowest terms in both the constructor and the read function. The read function throws an exception if the user enters a zero denominator. A second version of the fraction program demonstrates these variations. Please see fraction2 in the downloadable code at the bottom of the page.
void fraction::read()
{
cin >> numerator;
cin >> denominator;
if (denominator == 0)
throw "denominator = 0 error";
reduce();
}
|
fraction f;
try
{
f.read();
}
catch(char* error)
{
cerr << error << endl;
}
|
Together, fraction.h and fraction.cpp form a supplier. We need a client - code that uses the fraction class - to make a complete program. We begin with calc.cpp program from chapter 3. We need to make four modifications to the calc program to make it into a simple fraction calculator:
#include <fraction.h>double with fractioncin >> with read and replace cout << with print (the second replacement only applies to printing the numeric results and not to the prompts)add, sub, mult, and divswitch (choice)
{
case 'A':
case 'a':
cout << "Adding" << endl;
cout << "enter the first operand: ";
left.read();
cout << "enter the second operand: ";
right.read();
result = left.add(right);
result.print();
break;
.
.
.
The following programs are formatted with tab stops set at 8 spaces - the default for the gvim editor I used to write the code.
| View | Download |
|---|---|
| fraction.h | fraction.h |
| fraction.cpp | fraction.cpp |
| calc.cpp | calc.cpp |
| fraction2.h | fraction2.h |
| fraction2.cpp | fraction2.cpp |
| calc2.cpp | calc2.cpp |