A fraction comprises two integers, a numerator, and a denominator. Fractions can be added, subtracted, multiplied, and divided. 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)
The fraction class. The fraction class has one constructor (that serves as three), four arithmetic functions (add, sub, mult, and div), and two I/O functions (print and read).
A UML class diagram for the fraction class.
The C++ class specification is contained in fraction.h, which is available at the bottom of the page.
Class diagrams and specifications both show what functions the fraction class has but not how the functions are implemented - that is, they don't show what statements are in the bodies of the functions. Software developers turn to the program's requirements to determine how to implement the functions.
The fraction Class Requirements
We can write even small, simple programs in many ways. As the size and complexity increase, so does the number of ways we can write it. 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. So, sometimes when we write a program, we can choose just how we want to implement it. However, software developers often create programs for customers who may have additional requirements. The requirements may seem arbitrary and stifling our creativity - we may even feel micromanaged. But our code may need to integrate with existing code or a larger system. 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.
The fraction class shall have a single public constructor that serves as three distinct constructors:
A default constructor that makes an "empty" or "zero" fraction object. A default constructor call for the fraction class looks like this: fraction f;
A conversion constructor that converts an integer into a fraction. For example, the integer 5 is equivalent to the fraction 5/1. The conversion constructor will have one parameter that will initialize the fraction's numerator; the constructor will always set the denominator to 1. A fraction object is instantiated with this constructor as: fraction f(5);
A general constructor that initializes the fraction's numerator and denominator. Creating an object with this constructor looks like this: fraction f(2, 3);, which represents the fraction 2/3
None of the arithmetic operations shall change either fraction object
Each arithmetic operation shall create and return a new fraction object to represent the result of the operation
Each fraction object returned by an arithmetic operation shall be in lowest terms (1/2 rather than 2/4, improper fractions like 5/3 are okay)
For efficiency and to avoid duplicating code, reducing the fraction to the lowest terms shall be done by the constructor and not by the arithmetic functions
The output function shall display a fraction with the numerator and the denominator separated by a forward slash: 2/3 or 5/3
The input function shall prompt for and read the numerator, then prompt for and read the denominator
The Constructor
Following 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. We can have the constructor reduce the returned 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 fraction constructor. The function prototype, including the default arguments, is part of the class specification located in the fraction header file. We place the function definition in the fraction source code file. The initializer list initializes the member variables numerator and denominator before the function body executes; so, these variables are ready for use when the constructor calls the gcd function. The gcd function (included with the downloadable code at the bottom of the page) calculates the greatest common divisor of its arguments - the largest number that evenly divides both arguments. For example, gcd(10,15) is 5 and 10/15 = 2/3.
Designing The Arithmetic Functions
The four arithmetic operations are long-established and well-defined for fractions, so you undoubtedly learned how to do basic fraction arithmetic long ago. The next figure presents the formulas for fraction arithmetic using a familiar notation that is quite 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 translated much more complex formulas in previous chapters, but this time 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.
In the formulas, a, b, c, and d represent the numerators and denominators of two fraction operands. a and b represent the numerator and denominator of the left hand fraction, and c and d represent the numerator and denominator of the right hand fraction. When translated into C++ functions, each arithmetic function must create a new instance of the fraction class to store the result of the operation. So, the part of the formulas on the right side of the equals sign (not quite the same as the C++ assignment operator) represents the steps or operations needed to calculate the numerator and the denominator of the new fraction object.
The C++ code is written in a way to help ease the transition between the fraction formulas and the fraction class member functions, so it's not quite in the final form.
Implementing The Arithmetic Functions
Depending on its complexity, there are generally many ways to write a function. The fraction add and mult functions are used to illustrate two slightly different ways. All four arithmetic functions are contained in fraction.cpp at the bottom of the page.
The two remaining fraction functions are small and relatively simple.
Throwing and catching an exception.
Although there was no requirement to check the denominator to ensure that it is non-zero, it is an error for a simple fraction to have a zero denominator. We can include a test for this error, and the program can throw an exception if it is detected. C++ is quite flexible, and programmers can use any data type as an exception. In this example, the program throws a C-string. Version 2 of the program also demonstrates this option.
Throwing an exception
Catching an exception
Fraction Calculator: A Fraction Client
Together, fraction.h and fraction.cpp form a supplier. To make a complete program, we need a client - code that uses the fraction class. 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>
Replace double with fraction
Replace cin >> with read and replace cout << with print (the second replacement only applies to printing the numeric results and not to the prompts)
Replace the arithmetic operators +, -, *, and / with the corresponding fraction functions add, sub, mult, and div
Downloadable Code
Tab stops are set at 8 spaces - the default for the gvim editor I used to write the code.