C++. “Friendly” operator functions: differences, implementation




“Friendly” operator functions: differences, implementation. Overload of operators +, , *, / with the help of “friendly” operator functions

Before studying this topic, it is recommended to familiarize yourself with the following topic:


Contents


Search other websites:

1. Differences in parameters between the operator function implemented inside the class and the “friendly” operator function

There are two ways to overload any operator:

  • using an operator function that is implemented inside the class;
  • using an operator function that is implemented as a friend to the class.

Between the ways of implementing operator functions, there is a difference in the number of parameters that an operator function receives:

  • for unary operators, the operator function within the class does not receive parameters. And the class-friendly operator function receives one parameter;
  • for binary operators, an operator function within a class receives one parameter. But “friendly” function receives two parameters. In this case, the first parameter of the “friendly” function is the left operand, and the second parameter is the right operand.

These differences are due to the fact that the “friendly” operator function does not receive an implicit pointer this. Therefore, it is necessary to explicitly set parameters in it.

 

2. The general form of an operator function that is implemented outside the class (the “friendly” function to the class)

The operator function can be implemented outside the class. If an operator function overloads a unary operator, then it contains one parameter. If the operator function overloads the binary operator, then it receives two parameters.
The general form of an operator function that is class-friendly is as follows

return_type operator#(arguments_list)
{
    // operations
    // ...
}

here

  • return_type – the type of value that the operator function returns;
  • operator# – a keyword that defines an operator function in a class. The # symbol identifies the C operator that is overloaded. For example, if operator + is overloaded, then you need to specify operator+;
  • argument_list – list of parameters that the operator function receives. If the binary operator is overloaded in the “friendly” function, the argument_list contains two arguments. If the unary operator is overloaded, argument_list contains one argument.

In a class, this function must be declared “friendly” with the friend keyword.






 

3. An example of overloading a binary operator ‘-‘ in the class for which the “friendly” operator function is implemented

In this example, you can develop your own operator functions that are “friendly” to a given class.
The Complex class, which implements a complex number, is given. The class declares internal variables, constructors, access methods, and the friend function operator-().

The “friendly” operator-() function, implemented outside the class, subtracts complex numbers.

// class Complex
class Complex
{
private:
    float real; // real part
    float imag; // imaginary part

public:
    // constructors
    Complex(void)
    {
        real = imag = 0;
    }

    Complex(float _real, float _imag)
    {
        real = _real;
        imag = _imag;
    }

    // access methods
    float GetR(void) { return real; }
    float GetI(void) { return imag; }

    void SetRI(float _real, float _imag)
    {
        real = _real;
        imag = _imag;
    }

    // declaration of a friendly operator function to the class
    friend Complex operator-(Complex c1, Complex c2);
};

// "friendly" to the class Complex operator function,
// is implemented outside the class,
// it performs the subtraction of complex numbers
Complex operator-(Complex c1, Complex c2)
{
    Complex c; // create an object of class Complex

    // subtraction of complex numbers
    c.real = c1.real - c2.real;
    c.imag = c1.imag - c2.imag;

    return c;
}

Using the Complex class in another method

// use of "friendly" operator function
Complex c1(5,6);
Complex c2(3,-2);
Complex c3; // result
float a, b;

// checking
a = c1.GetR(); // a = 5
b = c1.GetI(); // b = 6

// invoke the class-friendly operator function
c3 = c1 - c2;

// result
a = c3.GetR(); // a = 5-3 = 2
b = c3.GetI(); // b = 6-(-2) = 8

As you can see from the example above, the friendly function operator-() receives two parameters. As you can see from the example above, the friendly function operator-() receives two parameters. The first parameter corresponds to the left operand in the binary subtraction operator ‘–’. The second parameter corresponds to the right operand.

 

4. An example of operator overloading ‘/‘. An implementation of a class containing a dynamic array of real numbers. The operator function is implemented as a “friendly” function

The example demonstrates how to properly implement the overload of “friendly” operator functions for classes in which memory is dynamically allocated. The class contains a dynamic array of float items.

The ArrayFloat class is declared, which implements a dynamic array of real numbers. The class provides the following elements:

  • internal variable size – the size of the array;
  • pointer A to type float. This is an array, the memory for which is dynamically allocated;
  • constructor of class ArrayFloat() without parameters;
  • constructor with two parameters ArrayFloat(int, float *);
  • copy constructor ArrayFloat(const ArrayFloat &). This constructor is necessary to avoid the disadvantages of bitwise copying. Without the copy constructor, the work of class objects will have errors;
  • destructor ~ArrayFloat().The memory allocated for the dynamic array A is released in the destructor;
  • methods GetSize(), SetSize() for accessing the size variable;
  • methods GetAi(), SetAi() for accessing a specific item of an array with a specified index;
  • the FormArray() method, which forms the elements of an array by the formula;
  • the Print() method, which outputs an array;
  • operator function operator=(const ArrayFloat&). This function overloads the assignment operator =, which implements the copying of objects. The function is necessary to avoid the disadvantages of bit-wise copying in the case of the assignment of objects obj1=obj2;
  • “friendly” to the class ArrayFloat operator function operator/(const ArrayFloat &, const ArrayFloat &), which overloads the division operator / for the class. The function implements element-by-element division of arrays. If the number of array elements does not match, then the resulting array is set to the size of the smallest of the two arrays.

