Функцией в программировании называется законченный участок программы, решающий определённую задачу — обычно это часть большой задачи, которую решает программа в целом, хотя в простых случаях написание программы сводится к написанию одной функции. Как и у функции в математическом смысле, у функции в программировании есть входы (параметры), выход (результат) и определение, указывающее, как рассчитывается значение выхода по заданным значениям входов.

Определение простых функций на Котлине мало отличается от определения математических функций. Рассмотрим для примера математическую функцию sqr(x) = x2. На Котлине она будет записана так:

В этом определении fun является ключевым словом, с которого начинается определение любой функции в Котлине;fun является сокращением от function — функция. sqr — это имя функции, x — параметр функции, = x * x — телофункции, определяющее, как следует вычислять её результат. Имя функции вместе с её параметрами и ключевым словом fun называется её заголовком.

Поскольку операция вычисления квадрата числа в Котлине отсутствует, результат вычисляется как произведение x * x. В Котлине присутствуют ещё ряд стандартных операций, перечислим некоторые из них:

  • + сложение;
  • — вычитание;
  • * умножение;
  • / деление;
  • % остаток от деления;
  • () круглые скобки для повышения приоритета операций.

Строчка, начинающаяся с символов //, является так называемым комментарием — он предназначен для лучшего понимания текста программы человеком и никак не влияет на поведение функции или программы. В Котлине возможны также скобочные комментарии, например:

Типы параметров / результата функций

Нотация : Int или : Double задаёт так называемый тип параметра x и требует пояснения. Тип в программировании подобен области допустимых значений в математике; математически, запись x: Int близка к записи x ∈ Z, а x: Double соответственно x ∈ R. Присутствие в определении функции типов параметров в Котлине обязательно; наличие типов позволяет функции «понять», для каких входов она применима, а для каких её использование является ошибкой.

Заметим, что тип имеется не только у параметров функции, но и у её результата. Полная запись функции возведения в квадрат в Котлине (для целого параметра) выглядит следующим образом:

Здесь нотация : Int перед знаком равенства задаёт тип результата функции. В Котлине в некоторых случаях необязательно записывать тип результата функции явно, поскольку функция сама может вывести этот тип, зная типы своих параметров. В данном случае известно, что произведение целого числа на целое также даст целое число, что позволяет использовать сокращённую форму записи без указания типа результата.

В Котлине определяется некоторое количество стандартных типов; помимо них, программист имеет право определять свои типы, используя так называемые классы (о них речь пойдёт позже). На первых порах нам потребуются следующие стандартные типы:

  • Int — целое число в диапазоне от -231 до 231-1;
  • Double — вещественное число в диапазоне (примерно) от -1.7 * 10308 до 1.7 * 10308, имеющее точность в (примерно) 15 десятичных цифр;
  • Boolean — так называемый логический или булевский тип с ровно двумя возможными значениями — true и false;
  • Char — символ (одиночный), например Z или Я; значением может являться любой символ, присутствующий в таблице «Unicode», с кодом от 0 до 216-1;
  • String — строка, состоящая из любого количества (в том числе из нуля) одиночных символов.

Целые и вещественные числа устроены по-разному. Например, целые числа сохраняются в памяти компьютера точно, а вещественные числа — приближённо. Некоторые операции и функции работают по-разному с целыми и вещественными числами. Например, по правилам Котлина 5 / 2 даёт 2 (деление целых чисел), но 5.0 / 2 (или 5 / 2.0) даёт 2.5 (деление вещественных чисел).

Имена

В приведённых примерах sqr являлось именем функции, а x — именем её параметра. По правилам языка Котлин, любое имя обязано начинаться с буквы или символа _ и может состоять из букв, цифр и символа _. Следует помнить, что Котлин различает строчные и прописные буквы; рекомендуется всегда начинать имена параметров и функций со строчной буквы, а имена типов — с прописной буквы. Имя, состоящее из нескольких слов, обычно записывается в так называемой camel case нотации, например bigNumber. Не рекомендуется:

  • использование букв не из латинского алфавита, например имя;
  • использование символа _ и цифр в именах, например flag_123;
  • использование транслитерации в именах, например dlina.

Литералы

