1. Объявление функций
Когда вы только начинаете программировать, кажется, самый простой вариант — это написать весь код в main, сверху вниз. И это действительно нормально на первых шагах: мозг занят синтаксисом, а не архитектурой.
Но довольно быстро появляются два симптома: вы копируете одинаковые куски кода (и потом чините баги в пяти местах), а main превращается в простыню, которую страшно трогать. Функции — это способ дать имя повторяющемуся действию или вычислению, чтобы код читался как сценарий: «сначала спросили ввод, потом посчитали, потом вывели».
Представьте, что программа — это рецепт. main — это «что делаем по шагам», а функции — это «как именно нарезать лук» и «как сварить макароны».
Минимальная функция: fun имя() { ... }
Сначала разберём самый простой вид функции: без параметров и без результата. В Kotlin функция создается с помощью ключевого слова fun, затем идёт имя и круглые скобки с параметрами (пока пустые), затем тело в фигурных скобках.
Важно: функции (как и main) обычно объявляют на уровне файла, то есть не «внутри main». Локальные функции мы разберём позже, а сейчас будем придерживаться классики: функции — отдельно, main — отдельно.
fun printWelcome() {
println("Добро пожаловать в программу!") // Добро пожаловать в программу!
}
fun main() {
printWelcome()
println("Сейчас начнём работать...") // Сейчас начнём работать...
}
Здесь printWelcome() — это просто «команда». Мы вызвали её, и она выполнилась. Обратите внимание на две вещи.
Во-первых, объявление функции само по себе ничего не запускает. Пока вы не вызвали printWelcome() из main, программа не обязана даже вспоминать, что такая функция существует.
Во-вторых, имя функции — это не место для фантазии. Нормальные имена начинаются с действия: printWelcome, readAge, calculateTotal. С ними код начинает читаться почти как текст.
Параметры и аргументы
Почти сразу хочется сделать такую функцию, чтобы она могла работать с разными данными. Для этого существуют параметры.
Параметры — это переменные, которые функция получает на вход. Они объявляются в круглых скобках и обязаны иметь тип. Аргументы — это конкретные значения, которые вы передаёте при вызове.
Давайте я оформлю вам эти факты в небольшую таблицу:
| Термин | Где живёт | Пример | Как понимать |
|---|---|---|---|
| Параметр | в объявлении функции | |
«что функция ожидает на вход» |
| Аргумент | в вызове функции | |
«что реально передали» |
Теперь пример. Сделаем функцию, которая печатает приветствие для конкретного пользователя:
fun greet(name: String) {
println("Привет, $name!") // Привет, Kotlin!
}
fun main() {
greet("Kotlin")
greet("мир")
}
Здесь name: String — параметр, а "Kotlin" и "мир" — аргументы. Обратите внимание: параметр name доступен только внутри тела функции. Функция завершилась — параметр исчез.
Функции с несколькими параметрами
Когда параметров несколько, они перечисляются через запятую. И тут начинают появляться первые «человеческие» ошибки: перепутать порядок аргументов легче, чем кажется (особенно если тип одинаковый, например два Int).
Сделаем функцию, которая печатает «рамку» для заголовка. Это выглядит как мелочь, но такие мелочи отлично тренируют мозг: вы сразу видите пользу от «дать имя повторяющемуся куску кода».
fun printTitle(title: String, width: Int) {
println("=".repeat(width))
println(title)
println("=".repeat(width))
}
fun main() {
printTitle("Отчёт", 10)
}
Если бы вы случайно вызвали printTitle(10, "Отчёт"), компилятор не дал бы скомпилировать программу, потому что типы не совпадают (и это хорошо). А вот если бы было printTitle(width: Int, height: Int) — компилятор бы пропустил, а логика бы сломалась. Поэтому говорящие имена параметров — это не только эстетика, но и защита от ошибок.
2. Возвращаемые значения и сигнатуры
Возвращаемый тип: функция как «выражение», а не просто «действие»
До этого мы делали функции, которые что-то печатают. Но очень часто функция должна посчитать и отдать результат обратно. Тогда говорят, что функция «возвращает значение».
В Kotlin возвращаемый тип пишется после списка параметров через двоеточие: : Int, : String, : Boolean и так далее. Пример из базового синтаксиса Kotlin показывает функцию sum(a: Int, b: Int): Int, которая возвращает сумму.
fun sum(a: Int, b: Int): Int {
return a + b
}
fun main() {
val total = sum(10, 20)
println(total) // 30
}
Старайтесь не пугаться слова return: в следующей лекции мы разберём, как именно функция «завершается» и что значит «вернуть значение». Пока зафиксируем главное: если у функции указан возвращаемый тип : Int, то результат вызова можно присвоить переменной и использовать дальше.
Важное наблюдение: вызов функции, которая возвращает значение, — это выражение. То есть его можно подставить внутрь строки или другого вычисления.
fun sum(a: Int, b: Int): Int {
return a + b
}
fun main() {
println("2 + 3 = ${sum(2, 3)}") // 2 + 3 = 5
}
Мы буквально вставили результат функции в string template. Вы это уже умеете делать с переменными; теперь учимся делать с функциями.
Сигнатура функции как контракт
Когда вы видите функцию, полезно уметь воспринимать её заголовок как контракт. Это сильно помогает, когда код становится больше, а вы уже не помните, что где написано.
Возьмём пример:
fun average(total: Int, days: Int): Double {
return total.toDouble() / days
}
Её сигнатура — это:
- имя: average
- параметры: (total: Int, days: Int)
- возвращаемый тип: Double
И это уже почти всё, что вам нужно, чтобы пользоваться функцией. Даже не читая тело, можно понять: «дайте мне total и days, я верну Double».
Можно представить это небольшой схемой:
flowchart LR
A["Аргументы в вызове<br/>average(7000, 7)"] --> B["Параметры функции<br/>total: Int, days: Int"]
B --> C["Тело функции<br/>вычисления"]
C --> D["Результат вызова<br/>Double"]
Если вы умеете читать сигнатуры, вы начинаете программировать быстрее: меньше прыгаете глазами по файлам, меньше полагаетесь на интуицию, больше — на контракт.
Параметры внутри функции — только для чтения
У новичков часто возникает желание сделать так: «я получил параметр, сейчас его подправлю и верну». Но Kotlin не даёт переназначать параметры. Это сделано намеренно: параметры — входные данные, а не рабочие переменные. Воспринимайте их как val-переменные. Ведь именно так их воспринимает компилятор Kotlin.
То есть вот так нельзя (компилятор ругнётся):
fun makeLouder(text: String): String {
text = text.uppercase() // Ошибка компиляции: Val cannot be reassigned
return text.uppercase()
}
Если вам нужно что-то менять, создайте локальную переменную:
fun normalizeName(name: String): String {
var result = name.trim()
result = result.replaceFirstChar { it.uppercase() }
return result
}
fun main() {
println(normalizeName(" kotlin ")) // Kotlin
}
Тут name — входные данные, а result — рабочая зона.
3. Мини-приложение «Трекер шагов»
Сейчас мы соберём небольшой, но цельный пример, который поможет вам собрать всё изученное вместе. Пусть это будет консольный «трекер шагов за неделю»: программа спрашивает шаги за каждый день, сохраняет в массив и печатает итог.
Раньше вы бы написали всё в main, а теперь начнём выносить повторяющиеся шаги в функции. Мы сознательно делаем код простым, чтобы фокус был на функциях, а не на хитрых приёмах.
Функция-помощник для печати заголовка
Когда программа растёт, приятно иметь единый стиль вывода.
fun printSection(title: String) {
println()
println("=== $title ===")
}
fun main() {
printSection("Трекер шагов")
}
Функция, которая читает число
Валидацию ввода мы будем делать позже, но уже сейчас можно вынести «попросить и прочитать» в отдельную функцию.
fun readInt(prompt: String): Int {
print(prompt)
val raw = readln().trim()
val number = raw.toIntOrNull()
if (number == null) return 0
return number
}
fun main() {
val x = readInt("Введите число: ")
println("Вы ввели: $x") // например: Вы ввели: 12
}
Как работает return мы разберём чуть позднее, сейчас же воспринимайте это как «функция обязана отдать Int, поэтому в конце отдаёт число».
Функция чтения шагов за день
Теперь оборачиваем «ввод шагов за конкретный день» в функцию с параметром dayName.
fun readStepsForDay(dayName: String): Int {
return readInt("Шаги за $dayName: ")
}
fun main() {
val mondaySteps = readStepsForDay("Понедельник")
println("Понедельник: $mondaySteps") // например: Понедельник: 5400
}
Заметьте, как код начинает читаться по-человечески: readStepsForDay("Понедельник") — почти предложение.
Функция подсчёта суммы по массиву
У нас есть массив IntArray. Мы уже умеем проходить его циклом и накапливать сумму. Теперь дадим этому действию имя.
fun sumSteps(steps: IntArray): Int {
var total = 0
for (x in steps) {
total += x
}
return total
}
fun main() {
val data = intArrayOf(3000, 5000, 4500)
println(sumSteps(data)) // 12500
}
Эта функция полезна не только в трекере шагов: она также пример того, как функция упаковывает длинный цикл и возвращает итог.
Собираем всё в один сценарий main
Соберём минимальный рабочий «трекер недели» из маленьких функций. Этот кусок будет длиннее чем обычно — это нормально, потому что main по задумке и должен быть сценарием, который читает человек.
fun printSection(title: String) {
println()
println("=== $title ===")
}
fun readInt(prompt: String): Int {
print(prompt)
val raw = readln().trim()
val number = raw.toIntOrNull()
if (number == null) return 0
return number
}
fun readStepsForDay(dayName: String): Int {
return readInt("Шаги за $dayName: ")
}
fun sumSteps(steps: IntArray): Int {
var total = 0
for (x in steps) total += x
return total
}
fun main() {
printSection("Трекер шагов за неделю")
val days = arrayOf("Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс")
val steps = IntArray(7)
for (i in days.indices) {
steps[i] = readStepsForDay(days[i])
}
val total = sumSteps(steps)
println("Итого шагов: $total") // например: Итого шагов: 42000
}
Что здесь важно и интересно:
- У каждой функции есть понятное имя и небольшой объём.
- Параметры показывают «что нужно для работы»: dayName, prompt, steps.
- Возвращаемый тип показывает «что функция отдаёт»: Int.
- main перестал быть свалкой деталей и превратился в сценарий: «покажи заголовок → собери данные → посчитай → выведи».
4. Типичные ошибки при объявлении и вызове функций
Ошибка №1: забыли указать тип параметра.
Новичков часто тянет написать fun greet(name) { ... }, как в некоторых языках. Kotlin так не умеет: тип параметра обязателен. Правильно fun greet(name: String) { ... }. Это сначала раздражает, а потом спасает от очень странных багов, когда «не то передали и не туда».
Ошибка №2: перепутали параметры и аргументы (и поэтому путаетесь в объяснениях).
Это кажется терминологией ради терминологии, но на практике помогает. Параметры — это то, что объявлено в функции, аргументы — то, что вы реально передали в вызове. Если вы называете всё «параметрами», в какой-то момент вы сами себе не сможете объяснить, что именно не совпало.
Ошибка №3: пытаетесь изменить параметр внутри функции.
После опыта с var хочется «подкрутить входное значение» прямо в параметре. Kotlin не разрешает переназначать параметры: они ведут себя как val. Если нужно менять — создайте локальную var и меняйте её. Код станет чуть длиннее, но в разы понятнее.
Ошибка №4: функция делает слишком много всего сразу.
Когда вы только начинаете, кажется удобным написать fun doAll() — «и ввод, и подсчёт, и печать». Но тогда функцию невозможно переиспользовать: вы хотели «только посчитать», а она ещё и печатает; хотели «только спросить», а она уже лезет в массив. Хорошее правило на старте: одна функция — один понятный шаг сценария или одно вычисление.
Ошибка №5: имена уровня f1, calc, doIt, потому что “и так понятно”.
Пока программа 20 строк — да, «и так понятно». Когда станет 200 — уже нет. Имя функции должно отвечать на вопрос «что она делает?» или «что она возвращает?». sumSteps читается без комментариев, а calc2 — как загадка из квеста.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