CSC 306 Introduction to Programming with C++

Friend Functions and Function Overloading for Classes in C++


Objectives

Important Definitions


This assignment may be completed individually or with a partner.

Friend Functions in C++

Recall that functions that are not members of a class cannot access the private member variables of that class. This restriction does provide protection from unintended attempts to either access or change these variable values, but there are occasions when it would be desireable to have certain functions be able to so.

In ANSI standard C++, a friend function of a class is not a member function of the class (hence does not need the scope resolution operator). It is, however, given the special privilege of having access to the private member variables of the class, just as the member functions do. A friend function must be declared in the class by inserting the keyword friend before the function return type in the class declaration.

Although Visual C++ is not strictly standard to ANSI and hence there have been problems with getting friend functions to work, Dev-C++ should be fine. To see these ideas in a working program and to ensure that your Dev-C++ compiler can handle friend functions, you can download and try compiling and running MoneyReader.cpp and infile.txt. These files are from Chapter 8, Display 8.8 of your text.

Operator Overloading

Operators such as +, -, *, /, %, ==, <, >, <=, >= are really C++ functions that use a special syntax for listing the function arguments. Rather than using a prefix syntax in which the function is before the arguments (this is what you have been defining so far), these operators use the infix syntax, where the function name is between the arguments.

An example will help highlight the utility of overloading these operators. Consider fractions. These rational numbers can be represented as the quotient of 2 integers, so to represent data of this type, you may define a class called Fraction that has in it a member variable for the numerator and another for the denominator. The constructor to make new Fraction variables can be defined so that you can create two variables for the values of 1/2 and 3/4 using the following statements:

Fraction x(1, 2); //Represents the fraction 1/2 
Fraction y(3, 4); //Represents the fraction 3/4 
So far, the only way we have seen to add these two numbers is to define a function such as add( x, y ) that take as arguments two Fraction variables and adds them together. This works, but is awkward. A more familiar way to add these two fractions is to use the notation x+y, which is possible with operator overloading!

A predefined operator such as + can be overloaded to add two fractions by using the reserved word operator in the function definition. The definition of addition operator can look like the following, which is a friend function of the Fraction class and returns the result of the addition, which is also of type Fraction:

friend Fraction operator +(Fraction x, Fraction y) {
  //Create the function definition here as usual.
}
Similarly, the predefined operator such as "==" can also be overloaded to test the equality of two fractions:

// within the Fraction class declaration
friend bool operator ==(Fraction x, Fraction y) 

  // some code here, such as the definition for main()

// define how the == operator works here.
bool operator ==(Fraction x, Fraction y) {
  //Create the function definition here as usual.
}
The << and >> operators can also be overloaded to facilitate easier output and input of Fraction data types:

// within the Fraction class declaration 
friend ostream& operator <<(ostream& outs, Fraction x) 

  // some code here, such as the definition for main()

// define how the << operator works here.
ostream& operator <<(ostream& outs, Fraction x) {
  //Create the function definition here as usual.
}
By doing this, you can use cout to write x, which is an object of type Fraction and originally could not be output this way, to the screen as you would any predefined variable types.

Statement Resulting Output
cout << "You have computed the fraction " << x << "." << endl;
  
You have computed the fraction 1/2.
  

Abstract Data Types (ADT)

In the class text book, an abstract data type (ADT) is a data type where "the programmers who use the type do not have access to the details of how the values and operations are implemented." A data structure defined by an ADT can only be accessed using the member functions and friend functions that are defined already. The entire set of these operations is called the interface and is exported (provided) by the class. An Abstract Data Type (ADT) is characterized by the following properties:
  1. It exports a newly defined type.

  2. It exports a newly defined set of operations on the new type (its interface). (In a later chapter, we will learn to place the interface and the implementation of the ADT in separate files.)

  3. Operations of the interface are the one and only access mechanism to the type's data structure.

  4. Comments given by preconditions define the application domain of the newly defined type.
In other words, programmers know which functions to call and what each one does because of well-defined pre- and post-conditions in the comments for each function, but not how each one works. In fact, the programmer does not care and treats it as a correct black box. Not all C++ classes are written as ADTs, but it is a highly desirable feature for good object-oriented programming.


Lab Specifics

This lab is to be done either individually or with another partner.

Your task in this lab is to write a rational number class as an ADT in a source code file called YourLastName(s)_306L7.cpp (depending on whether you do it alone or with a partner). Rational numbers can be expressed as a fraction p/q where p and q are each whole numbers and q does not equal 0. The member variables that represent the numerator and denominator should be integers and private to the class.

Include member functions for this class that does at least the following:

  1. Three different public constructor functions:
    1. The default constructor should set both numerator and denominator to 1.
    2. A constructor that takes a single integer input for the numerator and sets the denominator to 1. This is to represent whole numbers.
    3. A constructor that takes as input two integers and sets the numerator and denominator to the appropriate values. This constructor should perform some error checking, such as:
      • The denominator of a rational number can never be zero. If setting the denominator to zero is ever attempted by a user, you should call a private error function to handle the problem.
      • Any negative rational number should have the negative sign with the numerator only. If the user puts a negative number into the denominator, you should "fix" the stored fraction so that only the numerator is ever negative.

  2. Provide a private member error function that is called when the denominator fraction is ever set to 0. This function should both print an appropriate error message on the screen and set both the numerator and the denominator of the current fraction to zero (yes, this is illegal, but you can catch errors this way).

  3. Provide two public member accessor functions for the numerator and demoninator. Follow the convention that the names of accessor functions begin with "get".

  4. Provide two public member mutator functions that can change the value of the numerator and denominator of the current fraction respectively. Follow the convention that the names of mutator functions begin with "set".

  5. Provide a public member function that returns a double that is the result of converting the current fraction to a decimal number.
  6. Overload the addition "+" operator that can be called to add two fractions.

  7. Overload the subtraction "-" operator that can be called to subtract two fractions.

  8. Overload the multiplication "*" operator that can be called to multiply two fractions.

  9. Overload the division "/" operator that can be called to divide two fractions.

  10. Overload the unary negative "-" operator that returns the negative of the current fraction without changing its value. Using this operator on a fraction of one-half will return a negative one-half.

  11. Overload the >> operator so that takes an istream argument and gets a rational number written in the form p/q from either the keyboard or a file depending upon the istream parameter. This function should change the value of the current fraction appropriately.

  12. Overload the << operator so that takes an ostream argument and outputs a rational number as a fraction. For example, suppose that you have a variable rational that is an object of the class of rational numbers you designed, and the numerator is a 2 and the demoninator is 3. The following statement on the left will output the string on the right to the console:

    Statement Resulting Output
    cout << "Rational is: " << rational << endl;
      
    Rational is 2/3
      

  13. Overload the <, <=, >, and >= operators by creating four functions that return a Boolean value depending upon the relative size of two fractions.
    Hint: You might want to use the member function that converts the rational number into a decimal here.

  14. Overload the "==" operator by creating a function that returns a Boolean depending upon the equality of two fractions.
    Warning: Because 1/2 = 2/4, this function requires some care.

  15. Provide a main program that can be used to thoroughly test your class.

Here are some relationships that you may find useful:


Be sure to:

When you have completed your program and have it working to your satisfaction, drop the source code and your Microsoft Word Lab write-up YourLastName(s)_306L7.doc into the CSC306_L07 dropbox on the Academic server.


Back to Introduction to Computer Programming with C++ Homepage