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

newbie_

Опубликован:  2020-02-14T08:51:27.259766Z
2700

Продолжаем изучать C++ и его возможности. В этой демонстрации я покажу перегрузку оператора [] - доступ по индексу. С помощью такого оператора осуществляется доступ к элементам массива по индексу, в случае разработки описывающего коллекцию данных класса может возникнуть необходимость перегрузить этот оператор, как это делается, я продемонстрирую на учебном примере из книги Роберта Лафоре. Для справки: другие примеры перегрузки операторов можно найти в этом блоге по тегу operator_overloading.

Прежде чем приступить к разработке класса, содержащего функцию-член operator [], стоит детально рассмотреть один интересный пример.

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

x = getresult();

Обязательным условием в этом случае является однотипность переменной x и возвращаемого функцией getresult значения. Вызов функции в данном выражении стоит справа от оператора =.

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

setresult() = 109;

Давайте рассмотрим такой пример. Создаю в текстовом редакторе файл retref.cpp.

lFkJ4PfvlT.png

В этот файл пишу следующий код.

// retref.cpp

#include <iostream>

// Создаём глобальную переменную x
int x;
// Декларируем функцию setx, которая возвращает
// ссылку на переменную типа int
int & setx();

int main()
{
  // Выражение присваивания значения,
  // в котором вызов функции стоит слева от оператора =
  setx() = 92;
  // Выводим на экран значение переменной x
  // в заданной форме.
  std::cout << "x = " << x << std::endl;
  return 0;
}

int & setx()
{
  // Возвращаем ссылку на глобальную переменную x
  return x;
}

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

g++ -o exe -Wall retref.cpp
./exe

XRZhtXeySy.png

Как видно на снимке экрана выше, в итоге выполненных в программе действий в глобальной переменной x хранится значение 92, в полном соответствии с выражением setx() = 92;. Здесь следует обратить внимание и всегда иметь ввиду, что функция setx возвращает ссылку именно на глобальную переменную, и никак иначе. Это свойство функции C++ и будет использовано при перегрузке оператора [].

Создам ещё один файл - sarray.cpp, в этом файле будет храниться код целевого примера перегрузки оператора [].

sJEXiotvk1.png

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

Итак, для задуманной программы мне потребуется два заголовочных файла.

// sarray.cpp

#include <iostream>
#include <cstdlib>    // для использования функции exit

Задавать размер массива будем с помощью целой константы LIMIT, объявляем её.

const int LIMIT = 7;

Целевой для этого примера класс SArray будет иметь следующую декларацию.

class SArray
{
private:
  int arr[LIMIT];
public:
  // Перегрузка оператора []
  int & operator [] (int n);
};

Здесь следует обратить внимание, что массив arr в пространстве имен класса SArray доступен во всех его функциях-членах и является для них глобальной переменной, функция-член operator[] возвращает ссылку на int, а в качестве аргумента принимает целое n - заданный индекс. Определение функции operator [] класса SArray будет иметь следующий вид:

int & SArray::operator [] (int n)
{
  // проверяем значение индекса - переменной n
  // оно должно быть больше нуля и меньше LIMIT
  if (n < 0 || n >= LIMIT)
  {
    // выводим на терминал предупреждение
    std::cout << "Index out of bounds\n";
    // прерываем исполнение программы вызовом exit
    exit(1);
  }
  // возвращаем ссылку на значение arr[n]
  return arr[n];
}

Проверку концепции, тесты экземпляра класса SArray и доступ к его элементам по индексу для данного примера лучше всего выполнить в функции main, пишу такой код.

int main()
{
  // Создаём экземпляр класса SArray
  SArray s;
  // Переменная j будет использована в двух
  // циклах подряд, поэтому её объявляем отдельно
  int j;
  // Первый цикл инициирует хранящийся в s массив,
  // каждому элементу массива будет присвоено значение
  // его индекса умноженное на 10
  for (j = 0; j < LIMIT; j++)
    // в теле цикла доступ к элементам массива по индексу
    // можно получить с помощью переменной s
    // и перегруженного оператора []
    s[j] = j * 10;
  // Второй цикл последовательно выводит на терминал
  // значения каждого элемента массива s
  // в заданной форме
  for (j = 0; j < LIMIT; j++)
  {
    int temp = s[j];
    // Ожидаем вывод на терминал семи строчек
    // по числу элементов в массиве
    std::cout << "Element " << j << " is " << temp << std::endl;
  }
  // Попробуем получить доступ к элементу массива s
  // с заведомо неверным значением индекса
  int error = s[10];
  // Ожидаем, что строчка "Element 10 is ..." на экран не будет
  // выведена, так как программа к этому моменту
  // Будет прервана вызовом exit
  std::cout << "Element 10 is " << error << std::endl;
  return 0;
}

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

Момент истины. Компилирую и запускаю программу.

g++ -o exe -Wall sarray.cpp
./exe

aV8qmtR8lm.png

Как видно на снимке экрана, полученная программа выхлопнула на терминал следующие данные:

Element 0 is 0
Element 1 is 10
Element 2 is 20
Element 3 is 30
Element 4 is 40
Element 5 is 50
Element 6 is 60
Index out of bounds

Выхлоп программы полностью отвечает сформулированным в комментариях функции main ожиданиям, массив s правильно инициирован, оператор s[j] в каждой строчке выхлопа отдаёт правильное значение соответствующего индексу элемента массива, а предупреждение Index out of bounds - последняя строчка выхлопа программы. Таким образом, функция-член operator[] даёт возможность использовать с экземплярами класса SArray форму s[j], когда необходимо получить доступ к элементам s по индексу, оператор [] успешно перегружен.

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