1. Зачем нужны функции
Если писать программу без функций, неизбежно появляется копипаста: одна и та же логика ввода числа, проверки диапазона, расчёта скидки и вывода повторяется в разных местах. Сначала это кажется безобидным («подумаешь, ещё 6 строк»), а потом вы меняете формат вывода — и ищете эти 6 строк по всему файлу. Функция — это способ дать имя повторяющемуся действию или вычислению и использовать его столько раз, сколько нужно.
Можно представить функцию как «мини-машинку»: вы кладёте внутрь входные данные, машинка делает работу и либо возвращает результат, либо просто выполняет действие (например, печатает). Важная мысль: функция — это контракт. Она обещает: «если мне дать такие-то входные данные, я сделаю вот это».
Ниже — самый маленький пример функции-действия:
func sayHello() {
print("Привет! Я функция 🙂")
}
sayHello() // Привет! Я функция 🙂
Обратите внимание: сначала мы объявили функцию (через func), а потом вызвали (через sayHello()).
2. Скелет функции: func, имя, параметры, тело
Когда люди видят объявление функции впервые, оно выглядит как «слишком много символов вокруг простого действия». Но на самом деле там всё логично: Swift заставляет нас явно описывать, что функция принимает и что возвращает. Это и есть контракт, который компилятор проверяет вместо вас (и это одна из причин, почему Swift так любит типы).
Сигнатура (то есть «шапка») функции обычно читается так:
func имя(параметры) -> типРезультата { тело }
Давайте визуализируем части функции в табличке — так проще запомнить:
| Часть | Пример | Зачем нужна |
|---|---|---|
| Ключевое слово | |
Говорит Swift: «сейчас будет функция» |
| Имя | |
Чтобы вы могли вызывать функцию по имени |
| Параметры | |
Входные данные функции |
| Результат | |
Тип результата (что функция обещает вернуть) |
| Тело | |
Код, который выполняется при вызове |
Самый минимальный пример «функция что-то вычисляет и возвращает»:
func square(x: Int) -> Int {
return x * x
}
let v = square(x: 6)
print(v) // 36
Здесь важно: square не печатает результат. Она его возвращает. Печать — отдельным шагом. Это делает функции более универсальными: сегодня вы печатаете, завтра сравниваете, послезавтра сохраняете в переменную.
3. Параметры и аргументы: не путать «внутри» и «снаружи»
Параметры и аргументы — слова похожие, из-за чего мозг пытается «сэкономить память» и считать их одним и тем же. Но разница простая и очень практичная: параметр живёт в объявлении функции, а аргумент — в месте вызова. То есть параметр — это как «слот», а аргумент — конкретное значение, которое вы в этот слот передаёте.
Посмотрим на маленьком примере, где функция делает строку для приветствия:
func greeting(name: String) -> String {
return "Привет, \(name)!"
}
let text = greeting(name: "Аня")
print(text) // Привет, Аня!
В объявлении name — параметр. При вызове "Аня" — аргумент.
Функция может принимать несколько параметров. Тогда она становится похожей на «формулу» с несколькими входами:
func rectangleArea(width: Int, height: Int) -> Int {
return width * height
}
let area = rectangleArea(width: 4, height: 7)
print(area) // 28
Заметьте, как приятно читать вызов: width: 4, height: 7. Даже если вы забудете порядок, код всё равно «сам себя объясняет».
4. Возвращаемое значение и return
Возвращаемое значение — это то, ради чего многие функции вообще пишутся. И тут важно понимать два правила. Первое: если в сигнатуре есть -> Type, то функция обязана вернуть значение этого типа. Второе: вернуть значение можно только через return.
Простейший пример: функция определяет знак числа и возвращает строку:
func signDescription(value: Int) -> String {
if value > 0 {
return "positive"
} else if value < 0 {
return "negative"
} else {
return "zero"
}
}
print(signDescription(value: 10)) // positive
print(signDescription(value: 0)) // zero
Обратите внимание на дисциплину: каждая ветка if/else if/else возвращает String. Если хотя бы в одной ветке забыть return, компилятор скажет: «Ты обещал String, где он?»
Ещё важная деталь: return не только возвращает значение, но и сразу завершает выполнение функции. Всё, что написано после return в этой ветке, уже не выполнится.
5. Void: функция «делает», но ничего не возвращает
В реальных программах огромное количество функций ничего «полезного» не возвращают: они печатают, читают ввод, записывают в файл, показывают экран, отправляют запрос. На нашем текущем уровне самое понятное — печать: print() ведь тоже ничего не возвращает, он просто делает действие.
Если функция не возвращает результат, то говорят, что она возвращает Void. В Swift Void — это алиас для пустого tuple () (то есть «пустое значение»). Обычно -> Void в сигнатуре не пишут: отсутствие стрелки уже означает «ничего не возвращаю».
Пример функции, которая печатает заголовок:
func printHeader(title: String) {
print("=== \(title) ===")
}
printHeader(title: "Моя программа") // === Моя программа ===
Иногда return встречается и в Void-функциях — но не чтобы «вернуть результат», а чтобы выйти раньше. Это похоже на «стоп-слово» внутри функции.
Пример:
func printIfPositive(value: Int) {
if value <= 0 {
return
}
print("Число положительное")
}
printIfPositive(value: -3) // (ничего не напечатает)
printIfPositive(value: 5) // Число положительное
Это очень полезный приём: вместо глубоких вложенных if вы можете «раньше выйти» и оставить основной путь исполнения более прямым.
6. Контракт функции: return и неизменяемые параметры
Все ветки должны вернуть значение
Ошибки с return — одна из самых частых причин, почему новичок смотрит на компилятор и думает: «Он придирается». Но компилятор не придирается: он охраняет ваш контракт. Если функция обещает Int, то она должна вернуть Int в любом сценарии.
Например, вот так писать нельзя (показываю как антипример, ❌ не компилируется):
func badSign(value: Int) -> String {
if value > 0 {
return "positive"
}
// если value <= 0 — что вернуть? компилятор не даст собрать проект
}
Правильный вариант — всегда предусматривать «иначе»:
func goodSign(value: Int) -> String {
if value > 0 {
return "positive"
} else {
return "not positive"
}
}
print(goodSign(value: 0)) // not positive
Да, иногда это выглядит «избыточно». Но зато любой, кто вызывает вашу функцию, уверен: результат будет всегда, и он нужного типа.
Параметры внутри функции — как let
Новички часто пытаются «переиспользовать параметр как рабочую переменную»: прибавить к нему что-то, обрезать строку, изменить скидку. И тут Swift говорит: «нельзя». Причина простая: параметр — это вход, его лучше воспринимать как константу. Функция получила значение — и работает с ним, не подменяя сам «вход».
Если вам нужно менять значение — делайте локальную копию через var. Это выглядит скучно, зато очень ясно.
Пример:
func withPrefix(text: String, prefix: String) -> String {
var result = text
result = prefix + result
return result
}
print(withPrefix(text: "world", prefix: "hello ")) // hello world
Здесь text мы не трогаем. Мы создаём result и работаем с ним. Это дисциплина, которая позже спасает от неожиданных эффектов: когда входные данные «вдруг поменялись» и непонятно почему.
7. Пример: консольное приложение DiscountBuddy
Сейчас мы соберём маленькую программу, которую будем развивать в следующих лекциях дня. Идея простая: «калькулятор скидки». Пользователь вводит цену и процент скидки, а программа считает итоговую цену и печатает результат. Наша цель не в математике (там всё скучно), а в том, чтобы увидеть, как функции превращают простыню кода в аккуратные блоки.
Приветствие
Сначала сделаем функцию приветствия:
func printWelcome() {
print("DiscountBuddy v0.1")
print("Посчитаем цену со скидкой.")
}
printWelcome()
// DiscountBuddy v0.1
// Посчитаем цену со скидкой.
Ввод числа
Теперь вынесем ввод числа в отдельную функцию. Пока что будем максимально прямолинейны: если пользователь ввёл ерунду, получим 0. Это не идеальная валидация, но для текущего этапа курса — нормально:
func readInt(prompt: String) -> Int {
print(prompt, terminator: " ")
let line = readLine() ?? ""
return Int(line) ?? 0
}
let x = readInt(prompt: "Введите число:")
print("Вы ввели \(x)")
Расчёт цены со скидкой
И наконец, функция расчёта цены со скидкой — она возвращает значение, а не печатает:
func discountedPrice(price: Int, discountPercent: Int) -> Int {
let discount = price * discountPercent / 100
return price - discount
}
let finalPrice = discountedPrice(price: 1000, discountPercent: 15)
print(finalPrice) // 850
Склеиваем сценарий в top-level код
Теперь соберём это в один цельный кусок top-level кода, который выглядит как «сценарий», а не «каша»:
func printWelcome() {
print("DiscountBuddy v0.1")
}
func readInt(prompt: String) -> Int {
print(prompt, terminator: " ")
let line = readLine() ?? ""
return Int(line) ?? 0
}
func discountedPrice(price: Int, discountPercent: Int) -> Int {
let discount = price * discountPercent / 100
return price - discount
}
printWelcome()
let price = readInt(prompt: "Введите цену (евро):")
let percent = readInt(prompt: "Введите скидку (%):")
let result = discountedPrice(price: price, discountPercent: percent)
print("Итоговая цена: \(result) евро")
Заметьте, что top-level часть теперь читается почти как обычный текст: «поприветствуй → спроси цену → спроси скидку → посчитай → выведи». Вот ради этого мы и любим функции: они превращают код в понятный сценарий.
Как думать о функциях: вход → функция → выход
Когда вы только начинаете, очень легко начать воспринимать функции как магию: «вызвал — что-то где-то произошло». Чтобы этого не было, полезно держать в голове простую модель: функция — это преобразователь данных, иногда с побочным эффектом (например, печатью). То есть у неё есть вход, процесс и (возможно) выход.
Небольшая блок-схема помогает это закрепить:
flowchart LR
A[Аргументы при вызове] --> B[Тело функции]
B --> C[return значение]
B --> D["побочный эффект: print()"]
В нашем DiscountBuddy это выглядит так: readInt(prompt:) принимает строку-подсказку, печатает её (побочный эффект), читает ввод, а потом возвращает Int. А discountedPrice(...) вообще ничего не печатает: просто возвращает число. Такое разделение — очень сильная привычка: «функции-вычислители» проще проверять глазами, и они обычно меньше ломаются.
8. Типичные ошибки
Ошибка №1: функция обещает вернуть значение, но return есть не во всех ветках.
Это классика: вы написали -> Int, сделали if value > 0 { return 1 }, а дальше забыли else. Компилятор в этом месте ваш лучший друг: он не даёт вам выпустить программу, где в одном сценарии «результат не определён». Лечится привычкой: если функция возвращает значение, мысленно спрашивайте «а что вернётся во всех остальных случаях?».
Ошибка №2: смешивание вычислений и вывода без необходимости.
Новичок часто делает так: функция и считает, и печатает. А потом оказывается, что вы хотите использовать результат в другом месте без печати — и приходится переписывать. На уровне этого курса очень полезно разделять: «посчитать и вернуть» — одна функция, «красиво показать пользователю» — другая функция или top-level код.
Ошибка №3: попытка изменить параметр напрямую.
Внутри функции параметр ведёт себя как константа, и Swift не даст написать что-то вроде value = value + 1. Это не «придирка языка», а защита от путаницы: вход должен оставаться входом. Если нужно «покрутить» значение, создайте локальную переменную var temp = value и меняйте её.
Ошибка №4: неправильные ожидания от Void-функций.
Иногда хочется сделать let x = printWelcome() и ждать, что x будет строкой или чем-то полезным. Но Void означает: функция ничего не возвращает, она только делает действие. Если вам нужно значение — значит, функция должна возвращать тип (например, String) и внутри иметь return.
Ошибка №5: слишком длинные функции «на все случаи жизни».
На радостях от функций легко сделать одну огромную process() на 200 строк, которая и читает, и проверяет, и считает, и печатает. Формально это функция, но по сути — та же простыня, только в фигурных скобках. Хорошее правило для старта: если вы не можете за 10 секунд объяснить, что делает функция, и её имя получается вроде doEverythingAndMore(), значит пора делить на более маленькие шаги.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