Suppose we wanted to write a function to compare two Fraction objects. An intuitive function call might be the following:
Fraction f1(1,2);
Fraction f2(2,4);
if ( Equals(f1, f2) ) // compare two fraction objects
cout << "The fractions are equal";
To achieve this call, we would need to build a function with the following
prototype:
bool Equals(Fraction x, Fraction y);Notice that this would not be a member function of class Fraction. The sample call did not use the dot-operator. Here's a possible definition of the function:
bool Equals(Fraction x, Fraction y)
{
if (x.GetNumerator() * y.GetDenominator() == y.GetNumerator() * x.GetDenominator() )
return true;
else
return false;
}
Note that this algorithm results from finding a common denominator
(multiply the two denominators together), adjusting the numerators, then
comparing the numerators (integers). Also notice that this function calls
upon the accessor functions from the Fraction class --
GetNumerator() and GetDenominator() -- because it is not
a member function. It it outside the Fraction class.
Because this function is performing an operation on two Fraction objects, it is probably being written by the author of the Fraction class. As such, it would be easier (and more efficient in run time) to write it this way:
bool Equals(Fraction x, Fraction y)
{
if (x.numerator * y.denominator == y.numerator * x.denominator )
return true;
else
return false;
}
However, because this is an outside function, it does not have access to
private data, so this would be illegal, as is.
if ( f1.Equals(f2) )
cout << "The fractions are equal";
This would be a member function. One object is the calling object.
The other object is passed as a parameter. This would be the
corresponding definition. (Compare it to the friend version).
bool Fraction::Equals(Fraction f)
{
if (numerator * f.denominator == f.numerator * denominator )
return true;
else
return false;
}
f3 = f1.Add(f2); // call to member function version f3 = Add(f1, f2); // call to non-member function version
Fraction Add(Fraction f); // member friend Fraction Add(Fraction f1, Fraction f2); // friend
Recall how some of the built-in types allow automatic type conversions, like this:
int x = 5; double y = 4.5, z = 1.2; y = x; // legal, via automatic conversion z = x + y; // legal, using automatic conversion
Automatic type conversions can also be set up for classes, via a conversion constructor
Fraction(int n); // can be used to convert int to Fraction // suppose it initializes to n/1
Fraction f1, f2; // create simple fraction objects f1 = Fraction(4); // explicit call to constructor. Fraction 4/1 is // created and assigned to f1 f2 = 10; // implicit call to conversion constructor // equivalent to: f2 = Fraction(10); f1 = Add(f2, 5); // uses conversion constructor to turn 5 into 5/1
Fraction(int n, int d = 1);
explicit Fraction(double d); // will NOT be used for automatic conversions
Remember that const can be used in a variety of places. And everywhere it is applied in code, it:
friend Fraction Add(Fraction f1, Fraction f2);We definitely don't want to change the original fractions that were sent in. But to save overhead, we could use const reference parameters:
friend Fraction Add(const Fraction& f1, const Fraction& f2);Since the parameters are const, R-values can be sent in (just like with pass-by-value).
void Func(int x) const; // const member function
Yadda y; // object y
y.Func(5); // this function will not change the data of y
const int SIZE = 10; const double PI = 3.1415;
const Fraction ZERO; // this fraction is fixed at 0/1 const Fraction FIXED(3,4); // this fraction is fixed at 3/4
FIXED.Show(); // calling const functions cout << FIXED.Evaluate(); int n = ZERO.GetNumerator(); int d = ZERO.GetDenominator();The following calls would be illegal and would cause compiler errors:
FIXED.SetValue(5,7); ZERO.Input();Note that in the original version of Fraction (with no const member functions), ALL of these calls would result in compiler errors, even if the function itself didn't change anything (like Show).
const int SIZE = 10;
class Thing
{
public:
Thing(); // constructor -- intialize member data in here
// blah blah blah
private:
int x; // just declare here
int y = 0; // this would be ILLEGAL! cannot initialize here
const int Z = 10; // would also be ILLEGAL
};
Thing::Thing()
{
x = 0;
y = 0;
Z = 10;
}
returnType functionName(parameterList) : initialiation_list
{
// function body
}
Declaration:
The destructor looks like the default constructor (constructor with
no parameters), but with a ~ in front. Destructors cannot have parameters,
so there can only be one destructor for a class. Example: The
destructor for the Fraction class would be:
~Fraction();
Like the constructor, this function is called automatically (not explicitly)
The destructor's typical job is to do any clean-up tasks (usually involving memory allocation) that are needed, before an object is deallocated. However, like a constructor, a destructor is a function and can do other things, if desired.
Compile and run this example to see when
constructors and destructors are invoked.