Основы Swift / 19.3. Методы-операторы


Видео


Методы-операторы
Классы и структуры могут предоставить свои собственные реализации существующих операторов. Это известно как перегрузка существующих операторов.

Пример ниже демонстрирует, как реализовать оператор арифметического сложения (+) для произвольной структуры. Оператор арифметического сложения - это бинарный оператор, так как он имеет два операнда; также он является инфиксным оператором, так как он пишется между своими двумя операндами.
@{19.3\1\1}
Пример определяет структуру Vector2D для двухмерного вектора (x, y), сопровоэжаечмоего определением метода-оператора для сложения двух объектов типа Vector2D:
struct Vector2D {
    var x = 0.0, y = 0.0
}

extension Vector2D {
    static func + (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y + right.y)
    }
}
Метод-оператор определён как метод типа Vector2D с именем метода, совпадающим с оператором, который нужно перегрузить (+). Так как сложение не является частью обычного поведения вектора, то метод типа определён в расширении Vector2D, а не основном объявлении структуры Vector2D. Так как оператор арифметического сложения - это бинарный оператор, то этот метод-оператор принимает два входных параметра типа Vector2D и возвращает единственное выходное значение так же типа Vector2D.

В этой реализации входные параметры имеют имена left и right, чтобы представить те объекты типа Vector2D, что будут слева и справа оператора +. Метод возвращает новый экземпляр типа Vector2D, чьи значения x и y инициализированы суммами свойств x и y от складываемых вместе объектов типа Vector2D.
@{19.3\1\2}
Метод типа может быть использован как инфиксный оператор между существующими объектами типа Vector2D:
let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
// combinedVector is a Vector2D instance with values of (5.0, 5.0)
В этом примере вместе складываются два вектора (3.0, 1.0) и (2.0, 4.0) для получения вектора (5.0, 5.0).
Префиксные и постфиксные операторы
Пример, показанный выше, демонстрирует произвольную реализацию бинарного инфиксного оператора. Классы и структуры так же могут предоставлять свои реализации стандартных унарных операторов. Унарные операторы имеют один операнд. Они являются префиксными, если предшествуют своему операнду (как например -a) и постфиксными операторами, если они следуют за своим операндом (как например b!).

Вы можете реализовать префиксы или постфиксный унарный оператор, написав модификаторы prefix или postfix перед ключевым словом func, когда объявляете метод-оператор:
@{19.3\1\3}
Вы можете реализовать префиксный или постфиксный унарный оператор, написав модификаторы prefix или postfix перед ключевым словом func, когда объявляете метод-оператор:
extension Vector2D {
    static prefix func - (vector: Vector2D) -> Vector2D {
        return Vector2D(x: -vector.x, y: -vector.y)
    }
}
Пример выше реализует оператор унарного минуса (-a) для объектов типа Vector2D. Оператор унарного минуса - это префиксный оператор, а значит метод должен быть квалифицирован с помощью модификатора prefix.
@{19.3\1\4}
Для простых числовых значений оператор унарного минуса просто преобразует положительные числа в им противоположные и наоборот. Соответствующая реализация для объектов Vector2D выполняет эту операцию над обоими свойствами x и y:
let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative is a Vector2D instance with values of (-3.0, -4.0)
let alsoPositive = -negative
// alsoPositive is a Vector2D instance with values of (3.0, 4.0)
Составные операторы присваивания
Составные операторы присваивания объединяют присваивание (=) с другой операцией. Например, оператор присваивания/сложения (+=) объединяет сложение и присваивание в одну операцию. Вы должны отметить левое входное значение составного оператора присваивания как inout, так как значение параметра будет модифицировано изнутри оператора присваивания.

Невозможно перегрузить изначальный оператор присваивания (=). Только составные операторы присваивания могут быть перегружены. Аналогично, тернарный условный оператор (a ? b : c) не может быть перегружен.
@{19.3\1\5}
Пример ниже реализует метод в виде оператора сложения/присваивания для объектов типа Vector2D:
extension Vector2D {
    static func += (left: inout Vector2D, right: Vector2D) {
        left = left + right
    }
}
@{19.3\1\6}
Так как оператор сложения определён ранее, Вам ненужно заново его реализовывать. Вместо этого оператор сложения/присваивания использует преимущества существующего оператора сложения и использует его для задания левого значения в качестве суммы левого и правого:
var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original now has values of (4.0, 6.0)
Операторы эквивалентности
Пользовательские классы и структуры не получают дефолтной реализации операторов эквивалентности, известных как оператор равенства (==) и неравенства (!=). Для Swift невозможно угадать, что нужно квалифицировать в качестве равенства для Ваших собственных типов, так как значения эквивалентности зависит от того, какую роль эти типы играют в Вашем коде.

Для использования операторов эквивалентности для проверки равенства Ваших собственных типов предоставьте реализации операторов в том же виде, что и для других инфиксных операторов:
@{19.3\1\7}
extension Vector2D {
    static func == (left: Vector2D, right: Vector2D) -> Bool {
        return (left.x == right.x) && (left.y == right.y)
    }
    static func != (left: Vector2D, right: Vector2D) -> Bool {
        return !(left == right)
    }
}
Пример выше реализует оператор равенства (==) для проверки того, имеют ли два объекта Vector2D эквивалентные значения. В контексте Vector2D "равенство" означает "оба объекта имеют одинаковые значения x и y", и так что такая логика используется реализацией оператора. Пример так же реализует оператор неравенства (!=), который просто возвращает инверсию результата оператора равенства.
@{19.3\1\8}
Вы можете теперь использовать эти операторы для проверки того, являются ли два объекта типа Vector2D эквивалентными:
let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
    print("These two vectors are equivalent.")
}
// Выведет "These two vectors are equivalent."
Операторы сравнения
Аналогичная ситуация имеет место быть для операторов сравнения.

Ранее мы рассматривали сравнение кортежей. Стандартная библиотека позволяет сравнивать кортежи с 7-ю элементами. Хотите больше? Сделайте свой оператор сравнения кортежей с блекджекем и Дейнерис Бурерождённой.
Пользовательские операторы
Вы можете объявить и реализовать Ваши собственные операторы в дополнение к стандартным операторам, предоставляемым Swift.

Новые операторы объявляются на глобальном уровне с использованием ключевого слова operator и помечаются как prefix, infix или postfix:
@{19.3\1\9}
prefix operator +++
Пример выше определяет новый префиксный оператор с названием +++. Это оператор не имеет существующего смысла в Swift, так что ему будет задано Ваше собственное значение ниже в конкретном контексте работы с объектами Vector2D. Для целей примера +++ воспринимается как новый оператор "префиксного дублирования". Он удваивает значения x и y у объекта типа Vector2D, добавляя вектор к самому себе с помощью оператора сложения/присваивания, определённого ранее.
@{19.3\1\10
Для реализации оператора +++ Вы добавляете метод типа с названием +++ Vector2D:
extension Vector2D {
    static prefix func +++ (vector: inout Vector2D) -> Vector2D {
        vector += vector
        return vector
    }
}

var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled now has values of (2.0, 8.0)
// afterDoubling also has values of (2.0, 8.0)
Прецедентность для нестандартных инфиксных операторов
Пользовательские инфиксные операторы принадлежат своей прецедентной группе. Группа прецедентности определяет прецедентность оператора относительно других инфиксных операторов, как и ассоциативность оператора. Смотрите {Прецедентность и ассоциативность} для объяснения того, как эти характеристики оказывают влияние на взаимодействие инфиксного оператора с другими инфиксными операторами.

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

Вы не определяете прецедентность, когда определяете префиксы или постфиксный оператор. Однако, если Вы примените одновременно и префиксный, и постфиксный операторы к одному операнду, то первым будет применён постфикный оператор.
@{19.3\1\11}
infix operator +-: AdditionPrecedence
extension Vector2D {
    static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y - right.y)
    }
}
let firstVector = Vector2D(x: 1.0, y: 2.0)
let secondVector = Vector2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector is a Vector2D instance with values of (4.0, -2.0)
Этот оператор складывает вместе два значения x векторов и вычитает значение y второго вектора из первого. Так как это аддитивный оператор, то ему дана та же группа прецедентности, что и аддитивным инфиксным операторам, как например + и -