Литералами в программировании называются константы, записываемые прямо в тексте программы и не имеющие отдельных имён. У любого литерала имеется тип, определяемый по его записи. Примеры литералов в Котлине:

  • -7, 0, 42 — литералы типа Int;
  • 0.0, 3.14 или 6.67e-11 — литералы типа Double, обязаны иметь как целую, так и дробную часть, запись с символом e означает 6.67 * 10-11;
  • true или false — литералы типа Boolean
  • ‘Z’ — литерал типа Char, необходимо использовать простые одинарные кавычки;
  • «name» — литерал типа String, необходимо использовать простые двойные кавычки.

Ещё раз повторим, что Int и Double — два принципиально разных типа, а значит, 3 и 3.0 — два принципиально разных числовых литерала. В частности, деление целого числа на 3 будет происходить нацело и даст целый результат, а деление целого числа на 3.0 даёт уже вещественный результат.

Математические функции

В библиотеке Java определено большое количество математических функций, предназначенных для выполнения более сложных операций. Для примера их использования рассмотрим задачу решения квадратного уравнения ax2 + bx + c = 0.

Напомним, что корни квадратного уравнения ищутся по формуле x1,2 = (-b ± √d) / (2a), где d — дискриминант квадратного уравнения — вычисляется как d = b2-4ac. Мы решим эту задачу в упрощённом виде — найти какой-нибудь из двух возможных корней, скажем, тот, в котором в числителе используется знак плюс.

Для начала напишем функцию для расчёта дискриминанта (она ещё пригодится нам в будущем). Для расчёта b2применим уже написанную выше функцию sqr(x: Double).

В приведённой записи b является аргументом функции sqr. Запись вида sqr(b) называется вызовом функции sqr. Подчеркнём отличие параметра и аргумента — параметр определяется внутри функции и имеет определённое имя, в данном случае x, а аргумент передаётся в функцию снаружи и может являться как именем переменной, так и более сложным выражением.

Теперь напишем функцию для поиска корня квадратного уравнения. Для вычисления квадратного корня применим готовую математическую функцию sqrt(x: Double) из математической библиотеки Котлина.

Здесь мы, в свою очередь, используем уже написанную функцию discriminant для поиска дискриминанта, и выражение discriminant(a, b, c), то есть дискриминант уравнения, является аргументом функции sqrt. Это как раз тот случай, когда аргумент является сложным выражением.

Обратите внимание на нотацию kotlin.math. перед именем функции sqrt. Поскольку готовых функций существует очень много, они разбиты на так называемые пакеты и классы внутри пакетов. kotlin.math.sqrt является полнымименем функции вычисления квадратного корня, а sqrt — её коротким именем. Из-за неудобства работы с полными именами, чаще используется следующая запись:

Здесь import — так называемая директива импорта имён, смысл её пояснён в комментарии.

Примеры других функций из kotlin.math:

  • abs(x: Int) или abs(x: Double) — модуль;
  • sqrt(x: Double) — квадратный корень;
  • Double.pow(x: Double) — возведение в степень xy, используется как x.pow(y);
  • sin(x: Double) — синус, cos(x: Double) — косинус, tan(x: Double) — тангенс, все три функции считают, что x задан в радианах;
  • exp(x: Double) — экспонента ex;
  • log(x: Double), log10(x: Double) — соответственно натуральный и десятичный логарифм;
  • min(x: Int, y: Int) или min(x: Double, y: Double) — минимум из двух чисел;
  • max(x: Int, y: Int) или max(x: Double, y: Double) — максимум из двух чисел.

В том же пакете kotlin.math определены константы PI = 3.14…​ и E = 2.718…​.

Переменные в функциях

Выше мы рассмотрели примеры с функциями sqrdiscriminant и sqRoot, вычисление результата в которых занимало одну строчку кода. Однако, в программировании это скорее редкий случай; гораздо чаще расчёт результата функции предполагает реализацию некоторой последовательности вычислений — алгоритма. Для сохранения результатов промежуточных вычислений программисты придумали переменные.

Рассмотрим, например, задачу вычисления произведения двух корней квадратного уравнения. Напомним, что корни квадратного уравнения вычисляются как (-b+√d)/(2a) и (-b-√d)/(2a) соответственно, где d — дискриминант квадратного уравнения. При вычислении произведения удобно вначале сохранить вычисленный корень из дискриминанта в переменной sd, так как он используется при вычислении обоих корней. Затем нужно вычислить оба корня x1 и x2и уже потом рассчитать их произведение. На Котлине это записывается следующим образом:

