Урок №139. Перегрузка оператора ()

  Юрий  | 

  |

  Обновл. 25 Сен 2021  | 

 56556

 ǀ   19 

На этом уроке мы рассмотрим перегрузку оператора () в языке С++.

Перегрузка оператора ()

Все операторы, перегрузку которых мы рассматривали до сих пор, позволяли нам самостоятельно определять тип параметров в функции перегрузки оператора, но не их количество. Например, оператор == всегда принимает два параметра, тогда как оператор ! всегда принимает один параметр. Оператор () является особенно интересным, поскольку позволяет изменять как тип параметров, так и их количество.

Но следует помнить о двух вещах:

   Во-первых, перегрузка круглых скобок должна осуществляться через метод класса.

   Во-вторых, в не объектно-ориентированном C++ оператор () является оператором вызова функции. В случае с классами перегрузка круглых скобок выполняется в методе operator()(){} (в объявлении функции перегрузки находятся две пары круглых скобок).

Рассмотрим следующий класс:

Матрицы являются ключевой концепцией в линейной алгебре и часто используются в геометрическом моделировании и в 3D-графике. Всё, что вам нужно знать сейчас — это то, что класс Matrix является двумерным массивом (5×5 типа double).

На уроке о перегрузке оператора индексации мы использовали оператор [] для прямого доступа к элементам закрытого одномерного массива. Здесь же нам нужен доступ к элементам двумерного массива. Поскольку оператор [] ограничен лишь одним параметром, то его функциональности недостаточно для доступа к двумерному массиву.

Однако, поскольку оператор () может принимать разное количество параметров, мы можем объявить версию operator(), которая будет принимать два целочисленных параметра (два индекса), и использовать эти индексы для доступа к элементам нашего двумерного массива. Например:

Результат выполнения программы:

3.6

Выполним перегрузку оператора () еще раз, но уже без использования каких-либо параметров:

Результат выполнения программы:

0

Поскольку оператор () является очень гибким, то может возникнуть соблазн использовать его для самых разных целей. Однако это настоятельно не рекомендуется делать, поскольку оператор () не является очень информативным и зачастую может быть не понятно, что он делает. В примере, приведенном выше, функцию очистки массива лучше было бы записать в виде метода clear() или erase(), поскольку matrix.erase() выглядит намного информативнее, нежели matrix().

Функторы в C++


Перегрузка оператора () используется в реализации функторов (или «функциональных объектов») — классы, которые работают как функции. Преимущество функтора над обычной функцией заключается в том, что функторы могут хранить данные в переменных-членах (поскольку они сами являются классами). Вот пример использования простого функтора:

Обратите внимание, использование класса Accumulator выглядит так же, как и вызов обычной функции, но наш объект класса Accumulator может хранить значение, которое увеличивается.

Вы можете спросить: «Зачем использовать класс, если всё можно реализовать и через обычную функцию со статической локальной переменной?». Можно сделать и через static, но, поскольку функции представлены только одним глобальным экземпляром (т.е. нельзя создать несколько объектов функции), использовать эту функцию мы можем только для выполнения чего-то одного за раз. С помощью функторов мы можем создать любое количество отдельных функциональных объектов, которые нам нужны, и использовать их одновременно.

Заключение

Перегрузка оператора () с двумя параметрами используется для получения доступа к двумерным массивам или для возврата подмножеств одномерного массива (два параметра будут конкретизировать условия отбора элементов подмножества). Всё остальное лучше реализовать через отдельные методы с более информативными названиями, нежели через перегрузку оператора ().

Перегрузка оператора () также часто используется при создании функторов. Хотя функторы, которые мы использовали выше, являются довольно простыми и понятными, но обычно они используются в более продвинутых/сложных темах программирования и заслуживают отдельного урока.

Тест


Напишите класс, переменной-членом которого является строка. Перегрузите оператор () для возврата подстроки, которая начинается с индекса, указанного в значении первого параметра. Второй параметр должен указывать требуемую длину подстроки.

Подсказки:

   Вы можете использовать индекс массива [] для доступа к отдельным символам строки.

   Вы можете использовать оператор += для добавления чего-либо к строке.

Следующий фрагмент кода:

Должен выдавать следующий результат:

world!

