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)
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.
Both class diagrams and specifications show what functions the fraction class has but not how the functions the class implements them - 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 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.
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 initializing 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, but 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. 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 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 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.
In the formulas, a, b, c, and d represent the numerators and denominators of two fraction objects or 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 example uses the fractionadd 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.
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. 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>
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
The following programs are formatted with tab stops set at 8 spaces - the default for the gvim editor I used to write the code.