В этом примере тело функции записано в виде блока в фигурных скобках, в противоположность телу в виде выражения — как в функциях sqr и discriminant выше. Знак равенства при этом убирается и обязательно указывается тип результата функции. В примере присутствуют три промежуточные переменные — dx1x2. Определение промежуточной переменной в Котлине начинается с ключевого слова val (сокращение от value — значение), за которым следует имя переменной и, после знака равенства — её значение. При желании можно также указать тип переменной, например:

Если тип переменной не указан, он определяется автоматически, например, в данном случае он совпадёт с типом результата функции sqrt.

Блок состоит из так называемых операторов (в примере их четыре), выполняющихся по порядку сверху вниз. Преждечем использовать какую-либо переменную, её следует определить. Например, такая запись привела бы к ошибке:

Последний оператор функции, начинающийся с ключевого слова return, определяет значение её результата; returnпереводится с английского как вернуть (результат). Функция quadraticRootProduct в первую очередь вычислит значение переменной sd, используя другие функции discriminant и sqrt. Затем произойдёт вычисление переменных x1 и x2 и лишь в конце — вычисление результата в операторе return.

Для сравнения, приведём запись той же функции, не использующей переменные:

Хотя и записанная в одну строчку, такая функция является гораздо менее понятной, при её написании легко запутаться при расстановке скобок. Кроме того, в ней происходит двухкратное вычисление корня из дискриминанта, чего следует избегать.

Функция println и строковые шаблоны

Начнём с примера — функции, решающей квадратное уравнение и демонстрирующей решение пользователю.

Здесь мы подходим к такой важной части программирования, как взаимодействие с пользователем и вообще с внешним для программы миром. Обратите внимание — в этот момент используемые нами функции начинают отличаться от чисто математических, так как у них появляются побочные эффекты (side effects). Функция в программировании в общем случае не сводится только к зависимости между параметрами и результатом.

Функция println(p) определена в стандартной библиотеке языка Котлин и не требует подключения каких-либо пакетов. Её параметр p может иметь любой тип — так, вызов println(x1) выведет на отдельную строку консолизначение переменной x1. Чаще всего, однако, p является строкой, например, "x1 = $x1 x2 = $x2". В данной строке присутствуют строковые шаблоны $x1 и $x2, состоящие из символа $ и имени переменной (параметра). Вместо них программа автоматически подставит значение соответствующих переменных. Строковый шаблон позволяет также подставить значение сложного выражения, как, например, здесь: "x1 * x2 = ${x1 * x2}". В этом случае выражение записывается в фигурных скобках, чтобы программа имела возможность отследить его начало и конец.

Обратите внимание, что тип результата функции solveQuadraticEquation не указан. Это означает, что функция не имеет результата (в математическом смысле). Такие функции встречаются довольно часто, один из примеров — сама функция println, и их реальный результат сводится к их побочным эффектам — например, выводу на консоль.

Осталось определить — что же такое консоль? В привычной нам операционной системе Windows консоль — это окно или же его часть, которую программа использует для вывода текстовой информации. В Intellij IDEA данное окно можно открыть последовательностью команд View → Tool windows → Run. При запуске программы из операционной системы она сама откроет так называемое «окно терминала», которое будет использоваться программой для вывода текстовой информации.

Главная функция

Наличие главной функции превращает набор отдельных функций в программу, которую можно запустить. Выполнение программы начинается с вызова её главной функции из операционной системы. На Котлине главная функция определяется так:

Единственный параметр args главной функции имеет тип Array<String>, то есть массив строк. О массивах и об использовании параметра args главной функции мы поговорим позже. Результата главная функция не имеет. По правилам Котлина (и Java) она всегда обязана называться main. Для быстрого ввода заголовка главной функции в Intellij IDEA можно ввести в редактор специальную строку psvm с последующим нажатием клавиши Enter.

Данная короткая программа использует функцию quadraticRootProduct, определённую выше, для вычисления произведения корней квадратного уравнения, после чего выводит это произведение на консоль. Для того, чтобы её запустить, в Intellij IDEA достаточно щёлкнуть мышью на зелёный треугольник слева от заголовка функции main. Поскольку корни данного уравнения равны 1.0 и 2.0, после запуска программы на консоли мы увидим строчку

Тестовые функции