Ответ

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (160 оценок, среднее: 4,92 из 5)
Загрузка...

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

  1. Sagynysh:

    Если у вас вектор из векторов, то есть vector<vector<T>> v;
    То можно перегрузить оператор индексации ([]). Например :

    При таком вызове :

    Работает это вот так:

    m[i][j] — это по сути две операции -> (m[i])[j]
    m[i] — здесь возвращается вектор, то есть нужная нам строка, зависит от значения i
    m[i][j] — возвращается нужный нам элемент

  2. Ден:

    Почему так не работает?

    1. Владимир:

      Почему не работает? Оно работает но не так как вы рассчитываете.

      В tempChar вы вынимаете символ на каждой итерации цикла. А затем этот символ пытаетесь записывать в строку tempStr через оператор без условного доступа[]. На первой итерации может он и запишет первый символ, если класс с момента создания имеет массив из одного элемента.

      Но класс string динамически выделяет память при необходимости в момент "добавления" информации. Оператор [] не относится к тем функция которые "добавляют" информацию. Это оператор перезаписывает уже имеющуюся, а если ее нет(как в вашем случае) то попытка доступа к индексу массива которого нет, вызывает ошибку.

  3. Smi:

  4. Ваш ученик!:

  5. Александр:

  6. Константин:

    По тесту написалось такоэ:

  7. Валерий:

    По тесту. А в реализации оператора () выход на границы строки проверять разве не нужно? Странно.
    Вызов string(7, 7) еще проходит. А вот string(7, 8) — уже вылетает.
    На мой взгляд реализация

    будет более правильной.
    Возвращаем не более len символов начиная со start. Если строка закончилась раньше — возвращаем до конца строки.
    Либо можно вычислить минимум из требуемого кол-ва и размера "хвостика" строки и цикл ограничивать этим значением.

    1. teezbeGood:

      Тоже так решил, но с точки зрения красоты, чей вариант лучше не знаю)

      1. Владимир:

        Где красота? Что за спагетти код? Оптимизация так вообще в трубу вылетела. Пускай ваша запись и меньше со стороны кол-ва строчек, но что более важно это не кол-во строчек а их длинна.
        Одна команда -> одна строчка, и чем короче тем лучше.
        Да и к тому же все возможные сравнения засовывать в цикл, это какое то кощунство, оптимизация рыдает забившись в углу… Это при каждом цикле проводить вот эту кучу перепроверок? Да половина из них нужно проводить все единожды. Я просто в шоке от такого кода.

        1. Nikita:

          А где здесь макаронный код? Даже намека нет.

          Я вижу, что можно разве что вынести конечную длину подстроки сразу.

          И более того, какие это вычисления можно сделать единожды? В коде teezbeGood единожды можно получить только сумму.

        2. Александр:

          Ответ Никите (который ответил на этот же пост)

          действительно, макарон тут нет, но вот лишние проверки, которые можно сделать единожды есть.
          проверка

          может выбросить из цикла только, если

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

          тогда этот кусок кода превратится в:

          Вот до чего я бы докопался, если бы мне такой код сдали:
          1. Нельзя оформлять блок без фигурных скобок, если пишешь его в разных строках. Т.е. нужно было или тело цикла в той же строке написать или (предпочтительней) фигурные скопки добавить. Я реально не с первой попытки тело, как тело воспринял из-за этого 🙂
          2. Идентификатор index плохой. Места занимает много, а используется только в двух соседних строках, где и так из контекста понятно, что это именно индекс. Поэтому для него лучше использовать короткое i.
          3. (Эту "ошибку" я не исправлял) Если для длины/индексов массива (что предпочтительно) используется собственный (для массива) тип данных, то лучше пользоваться именно им, а не int. Причем и в заголовках функций и в теле этих функций.
          4. Слету не придумалось хорошее название для конца интервала… придумалось endIndex -> endI. Но чет оно тоже не айс… А айс перебирать не индексы, а указатели/итераторы 🙂

        3. грустный ламер:

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

        4. Grave18:

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

    2. Артём:

      А как насчет простой человеческой автоматической проверки выхода из диапазона std::string через метод доступа к элементу по индексу .at()?))

  8. Александр:

    @@@
    Поскольку оператор [] ограничен лишь одним параметром, то его функциональности недостаточно для доступа к двумерному массиву.
    @@@
    почему недостаточно?
    мы можем возвращать указатель на начало "строки" двумерного массива

    обращение к двумерному массиву:

    1. Grave18:

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

    2. Александр:

      Можете поподробней обьяснить реализацию? Не совсем понимаю, как это работает

  9. kmish:

Добавить комментарий для Александр Отменить ответ

Ваш E-mail не будет опубликован. Обязательные поля помечены *