JavaRush /Курси /Swift SELF /Оголошення функцій: параметри та результат

Оголошення функцій: параметри та результат

Swift SELF
Рівень 9 , Лекція 1
Відкрита

1. Навіщо потрібні функції

Якщо писати програму без функцій, неминуче зʼявляється копіпаста: одна й та сама логіка введення числа, перевірки діапазону, розрахунку знижки та виведення повторюється в різних місцях. Спочатку це здається нешкідливим («подумаєш, ще 6 рядків»), а потім ви змінюєте формат виведення — і шукаєте ці 6 рядків по всьому файлу. Функція — це спосіб дати імʼя повторюваній дії або обчисленню та використовувати його стільки разів, скільки потрібно.

Можна уявити функцію як «міні-машинку»: ви передаєте їй вхідні дані, вона виконує роботу й або повертає результат, або просто щось робить, наприклад друкує. Важлива думка: функція — це контракт. Вона обіцяє: «якщо їй дати певні вхідні дані, вона зробить ось це».

Нижче — найменший приклад функції, що виконує дію:

func sayHello() {
    print("Привіт! Я функція 🙂")
}

sayHello() // Привіт! Я функція 🙂

Зверніть увагу: спочатку ми оголосили функцію через func, а потім викликали її через sayHello().

2. Скелет функції: func, ім’я, параметри, тіло

Коли люди вперше бачать оголошення функції, воно виглядає як «занадто багато символів навколо простої дії». Але насправді тут усе логічно: Swift змушує нас явно описувати, що функція приймає і що повертає. Це і є контракт, який компілятор перевіряє замість вас, — і це одна з причин, чому Swift так любить типи.

Сигнатура, тобто «шапка» функції, зазвичай має такий вигляд:

func імʼя(параметри) -> типРезультату { тіло }

Давайте покажемо частини функції в таблиці — так їх легше запам’ятати:

Частина Приклад Навіщо потрібна
Ключове слово
func
Каже Swift: «зараз буде функція»
Ім’я
square
Щоб ви могли викликати функцію за ім’ям
Параметри
(x: Int)
Вхідні дані функції
Результат
-> Int
Тип результату — те, що функція обіцяє повернути
Тіло
{ ... }
Код, який виконується під час виклику

Найменший приклад: функція щось обчислює й повертає результат:

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(), значить, настав час розбити її на менші кроки.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