Перейти к содержимому
Теория · Функции · урок 1 из 9

Функции

Чему вы научитесь

Чему вы научитесь:

  • объявлять свои функции с параметрами и результатом и вызывать их;
  • задавать метки аргументов, убирать их через _ и давать параметрам значения по умолчанию;
  • возвращать несколько значений кортежем и менять переменную через inout;
  • объяснять, как рекурсивная функция вызывает саму себя и где она останавливается.
Кухонный прибор add с двумя воронками a: и b:, в которые падают фрукты, и стакан сока на выходе

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

Функция в программе — такой же комбайн. Вы уже вызывали функции, встроенные в Swift: print печатает значения, а type(of:) называет тип. Теперь вы научитесь писать свои.

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

Вот функция, которая складывает два числа:

SWIFT
func add(a: Int, b: Int) -> Int {
    return a + b
}

let sum = add(a: 2, b: 3)
print(sum) // 5
print(add(a: 10, b: -4)) // 6

Давайте разберём программу по шагам:

  1. Строки от func до закрывающей фигурной скобки объявляют функцию add. Сами по себе они ничего не выполняют: это описание комбайна.
  2. add(a: 2, b: 3) вызывает функцию: число 2 попадает в a, а 3 — в b.
  3. Функция вычисляет a + b, и return отдаёт 5 туда, откуда её вызвали. Это число попадает в константу sum.
  4. print(sum) печатает 5.
  5. Второй вызов работает с другими числами и даёт 6.

Объявление функции

Строка func add(a: Int, b: Int) -> Int — заголовок функции. Его части:

  • func — ключевое слово, с которого начинается объявление;
  • add — имя функции, по нему её вызывают;
  • (a: Int, b: Int) — параметры в круглых скобках, через запятую: имя и тип каждого;
  • -> Int — тип результата, который функция возвращает.

За заголовком в фигурных скобках стоит тело функции — код, который выполняется при каждом вызове.

Параметр — имя, под которым тело функции видит переданное значение. В комбайне это отверстие с табличкой: a и b — два отверстия для двух чисел. Внутри тела параметр ведёт себя как константа let: его читают, но не меняют.

Вызов и аргументы

При вызове после имени функции в скобках пишут аргументы — значения для параметров. Перед каждым аргументом стоит имя параметра с двоеточием: a: 2. Это метка аргумента, такая же, как separator: у print. Аргументы идут в том же порядке, что и параметры в заголовке.

Вызов функции — это выражение, и его значение — результат функции. Поэтому вызов можно положить в константу, напечатать или передать аргументом в другой вызов. Вызов выполняется, когда программа доходит до его строки, и в sum попадает готовое число 5, а не сам вызов.

На собеседовании начинающего разработчика иногда спрашивают, чем параметр отличается от аргумента. Ответ:

  • параметр — имя в объявлении функции: a в func add(a: Int, b: Int);
  • аргумент — значение, которое передают при вызове: 2 в add(a: 2, b: 3).

Результат функции

Запись -> Int обещает, что функция вернёт Int. Возвращает его инструкция return: она вычисляет значение справа от себя, отдаёт его и сразу завершает функцию. Строки тела ниже return уже не выполняются.

Тело из одного выражения

Если тело функции — одно выражение, return можно не писать: Swift вернёт значение этого выражения сам. Так устроена функция square в примере ниже.

Функция без результата

Функция не обязана что-то возвращать. Функция greet ниже печатает приветствие, и стрелки -> в её заголовке нет. Тип результата у неё всё равно есть — Void, то есть «ничего». Писать -> Void можно, но обычно не пишут.

SWIFT
func square(x: Int) -> Int {
    x * x
}

func greet(name: String) {
    print("Hello, \(name)!")
}

print(square(x: 4)) // 16
greet(name: "Anna") // Hello, Anna!
print(add(a: square(x: 3), b: 1)) // 10

Разберём три вызова:

  1. square(x: 4) возвращает 16, хотя return в теле нет.
  2. greet(name: "Anna") печатает строку сама, а результата не возвращает. Поэтому её вызывают отдельной строкой, а не внутри print.
  3. В последней строке square(x: 3) стоит аргументом другого вызова. Сначала выполняется он и даёт 9, затем add получает 9 и 1 и возвращает 10.

Здесь картинка с комбайном неточна. Сок из комбайна вытечет, даже если стакан не подставили. А результат функции, который никуда не положили и не напечатали, Swift считает подозрительным: строка add(a: 2, b: 3) сама по себе даёт предупреждение result of call to 'add(a:b:)' is unused.

Частая ошибка

Функция пытается изменить переменную, объявленную прямо в main.swift:

SWIFT
var total = 0
func addToTotal(n: Int) {
    total += n // ✗
}
TEXT
error: main actor-isolated var 'total' can not be mutated from a nonisolated context

Программа не компилируется. Сообщение говорит: переменная total закреплена за главной частью программы — строками main.swift вне всех функций, — и функции менять её нельзя. Прочитать такую переменную из функции тоже не выйдет. А константу let с числом или строкой функция прочитать может: её никто не изменит.

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

Комбайн не лезет к вам в холодильник: всё нужное ему кладут в отверстия. Так и здесь: передайте значение параметром, а результат присвойте сами.

SWIFT
var total = 0
total = add(a: total, b: 5)
total = add(a: total, b: 7)
print(total) // 12

Функция add получает total аргументом и возвращает сумму, а присваиваете сумму переменной total вы сами, уже вне функции.

Главное

  • Функцию объявляют словом func: имя, параметры с типами в круглых скобках, тип результата после -> и тело в фигурных скобках.
  • При вызове перед аргументом пишут метку: add(a: 2, b: 3); параметр — имя в объявлении, аргумент — значение в вызове.
  • return возвращает результат и завершает функцию; тело из одного выражения возвращает его без return, а функция без -> возвращает Void.
  • Функция не трогает переменные var главной части программы: значения ей передают параметрами, а результат она возвращает.