Функции в программировании
Мы уже не раз сталкивались с функциями, вызывая их в своих программах. Это были встроенные в стандартную библиотеку Kotlin функции, код которых нас не интересовал. Нам было не важно как они работают, достаточно было знать как их вызвать, и что они в итоге делают.
Когда же подходящей нам встроенной функции нет, мы всегда можем написать свою. Такие функции называют пользовательскими. По-сути функция в программировании представляет собой кусок программного кода, выполняющий определенную задачу и вызываемый, когда требуется, по имени.
Функции, как и циклы, позволяют многократно делать одно и то же. Однако, если тело цикла выполняется сразу множество раз подряд, тело функции исполняется однажды. Просто мы может затребовать ее исполнение в разное время и из разных мест программы, обратившись к функции по имени, то есть вызвав ее. Кроме того, поведение одной и той же функции можно менять, передавая в нее разные аргументы.
Объявление функции в Kotlin начинается с ключевого слова fun
, за которым идет имя функции, после которого в круглых скобках указываются параметры. Тело функции заключается в фигурные скобки.
import kotlin.random.Random fun main() { val nums: Array<Int> = Array(10, {it}) printArray(nums) fillArray(nums, 0, 3) printArray(nums) } fun printArray(a: Array<Int>) { for (i in a) print(" $i ") println() } fun fillArray(a: Array<Int>, low: Int, high: Int) { for (i in a.indices) a[i] = Random.nextInt(low, high) }
Пример выполнения программы:
0 1 2 3 4 5 6 7 8 9 1 0 0 1 2 1 1 0 0 1
В программе, не считая главной функции main()
, определены еще две – printArray()
и fillArray()
. Первая выводит массив на экран в определенном формате, вторая – заполняет массив случайными числами. Исходно, с помощью выражения Array(10, {it})
, массив был заполнен индексами самого массива.
В теле main()
мы два раза вызываем printArray()
и один раз fillArray()
.
Когда функция вызывается, поток выполнения программы переходит из места ее вызова к месту ее определения и начинает выполнять тело уже этой, вызванной, функции. Когда тело функции выполнено, поток выполнения программы возвращается в то место, откуда функция вызывалась. Точнее, сразу после места вызова функции.
В нашем примере функция printArray()
имеет один параметр – это переменная a типа Array<Int>
. Параметры функции указываются в круглых скобках в заголовке. Если параметров несколько, как в случае с fillArray()
, то между собой они разделяются запятыми. Параметров у функции может не быть.
Когда функция вызывается, то в нее передаются аргументы. Количество и тип аргументов должен соответствовать количеству и типам параметров функции. Таким образом, a – это переменная-параметр функции. Переменная nums – это аргумент, передаваемый в функцию.
На самом деле в функцию передается не сама переменная nums. Ни fillArray()
, ни printArray()
ничего не знают об этой переменной. Если вы попробуете оттуда обратиться к ней за ее значением, получите ошибку.
Unresolved reference – неразрешимая ссылка, функции непонятно, на что ссылается переменная nums, для нее nums не существует. IntelliJ IDEA предлагает создать в функции собственную локальную версию nums. Если так сделать, это будет другая nums, а не та, что в функции main()
.
Как же массив, определенный в одной функции, оказывается в другой? Массив – это объект в памяти. Переменная nums играет роль указателя, ссылки на него. Когда мы вызываем функцию printArray()
и в скобках записываем nums, то на самом деле в функцию передается не переменная, связанная с объектом, а ссылка на этот объект. Уже в функции эта ссылка связывается с параметром-переменной a.
Таким образом, на один и тот же объект в памяти указывают уже две переменные – nums в функции main()
и a – в printArray()
. Это сравнимо с тем, как на одно и то же место могут указывать разные указатели, расположенные в разных местах.
Если мы меняем объект, как это происходит в функции fillArray()
, через одну переменную, то обратившись к этому объекту через другую, увидим эти изменения. Объект-то один.
Иначе обстоит дело, когда передаются неизменяемые типы, в том числе примитивные. Тут либо из переменной-аргумента в переменную-параметр копируется непосредственно значение, то есть в памяти появляются, например, два числа, либо все же копируется ссылка. Но в таком случае поскольку объект изменять нельзя, никаких изменений с ним в функции не произойдет по определению. Более того, параметры в Kotlin – это val
переменные, то есть им нельзя присваивать новое значение.
С другой стороны, из функций можно возвращать значения, которые могут быть присвоены переменным в том месте программы, откуда функция вызывалась. Например, когда мы вызывали функцию readln()
, то присваивали то, что она возвращает, переменной.
val s = readln()
Для возврата значения из функции используется оператор return
. Чтобы в объявлении функции показать, что она возвращает определенный тип данных, в заголовке после круглых скобок ставят двоеточие и указывают возвращаемый тип.
fun main() { val num = readln().toInt() val sumDigits = countDig(num) println(sumDigits) } fun countDig(int: Int): Int { var i = int var sum = 0 while (i > 0) { sum = sum + i % 10 i = i / 10 } return sum }
Пример выполнения программы:
378 18
Функция countDig()
считает сумму цифр переданного ей числа и возвращает ее из себя. Знак процента по отношению к целочисленным операндам выполняет операцию нахождения остатка от деления первого на второй. При таком делении на 10 извлекается последняя цифра числа (i % 10)
. Далее добавляем ее к сумме, а полученное новое значение присваиваем переменной sum. Операция деления по отношению к целым числам делит их нацело, то есть выражение i / 10
избавляет i от последней цифры, которая уже была добавлена к сумме.
Выражение return sum
осуществляет выход из функции и передачу в место вызова функции значения переменной sum. Там это значение может быть присвоено своей локальной переменной. Так в программе выше результат работы countDig()
присваивается переменной sumDigits.
На самом деле в Kotlin не существует функций, которые вообще ничего не возвращают. Если функция явно не возвращает из себя никаких значений, значит по-умолчанию она возвращает объект Unit
, что можно трактовать как "ничего", однако путать его с null
не следует.
Мы можем присвоить этот объект переменной, хотя смысла в этом нет.
Практическая работа:
Напишите функцию, которой в качестве аргумента передается массив, и из которой возвращается словарь, в котором индексы массива становятся ключами, а элементы массива – значениями.
PDF-версия курса с ответами к практическим работам