Тестовые функции — особый вид функций, предназначенных для проверки правильности работы других функций. Поскольку человеку свойственно ошибаться, программисты изобрели немало способов, как можно проконтролировать правильность программы, как своей собственной, так и написанной другими людьми. Тестовые функции являются одним из таких способов. Рассмотрим пример:

Написание тестовых функций требует подключения к программе одной из библиотек автоматического тестирования, например, библиотеки JUnit. Большинство классов этой библиотеки находятся в пакете org.junit для версии JUnit 4.x или в пакете org.junit.jupiter.api для версии JUnit 5.х.

@Test — это так называемая аннотация, то есть, пометка, используемая для придания функции testSqrдополнительного смысла. В данном случае, аннотация делает функцию testSqr тестовой. Функция assertEqualsпредназначена для сравнения результата вызова некоторой другой функции, например, sqr, с ожидаемым. В приведённом примере она вызывается трижды.

Тестовых функций в проекте может быть много, любая из них запускается так же, как и главная функция — нажатием зелёного треугольника слева от заголовка функции. Тестовые функции выполняются по тем же принципам, что и любые другие, но вызовы assertEquals происходят особым образом:

  • если проверка показала совпадение результата с ожидаемым, функция не делает ничего;
  • в противном случае выполнение тестовой функции завершается и в IDEA появится сообщение, выделенное красным цветом, о неудачном завершении тестовой функции.

Если тестовая функция завершила работу и результаты всех проверок совпали с ожидаемыми, тестовая функция считается завершившейся успешно.

Наконец, что же такое class Tests? По правилам библиотеки JUnit, все тестовые функции обязаны находиться внутри какого-либо класса. О том, для чего нужны классы, мы поговорим позднее. В данном примере для этой цели был создан класс с именем Tests (имя может быть произвольным), и тестовая функция была записана в нём. Зелёный треугольник напротив имени класса позволяет одновременно запустить все тестовые функции в данном классе.

Любая написанная программа или функция всегда требует проверки. Это требование тем важнее, чем сложнее программа или функция. Тестовые функции позволяют доказать правильность работы проверяемой функции, по крайней мере, для некоторых значений её аргументов.

Наряду с тестовыми функциями, может быть использовано и ручное тестирование. Ручное тестирование предполагает вывод результатов функции на консоль и ручную проверку их с ожидаемыми. Для ручного тестирования может быть использована главная функция, например:

В нормальном случае мы должны увидеть на консоли строчки

Ручное тестирование гораздо более трудоёмко и требует от программиста или тестировщика гораздо большего внимания. Поэтому в современном программировании рекомендуется начинать проверку функций с создания тестовых функций, которые запускаются каждый раз при изменении программы и позволяют заметить появившиеся ошибки. Ручное тестирование выполняется существенно реже, обычно перед выпуском новой версии программы. Но об этом позже…​

Упражнения

Откройте файл srс/lesson1/task1/Simple.kt в проекте KotlinAsFirst.

Как скачать и подключить проект KotlinAsFirst смотрите во Введении.

В файле перечислено некоторое количество задач на этот раздел в форме:

Выберите любую из задач. Придумайте её решение и замените = TODO() на тело функции с записью вашего алгоритма (в форме выражения либо в форме блока на ваш выбор). Важно: не следует при этом менять имя функции, количество, имена и типы параметров, тип результата.

Откройте затем файл test/lesson1/task1/Tests.kt, найдите в нём тестовую функцию — её название должно совпадать с названием написанной вами функции. Щёлкните мышью на зелёный значок запуска теста, в нижней части окна IDEA появится окно тестирования. Если задача решена верно, вы увидите в этом окне зелёную надпись All Tests Passed, в противном случае — 1 test failed с описанием проблемы вида:

Ниже вы увидите ссылку на строчку тестовой функции, проверка в которой оказалась неудачной. Expected — это ожидаемое значение результата, а Actual — реально полученное. Исправьте все ошибки и добейтесь прохождения теста.

Внутри файла srс/lesson1/task1/Simple.kt добавьте главную функцию main. Вызовите в ней написанную вами функцию с произвольными аргументами и выведите результат на консоль с помощью println, например:

Запустите главную функцию. Убедитесь, что результат совпадает с ожидаемым вами.

Решите несколько других задач из того же файла. Убедитесь в том, что можете их решать уверенно и без посторонней помощи. После этого вы можете перейти к следующему разделу.

Добавить комментарий