The class implementation for an application of the Console Application type is as follows:

#include <iostream>
using namespace std;

// class - array of type float
class ArrayFloat
{
private:
  int size;
  float * A; // dynamic size of the array

public:
  // class constructors
  // constructor without parameters
  ArrayFloat()
  {
    size = 0;
    A = nullptr;
  }

  // constructor with two parameters
  ArrayFloat(int nsize, float * nA)
  {
    size = nsize;
    A = new float[size];

    for (int i = 0; i < nsize; i++)
      A[i] = nA[i];
  }

  // copy constructor, 
  // needed to avoid the disadvantages of bitwise copying
  ArrayFloat(const ArrayFloat& nA)
  {
    if (size > 0)
    {
      delete[] A; // release previously allocated memory
    }

    // allocate memory for A
    A = new float[nA.size];

    // copy *this <= nA
    size = nA.size;
    for (int i = 0; i < size; i++)
      A[i] = nA.A[i];
  }

  // destructor
  ~ArrayFloat()
  {
    // release memory allocated for array A
    if (size > 0)
      delete[] A;
  }

  // access methods
  int GetSize(void) { return size; }

  void SetSize(int nsize)
  {
    if (size > 0)
      delete[] A;

    size = nsize;
    A = new float[size]; // allocate a new piece of memory

    // fill the array with zeros
    for (int i = 0; i < size; i++)
      A[i] = 0.0f;
  }

  // read the value of index
  float GetAi(int index)
  {
    if ((index >= 0) && (index < size))
      return A[index];
    else
      return 0;
  }

  // set a new value
  void SetAi(int index, float value)
  {
    if ((index >= 0) && (index < size))
      A[index] = value;
  }

  // array forming method
  void FormArray()
  {
    for (int i = 0; i < size; i++)
      A[i] = (float)i;
  }

  // метод, отображающий массив 
  void Print(const char* objName)
  {
    cout << "Object: " << objName << endl;
    for (int i = 0; i < size; i++)
      cout << A[i] << " ";
    cout << endl << endl;
  }

  // Operators overloading
  // Overloading the copy operator operator=(const ArrayFloat &)
  ArrayFloat operator=(const ArrayFloat& nA)
  {
    if (size > 0)
      delete[] A; // release previously allocated memory

    // allocate memory for A again
    A = new float[nA.size];

    // copy *this <= nA
    size = nA.size;
    for (int i = 0; i < size; i++)
      A[i] = nA.A[i];
    return *this;
  }

  // "friendly" operator function '/',
  // declaration only, implementation outside the class
  friend ArrayFloat operator/(const ArrayFloat& A1, const ArrayFloat& A2);
};

// "friendly" operator function,
// function receives two parameters A1, A2
ArrayFloat operator/(const ArrayFloat& A1, const ArrayFloat& A2)
{
  // create an object of class ArrayFloat
  ArrayFloat A; // constructor without parameters is called
  int n;

  // take the minimum value from the size of two arrays A1, A2
  n = A1.size;
  if (n > A2.size) n = A2.size;

  // set the new size of the array A, the memory reallocation
  A.SetSize(n);

  // element by element division
  for (int i = 0; i < n; i++)
  {
    if (A2.A[i] != 0) // bypass the division by 0
      A.A[i] = A1.A[i] / A2.A[i];
    else
      A.A[i] = 0.0f;
  }

  return A; // return a new object
}

void main()
{
  // Demonstration of the ArrayFloat class
  ArrayFloat AF1; // constructor without parameters
  AF1.FormArray();

  int size; // additional variables
  float x;

  size = AF1.GetSize(); // size = 0
  x = AF1.GetAi(3); // x = 0
  cout << "size = " << size << endl;
  cout << "x = " << x << endl;

  ArrayFloat AF2 = AF1; // invoke the copy constructor
  cout << AF2.GetAi(3) << endl;

  ArrayFloat AF3;
  AF3.SetSize(10);
  AF3.FormArray();
  AF2 = AF3; // invoke the copy operator operator=(const ArrayFloat&)
  cout << "AF2[3] = " << AF2.GetAi(3) << endl;

  ArrayFloat AF4 = AF2; // invoke the copy constructor
  cout << "AF4[5] = " << AF2.GetAi(5) << endl;

  // 'chained' copy operator
  AF1 = AF2 = AF4 = AF3; // invoke the copy operator
  cout << "AF1[8] = " << AF1.GetAi(8) << endl;

  // Demonstration of the "friendly" operator function
  AF3 = AF2 / AF1; // invoke the friendly function operator/()
  cout << endl << endl;
  AF3.Print("AF3");

  // "Friendly" function works as a "chain"
  ArrayFloat AF5;
  AF5 = AF1 / AF2 / AF3;
  cout << endl;
  AF5.Print("AF5");

  //
  float F[] = { 1.1, 2.3, 4.5 };
  ArrayFloat AF6(3, F); // constructor with 2 parameters
  AF6.Print("AF6");
}

The result of the program:

size = 0
x = 0
0
AF2[3] = 3
AF4[5] = 5
AF1[8] = 8


Object: AF3
0 1 1 1 1 1 1 1 1 1


Object: AF5
0 1 1 1 1 1 1 1 1 1

Object: AF6
1.1 2.3 4.5

 


Related topics