2.3. Перегрузка операций

Операции могут быть перегружены с помощью составных и дружественных функций. Имя функции, соответствующей операции, получается добавлением символа операции к слову operator. Например,

      class Bits

            {

                  char *b;

                  int size;

            public:

                  Bits operator+(const Bits&);       // сложение

                  Bits operator-(const Bits&);       // вычитание

                  Bits operator-();                  // унарный минус

                  Friend Bits& operator^(const Bits&, const Bits&); //XOR

            };

Если операция определяется с помощью составной функции, то эта функция имеет на один аргумент меньше, чем в том случае, когда операция определяется с помощью дружественной функции. Для составной функции первый аргумент предполагается равным *this. Например, для класса строки операцию сравнения относительно лексикографического (алфавитного) порядка можно определить с помощью приведённой ниже составной функции:

      #include <string.h>

      class String

            {

                  char *s;

                  int len;

            public:

                  int operator<(String st)

                        {

                        return strcmp(s,st,s)<0;

                        }

            };

То же самое с помощью дружественной функции определяется следующим образом

      #include <string.h>

      class String

            {

                  char *s;

                  int len;

            public:    

                  friend int operator<(String, String);

            };

      operator<(String str1, String str2)

            {

            return strcmp(str1.s, str2.s)<0;

            }

Перегрузка операций позволяет определить для классов значения любых операций, исключая “.”, ”::”, ”.*”, ”?:”, sizeof. Отсюда вытекает, что разрешено определять операции для класса, символы которых равны:

new   delete

+     —         *    /    %    ^    &

|     ~         !    =    <    >    +=

-=    *=        /=   %=   ^=   &=   |=

<<    >>        <<=  >>=  ==   !=   <=

>=    &&        ||   ++   —   ()   []  

->    ->*

Здесь стандартная операция ->* обозначает косвенное обращение к элементу класса (элементу структуры) через указатель на объект и указатель на этот элемент,

например,

      class C

            {

            int *d;

            friend int f(C *p);

            };

            int f(C *p) {return p->*d;}

Аналогично, операция .* обозначает прямое обращение к элементу класса по имени объекта и указателю на элемент.

Приведем пример перегрузки операции индексации [], обычно используемую для возвращения ссылки, что позволяет применять ее в операции присваивания с обеих сторон.

Пример. Пусть класс определен как строка символов. Определим операцию индексации, позволяющую читать и записывать i-й символ строки:

#include <string.h>

#include <conio.h>

#include <iostream.h>

// Класс строка

class String

      {

      // Закрытые элементы

            char *s;    // Сама строка

            int len;    // Её длина

      public:     // Общедоступные элементы

            // Перегрузка операции []

            char& operator[](int pos)

                  {

                  return s[pos];

                  }

            // Инициализация строки

            void init(char *s)

                  {

                  len=strlen(s);          // Определение длины

                  String::s=new char[len+1]; // Выделение памяти под строку

                  strcpy(String::s, s);   // Присваивание

                  }

            // Вывод строки на экран

            void show()

                  {

                  cout<<s<<‘n';

                  }

      };

void main()

      {

      clrscr();   // Очистка экрана

      String a;         // Создаём строку

      a.init("abc");    // Инициализируем её

      cout<<"Начальное содержимое строки: ";

      a.show();      // Выводим строку на экран

      a[1]=’c';

      cout<<"Содержимое строки после операции a[1]=’c': ";

      a.show();         // Выводим строку на экран

      a[0]=’b';

      cout<<"Содержимое строки после операции a[0]=’b': ";

      a.show();      // Выводим строку на экран

      cout<<"Содержимое строки после операции a[0]=a[2]: ";

      a[0]=a[2];

      a.show();         // Выводим строку на экран

      getch();    // Ожидание нажатия клавиши

      }

Результаты работы программы

Начальное содержимое строки: abc

Содержимое строки после операции a[1]=’c': acc

Содержимое строки после операции a[0]=’b': bcc

Содержимое строки после операции a[0]=a[2]: ccc

Вызов операции возвращает адрес a[i]. Присваивание a[i]=x записывает в этот адрес x.

Операции ++ и могут быть как префиксными и записываться ++x или x, так и постфиксными – x++, x. Если определять префиксные операции через составные функции, то следует указать обычным образом тип возвращаемого значения. Например,

      class A

            {

            A& operator++();

            A& operator—();

            };

Префиксные операции ++ и можно определить как дружественные функции от одной переменной, представляющие собой ссылку на объект данного класса. Например,

      class A  

            {

            friend A& operator++(A&);

            friend A& operator—(A&);

            };

Постфиксные операции ++ и определяются с помощью функций, имеющих дополнительный аргумент типа int, который на самом деле не используется. Например:

      class A

            {

                  int x;

            public:

                  void operator++() { x=x+2;}

                  void operator++(int) { x=x+1;}

            };

      void main()

            {

            A b;

            b++;  // b.x увеличили на 1

            ++b;  // b.x увеличили на 2

            }