Перегрузка операторов C++, часть первая - унарные операторы

newbie_

Опубликован:  2020-02-01T09:03:18.360550Z
Отредактирован:  2020-02-01T09:23:26.360664Z
400

Перегрузка операторов C++ - достаточно обширная тема и предполагает некоторое количество тонкостей и деталей, с которыми стоит планомерно и последовательно разобраться каждому начинающему осваивать этот без всяких сомнений великолепный диалект. Мне в руки попала замечательная книга Роберта Лафоре, и начиная с этого выпуска блога я собираюсь разобрать ключевые примеры из этой книги, посвященные перегрузке операторов. Начну я с унарных операторов и детально разберу предложенный в книге пример.

Стоит сказать пару слов о перегрузке операторов. Дело в том, что C++ является объектно ориентированным языком, и поэтому написанные на нём программы могут иметь в своём составе как объекты базовых типов - переменные типов int, float, double etc., так и объекты - экземпляры определённых в программе классов - частные решения конкретного программиста. Поведение операторов и производимые ими действия по отношению к переменным базовых типов определено исходным кодом компилятора C++ и наследуется от языка C. А вот поведение операторов по отношению к экземплярам того или иного определённого в программе класса в очень многих возможных случаях необходимо определить непосредственно в коде конкретного класса. Такие определения и принято называть перегрузкой операторов. В этом обзоре рассмотрим перегрузку унарного оператора ++.

Как известно, унарные операторы взаимодействуют только с одним объектом, в качестве примера унарного оператора обычно на ум приходят операторы ++ - increment, или -- - decrement. Продемонстрировать перегрузку такого оператора проще всего на примере элементарного счётчика, который я сейчас и воспроизведу. Для этой демонстрации мне понадобится десктоп, я буду использовать свой десктоп с Debian buster на борту, Geany для набора кода, компилятор g++ для сборки исполняемого файла программы и терминал для её тестирования. Итак, запускаю Geany и создаю файл counter.cpp.

Для задуманной программы мне достаточно будет одного модуля стандартной библиотеки.

// counter.cpp

#include <iostream>

Создаю класс, на базе которого и будет продемонстрирована перегрузка унарного оператора ++.

class Counter
{
  private:

  public:

};

В приватном блоке этого класса определяю одну единственную переменную - count, на основе которой и будет реализован счётчик и его поведение.

    unsigned int count;

В публичном блоке класса Counter необходимо определить конструктор без аргументов, который определит состояние вновь созданных экземпляров этого класса.

    Counter() : count(0) {}

Таким образом каждый созданный выражением Counter name; счётчик в начальном состоянии будет иметь значение 0 в своём члене count.

Для реализации задуманного классу Counter необходим ещё один конструктор, который будет принимать один аргумент и таким образом создавать экземпляр с заданным значением члена count.

    Counter(unsigned int n) : count(n) {}

Мне необходимо будет вывести на экран состояние счётчика, для этого предназначена функция член get_count, которая вернёт значение count конкретного экземпляра.

    unsigned int get_count() const { return count; }

Для перегрузки операторов в C++ определены соответствующие механизмы, а именно, перегрузка оператора ++ производится посредством определения соответствующих функций членов класса. Как известно из теории, оператор ++ имеет две возможных нотации:

  • префикс нотация - ++i;
  • постфикс нотация - i++.

Поведение оператора в двух нотациях будет отличаться в составных выражениях типа:

b = ++i;
// или
b = i++;

Это значит, что для нашего подопытного класса Counter поведение оператора ++ будет определяться двумя функциями - для каждой нотации оператора своя функция член. Начнём с префикс нотации. В C++ определён механизм перегрузки операторов с помощью функции члена с именем operator и обозначением перегружаемого оператора. В частности для префикс нотации оператора ++ в классе Counter можно определить следующую функцию член.

    Counter operator ++ () { return Counter(++count); }

Как видно, функция возвращает значение типа Counter - экземпляр этого же класса, который в теле функции создаётся с помощью второго конструктора класса - конструктора с одним аргументом.

Для постфикс нотации оператора ++ можно определить следующую функцию:

    Counter operator ++ (int) { return Counter(count++); }

Здесь следует обратить особое внимание на int в заголовке функции в скобках, где обычно задаются аргументы функции. Дело в том, что в данном случае int не является аргументом как таковым, и он не обозначает одноимённый тип int. Это просто сигнал компилятору, что данная функция член определяет версию оператора ++ с постфикс нотацией.

Поскольку в рамках этой демонстрации важно продемонстрировать перегрузку конкретного унарного оператора, дальнейшую разработку класса Counter я пока опущу и, возможно, продолжу в следующих выпусках блога. А чтобы протестировать полученный класс, мне остаётся определить в программе функцию main.

int main()
{
  Counter c1, c2;

  // Тестируем первый конструктор класса (без аргументов)
  std::cout << "c1=" << c1.get_count() << std::endl; // Ожидаем - 0
  std::cout << "c2=" << c2.get_count() << std::endl; // Ожидаем - 0

  // Тестируем префикс нотацию оператора ++
  ++с1; // первый инкремент
  // То же самое в составе выражения,
  // в том числе тестируем второй конструктор класса (с одним аргументом)
  c2 = ++c1; // второй инкремент
  std::cout << "c1=" << c1.get_count() << std::endl; // Ожидаем - 2
  std::cout << "c2=" << c2.get_count() << std::endl; // Ожидаем - 2

  // Тестируем постфикc нотацию оператора ++
  c2 = c1++; // третий инкремент
  std::cout << "c1=" << c1.get_count() << std::endl; // Ожидаем - 3
  std::cout << "c2=" << c2.get_count() << std::endl; // Ожидаем - 2

  return 0;
}

Код программы целиком:

// counter.cpp

#include <iostream>

class Counter
{
  private:
    unsigned int count;
  public:
    Counter() : count(0) {}
    Counter(unsigned int n) : count(n) {}
    unsigned int get_count() const { return count; }
    Counter operator ++ () { return Counter(++count); }
    Counter operator ++ (int) { return Counter(count++); }
};

int main()
{
  Counter c1, c2;
  std::cout << "c1=" << c1.get_count() << std::endl;
  std::cout << "c2=" << c2.get_count() << std::endl;

  ++c1;
  c2 = ++c1;
  std::cout << "c1=" << c1.get_count() << std::endl;
  std::cout << "c2=" << c2.get_count() << std::endl;

  c2 = c1++;
  std::cout << "c1=" << c1.get_count() << std::endl;
  std::cout << "c2=" << c2.get_count() << std::endl;

  return 0;
}

Компилирую программу и пробую запустить полученный исполняемый файл.

g++ -o exe counter.cpp
./exe

OFm6TKxiRq.png

Как видно на снимке экрана, компиляция завершилась успешно, без ошибок и предупреждений, и в результате исполнения в терминале полученная программа выхлопнула следующие значения:

c1=0
c2=0
c1=2
c2=2
c1=3
c2=2

Сравниваю их с ожидаемыми значениями - всё работает как и задумано. В общем-то цель этой демонстрации полностью достигнута, хотя определение класса Counter в рассмотренной программе пока не может считаться завершенным, но об этом я расскажу в одном из следующих выпусков этого блога.

Комментарии: