Несколько результатов
В теме об операторах конфеты делили так: / говорит, сколько получит каждый ребёнок, а % — сколько конфет останется. Это два ответа на один вопрос, и удобно получать их из одной функции. Но после -> до сих пор стоял один тип, а return отдавал одно значение.
Представьте, что комбайн выдаёт не один стакан, а поднос с двумя: на одном написано «каждому», на другом — «остаток». Поднос несут одним движением, а пьют из каждого стакана отдельно. В Swift такой поднос называется кортежем:
func share(_ sweets: Int, by kids: Int) -> (each: Int, left: Int) {
(sweets / kids, sweets % kids)
}
let result = share(17, by: 5)
print(result.each) // 3
print(result.left) // 2
print(result) // (each: 3, left: 2)Давайте разберём программу:
-> (each: Int, left: Int)обещает, что функция вернёт два целых числа с именамиeachиleft.- Тело — одно выражение: два значения в круглых скобках через запятую.
share(17, by: 5)возвращает3и2вместе, и оба числа лежат вresult.result.eachиresult.leftдостают их по именам.print(result)печатает весь кортеж вместе с именами частей.
Кортеж
Кортеж — несколько значений, собранных в одно. Их пишут в круглых скобках через запятую: (3, 2).
Тип кортежа записывают так же, в скобках: (each: Int, left: Int). Имена частей необязательны: (Int, Int) — тоже кортеж из двух целых чисел. Части могут быть и разных типов: ("Anna", 30) — кортеж из строки и числа.
Так поступает и стандартная библиотека Swift — готовые типы и функции, которые устанавливаются вместе с языком, как Int и print. Например, 17.quotientAndRemainder(dividingBy: 5) возвращает кортеж (quotient: 3, remainder: 2) — частное и остаток вместе.
На собеседовании начинающего разработчика спрашивают: как вернуть из функции несколько значений? Короткий ответ — кортежем. Тип результата пишут в скобках, -> (each: Int, left: Int), а сами значения перечисляют через запятую, тоже в скобках.
Как достать части
Части кортежа достают четырьмя способами. Доступ по имени вы уже видели, а остальные три — в этом примере:
print(result.0, result.1) // 3 2
let (perKid, leftover) = share(20, by: 3)
print(perKid, leftover) // 6 2
let (whole, _) = share(9, by: 2)
print(whole) // 4По имени
result.each — имя части после точки. Имена проверяет компилятор: на result.rest он ответит value of tuple type '(each: Int, left: Int)' has no member 'rest'.
По номеру
result.0 и result.1 — номер части после точки. Части нумеруются с нуля. Номера работают всегда, даже если у частей нет имён, но по имени код читать легче.
Раскладывание по константам
let (perKid, leftover) = share(20, by: 3) объявляет сразу две константы: первая часть кортежа попадает в perKid, вторая — в leftover. Функция вернула (6, 2), и программа печатает 6 2.
Пропуск части
Если одна из частей не нужна, на её место пишут _, как в цикле, где номер не нужен. Строка let (whole, _) = share(9, by: 2) кладёт в whole число 4, а остаток 1 никуда не сохраняется.
Здесь картинка с подносом неточна. Стаканы с подноса можно взять в любом порядке, и каждый останется со своим соком. А при раскладывании Swift смотрит только на порядок частей, а не на их имена.
Частая ошибка
Кортеж разложили по константам с теми же именами, что в типе результата, но в другом порядке:
let (left, each) = share(17, by: 5) // ✗
print(left, each) // 3 2Ошибки нет ни при компиляции, ни при запуске, но в left оказалось 3, а в each — 2. Первая часть кортежа всегда идёт в первую константу, а Swift не сравнивает имена each и left из типа результата с именами констант. Перечисляйте константы в том же порядке, что и части в типе результата, или берите части по имени: result.left.
Главное
- Кортеж собирает несколько значений в одно:
(3, 2), а с типом результата(each: Int, left: Int)функция возвращает их вместе. - Части кортежа достают по имени,
result.each, или по номеру с нуля,result.0. let (a, b) = …раскладывает кортеж по константам, а_пропускает ненужную часть.- Раскладывание идёт по порядку частей, а не по их именам.