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 "додатне"
} else if value < 0 {
return "відʼємне"
} else {
return "нуль"
}
}
print(signDescription(value: 10)) // додатне
print(signDescription(value: 0)) // нуль
Зверніть увагу на дисципліну: кожна гілка if/else if/else повертає String. Якщо хоча б в одній гілці забути return, компілятор скаже: «Ви обіцяли String, де ж він?»
Ще одна важлива деталь: return не лише повертає значення, а й одразу завершує виконання функції. Усе, що написано після return у цій гілці, уже не виконається.
5. Void: функція «щось робить», але нічого не повертає
У реальних програмах багато функцій не повертають нічого «корисного»: вони друкують, читають введення, записують у файл, показують екран, надсилають запит. На нашому поточному рівні найзрозуміліше — виведення: print() теж нічого не повертає, вона просто виконує дію.
Якщо функція не повертає результат, то кажуть, що вона повертає Void. У Swift Void — це псевдонім для порожнього кортежа (), тобто «порожнього значення». Зазвичай -> 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 "додатне"
}
// якщо value <= 0 — що повернути? компілятор не дасть зібрати проєкт
}
Правильний варіант — завжди передбачати «інакше»:
func goodSign(value: Int) -> String {
if value > 0 {
return "додатне"
} else {
return "недодатне"
}
}
print(goodSign(value: 0)) // недодатне
Так, іноді це виглядає надлишково. Але зате кожен, хто викликає вашу функцію, упевнений: результат буде завжди, і саме потрібного типу.
Параметри всередині функції — як 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
Збираємо сценарій у код верхнього рівня
Тепер зберемо це в один цілісний фрагмент коду верхнього рівня, який читається як сценарій, а не як хаотичний набір інструкцій:
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) євро")
Зверніть увагу, що частина верхнього рівня тепер читається майже як звичайний текст: «привітай — запитай ціну — запитай знижку — обчисли — виведи». Ось заради цього ми й любимо функції: вони перетворюють код на зрозумілий сценарій.
Як думати про функції: вхід → функція → вихід
На початку легко сприймати функції як магію: «викликав — і щось десь сталося». Щоб цього не було, корисно тримати в голові просту модель: функція — це перетворювач даних, іноді з побічним ефектом, наприклад друком. Тобто в неї є вхід, процес і, можливо, вихід.
Невелика блок-схема допомагає це закріпити:
flowchart LR
A[Аргументи під час виклику] --> B[Тіло функції]
B --> C[Значення, що повертається]
B --> D["Побічний ефект: print()"]
У нашому DiscountBuddy це виглядає так: readInt(prompt:) приймає рядок-підказку, друкує її, читає введення, а потім повертає Int. А discountedPrice(...) взагалі нічого не виводить: просто повертає число. Таке розділення — дуже сильна звичка: функції-обчислювачі простіше перевіряти вручну, і вони зазвичай рідше ламаються.
8. Типові помилки
Помилка № 1: функція обіцяє повернути значення, але return є не в усіх гілках.
Це класика: ви написали -> Int, зробили if value > 0 { return 1 }, а далі забули else. Компілятор у цьому місці — ваш найкращий друг: він не дає вам випустити програму, де в одному зі сценаріїв результат не визначений. Лікується звичкою: якщо функція повертає значення, подумки запитуйте себе, що повернеться в усіх інших випадках.
Помилка № 2: змішування обчислень і виведення без потреби.
Новачок часто робить так: функція і рахує, і друкує. А потім виявляється, що ви хочете використати результат в іншому місці без друку — і доводиться переписувати. На цьому етапі курсу дуже корисно розділяти: «порахувати й повернути» — одна функція, «зручно показати користувачеві» — інша функція або код верхнього рівня.
Помилка № 3: спроба змінити параметр безпосередньо.
Усередині функції параметр поводиться як константа, і Swift не дасть написати щось на кшталт value = value + 1. Це не примха мови, а захист від плутанини: вхід має залишатися входом. Якщо потрібно покрутити значення, створіть локальну змінну var temp = value і змінюйте її.
Помилка № 4: неправильні очікування від Void-функцій.
Іноді хочеться зробити let x = printWelcome() і чекати, що x буде рядком або чимось корисним. Але Void означає: функція нічого не повертає, вона лише виконує дію. Якщо вам потрібне значення, отже, функція має повертати тип, наприклад String, і всередині містити return.
Помилка № 5: надто довгі функції «на всі випадки життя».
Захопившись функціями, легко зробити одну величезну process() на 200 рядків, яка і читає, і перевіряє, і рахує, і друкує. Формально це функція, але по суті — та сама громіздка конструкція, тільки у фігурних дужках. Гарне правило для старту: якщо ви не можете за 10 секунд пояснити, що робить функція, і її ім’я виходить на кшталт doEverythingAndMore(), значить, настав час розбити її на менші кроки.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