Перейти к основному содержимому

Методы

Фрагмент программного кода, к которому можно обратиться из другого места программы, называется методом. Данный раздел содержит особенности описания и использования методов.

Общая информация

При разработке программы часто возникает необходимость повторного использования какого-либо ее фрагмента. Для простого повторения одного и того же фрагмента кода несколько раз можно использовать цикл. А если этот фрагмент вызывается из разных мест программы, необходимо выделить программный код и оформить его специальным образом. Такой обособленный фрагмент кода называется метод. Метод может как возвращать значение, так и не возвращать ничего (подробнее).

Используемые определения

Для описания методов будут использоваться следующие термины:

  • Определение метода

    Место в исходном коде, где создан метод.

  • Сигнатура метода

    Уникальный идентификатор метода, состоящий из области видимости метода, модификатора, имени метода, списка параметров и типа возвращаемого значения.

  • Объявление метода

    Часть определения метода, которая содержит его сигнатуру.

  • Имя метода

    Идентификатор, расположенный после ключевого слова метод, используемый для его объявления и вызова.

  • Тело метода

    Инструкции, расположенные между объявлением метода и символом ;, описывающим завершение метода.

  • Вызов метода

    Место в тексте программы, где указывается имя метода с указанием фактических параметров и использованием результата работы, если метод возвращает значение.

  • Определение параметров и передача аргументов

    • Параметр метода — это именованная переменная, через которую тело метода получает значение при вызове.
    • Аргумент — это значение, которое указывается во время вызова метода.
    • Передача параметров — это предоставление аргументов методу при его вызове.
  • Возврат значения

    Передача вызывающему программному коду какого-либо значения при завершении работы метода. Метод не обязательно должен возвращать какое-либо значение.

Определение метода

Прежде чем использовать метод, его надо определить. Определение метода выглядит следующим образом:

@аннотация-видимости
метод имя-метода([параметр[, параметр[, ...]]])[: тип-возвращаемого-значения]
тело-метода
[возврат[ значение]]
;

Каждый параметр описывается следующим образом:

имя-параметра: тип-параметра[ = значение-по-умолчанию]

имя-параметра задает имя параметра в соответствии со стандартными правилами формирования имен.

тип-параметра указывает, соответственно, тип параметра (в том числе и составной).

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

  • Литералы (включая литералы коллекций) являются вычислимыми.
  • Обращения к константам модулей являются вычислимыми.
конст МЕТРОВ_В_КИЛОМЕТРЕ: Число = 1000 

// Вычислимые значения по умолчанию: литерал и константа
метод МетровВКилометре(Сообщение: Строка = "В километре ", Число: Число = МЕТРОВ_В_КИЛОМЕТРЕ): Строка
возврат Сообщение + Число + " метров"
;

Операции над вычислимыми значениями являются вычислимыми, кроме следующих исключений:

  • вызовы методов и обращения к свойствам,
  • вызовы конструкторов.
конст МЕТРОВ_В_КИЛОМЕТРЕ: Строка = "1000"

// Выражение невозможно вычислить на этапе компиляции (вызов метода)
метод СообщитьЧасовойПояс(Пояс: ЧасовойПояс = ЧасовойПояс.Текущий()): Строка
возврат "Текущий часовой пояс $Пояс"
;

// Выражение невозможно вычислить на этапе компиляции (вызов конструктора)
метод МетровВКилометре(Число: Число = новый Число(МЕТРОВ_В_КИЛОМЕТРЕ)): Строка
возврат "В километре $Число метров"
;

Особенности передачи параметров

Переменные хранят ссылки на экземпляры. В методы передаются значения переменных. Таким образом можно в методе изменять состояние экземпляра, но нельзя изменить значение переменной, передаваемого в качестве аргумента метода.

метод ТестовыйМетод(Параметр: Число)
Параметр = 6
;

метод Скрипт(): Строка
пер ЧислоДляТеста = 5
ТестовыйМетод(ЧислоДляТеста)
пер ПослеВызова = "ДляТеста = $ЧислоДляТеста"
возврат ПослеВызова // ДляТеста = 5
;

В данном примере значение переменной ЧислоДляТеста в методе Скрипт() будет равно 5 и до вызова метода ТестовыйМетод(), и после вызова этого метода. Такое поведение обеспечивает передача параметра «по значению».

Однако, если в метод передается экземпляр, который располагает методами для изменения своего содержимого, то ситуация будет немного другой. Рассмотрим пример:

метод ОчиститьМассив(Параметр: Массив<Число>)
Параметр.Очистить()
// Не вызовет изменений
Параметр = [1, 2, 3]
;

метод Скрипт(): Строка
пер МассивДляТеста = [1, 2]
ОчиститьМассив(МассивДляТеста)
пер ПослеВызова = "ДляТеста = " + МассивДляТеста
возврат ПослеВызова // ДляТеста = []
;

В этом примере до выполнения метода ОчиститьМассив() в массиве МассивДляТеста будет два значения: 1 и 2. После вызова метода ОчиститьМассив() в массиве не будет ни одного элемента. В то же время, если в методе ОчиститьМассив() мы попробуем присвоить параметру Параметр какое-либо значение, это не приведет к каким-либо изменениям аргумента (переменной МассивДляТеста).

Вызов метода с именованными параметрами

При вызове метода допускается передача не только аргументов в соответствии с их позицией в сигнатуре, то есть позиционных аргументов, но и передача аргументов вместе с их именами — именованных аргументов. Например:

метод Поделить(А: Число, Б: Число): Число
возврат А / Б
;

метод Скрипт()
// Передача аргументов по именам
Поделить(Б = 10, А = 0) // 0
Поделить(А = 10, Б = 0) // Ошибка: деление на ноль
;

Синтаксис:

имя-метода(имя-параметра-1 = значение, имя-параметра-2 = значение, ... , имя-параметра-N = значение)
примечание

Нумерация параметров в данном случае является условной, используется для отличия параметров друг от друга и не указывает на порядковый номер параметра при определении или при вызове метода.

Прежде всего, могут быть методы, которые не допускают использования позиционных параметров. В этом случае перед ними указывается аннотация @ИменованныеПараметры. Подробнее об аннотациях смотрите здесь.

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

  • Передавать аргументы не обязательно в строгом порядке. Пользователю не требуется искать сигнатуру метода, чтобы вспомнить строгий порядок аргументов. Кроме того, если во время написания кода разработчик поменяет местами параметры в сигнатуре, в том числе параметры одного типа, это не приведет к ошибке во время компиляции или исполнения кода. Сравните:

    метод Разделить(А: Число, Б: Число): Число
    возврат А / Б
    ;

    метод Скрипт()
    // Вызовы ниже равносильны:
    Разделить(3, 2) // 1.5
    Разделить(А = 3, Б = 2) // 1.5
    Разделить(Б = 2, А = 3) // 1.5
    ;

    или:

    метод РассчитатьСебестоимость(Цена: Число, Наценка: Число, Налог: Число): Число
    возврат Цена - (Цена * Наценка / 100 + Цена * Налог / 100)
    ;

    метод Скрипт()
    // Позиционные параметры
    // Результат: -0.1. Неправильно
    РассчитатьСебестоимость(1, 10, 100)
    // Именованные параметры
    // Результат: 89. Правильно
    РассчитатьСебестоимость(Налог = 1, Наценка = 10, Цена = 100)
    ;
  • Имена параметров отображаются сразу же при вызове метода. В таком случае не требуется искать сигнатуру метода, чтобы вспомнить, какой параметр что означает. См. пример выше или:

    // Сигнатура метода
    метод Сообщить(Сообщение: Строка, ПеревестиВВерхнийРегистр: Булево): Строка
    если ПеревестиВВерхнийРегистр == Истина
    возврат Сообщение.ВВерхнийРегистр()
    иначе
    возврат Сообщение
    ;
    ;

    метод Скрипт()
    // Непонятно значение второго параметра
    Сообщить("Привет, мир!", Ложь) // "Привет, мир!"
    // Значение Истина передается в параметр ПеревестиВВерхнийРегистр
    Сообщить("Привет, мир!", ПеревестиВВерхнийРегистр = Истина) // ПРИВЕТ, МИР!
    ;
  • Можно выборочно указывать только некоторые, наиболее важные параметры. Это может быть особенно удобным, когда в методе используется много необязательных, «служебных» параметров. Для них будут использоваться значения по умолчанию, которые указываются при определении метода. Кроме того, для некоторых параметров уже могут быть указаны значения по умолчанию. Так, если для метода РассчитатьСебестоимость() задать значения по умолчанию: для наценки — 10 (%), а для налога — 1 (%), то можно передавать только значение цены. Сравните:

    метод РассчитатьСебестоимость(Налог: Число = 1, Наценка: Число = 10, Цена: Число): Число
    возврат Цена - (Цена * Наценка / 100 + Цена * Налог / 100)
    ;

    метод Скрипт()
    // Позиционные параметры
    // Ошибка в следующей строке: Параметр Цена не имеет значения по умолчанию
    РассчитатьСебестоимость(100)
    // Именованные параметры
    РассчитатьСебестоимость(Цена = 100) // Результат: 89
    ;

Смешанная параметризация

При вызове метода можно смешивать позиционную и именованную формы параметризации. Например:

метод РассчитатьСебестоимость(Налог: Число = 1, Наценка: Число = 10, Цена: Число): Число
возврат Цена - (Цена * Наценка / 100 + Цена * Налог / 100)
;

метод Скрипт()
// Вызовы метода ниже равносильны
РассчитатьСебестоимость(Налог = 1, Наценка = 10, Цена = 100)
РассчитатьСебестоимость(1, 10, Цена = 100)
;

При смешанной параметризации действуют следующие правила:

  • Если параметр со значением по умолчанию находится перед параметром с обязательным значением, то в вызове при опускании первого параметра требуется указать второй параметр в именованном виде.
  • При вызове все позиционные параметры должны быть указаны до первого именованного параметра.
  • Параметры со значениями по умолчанию, находящиеся в конце сигнатуры, можно опускать при вызовах.
метод РассчитатьСебестоимость(Налог: Число = 1, Наценка: Число, Цена: Число = 100): Число
возврат Цена - (Цена * Наценка / 100 + Цена * Налог / 100)
;

метод Скрипт()
// Параметры Налог и Цена можно опустить, при этом указав имя для обязательного параметра Наценка
РассчитатьСебестоимость(Наценка = 10)
// После указания именованного параметра все последующие параметры должны быть именованные
// В параметр Цена нельзя передать аргумент, не указав его имя
РассчитатьСебестоимость(Наценка = 10, Цена = 100)
;

Разрешение неоднозначности

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

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

    метод Обработать(А: Число, Б: Строка): Строка
    возврат"Версия 1: ${А}, ${Б}"
    ;

    метод Обработать(Б: Строка, А: Число): Строка
    возврат "Версия 2: ${А}, ${Б}"
    ;

    метод Скрипт()
    // Позиционный вызов — работает
    Обработать(10, "текст") // Версия 1: 10, текст

    // Именованный вызов — ошибка неоднозначности
    Обработать(А = 10, Б = "текст")
    ;
  • При объявлении методов проверка на возможную неоднозначность вызовов в именованной форме не производится, в отличие от позиционной:

    // Проверка на возможную неоднозначность вызовов в именованной форме не производится
    метод Тест(Б: Строка, А: Число): Строка
    возврат "Версия 1: ${А}, ${Б}"
    ;

    // Перегрузка метода Тест() допускает неоднозначный вызов в позиционной форме
    метод Тест(А: Число, Б: Строка): Строка
    возврат "Версия 1: ${А}, ${Б}"
    ;

    метод Тест(А: Число, Б: Строка): Строка
    возврат "Версия 1: ${А}, ${Б}"
    ;
  • Некоторые перегрузки метода не допускают использования именованной формы:

    метод Конкатенация(А: Число, Б: Строка): Строка
    возврат А.ВСтроку() + Б
    ;

    метод Конкатенация(Б: Строка, А: Число): Строка
    возврат А.ВСтроку() + Б
    ;

    метод Скрипт()
    // Неоднозначный вызов метода Конкатенация() с именованными параметрами
    Конкатенация(А = 10, Б = "текст")
    ;
  • При этом из нескольких перегрузок, которые соответствуют именованному вызову метода, будет выбрана перегрузка, у которой меньше всего аргументов:

    // Первая перегрузка
    метод Сообщить(Число: Число, Сообщение = "Это число: ", ПосчитатьДлину = Истина): Строка
    если ПосчитатьДлину == Истина
    возврат "Длина сообщения: ${(Сообщение+Число).Длина()}"
    иначе
    возврат Сообщение + " " + Число
    ;
    ;

    // Вторая перегрузка
    метод Сообщить(Число: Число, ПосчитатьДлину: Булево): Строка
    если ПосчитатьДлину == Истина
    возврат "Длина сообщения: ${Число.ВСтроку().Длина()}"
    иначе
    возврат Число.ВСтроку()
    ;
    ;

    // Какая перегрузка будет выбрана?
    метод Скрипт()
    // Первая — у второй есть обязательный параметр УдалитьПробелы
    Сообщить(255)
    // Вторая — используется позиционная форма вызова
    Сообщить(255, Истина)
    // Первая — в сигнатуре второй перегрузки второй параметр имеет тип Булево, а не Строка
    Сообщить(255, "Это число: ")
    // Первая — только она имеет все три подходящих параметра
    Сообщить(255, "Это число: ", Истина)
    // Вторая — она короче, ошибки неоднозначности нет
    Сообщить(Число = 255, ПосчитатьДлину = Ложь)
    ;

Завершение метода и возвращаемое значение

Метод завершает свою работу после того, как завершается исполнение тела метода.

метод ОчиститьМассив(Массив: Массив<Объект>)
// Метод завершит работу после очистки массива
Массив.Очистить()
;

Другой способ прервать исполнение метода — это указать в необходимом месте метода ключевое слово возврат.

метод ПроверитьЧетность(Число: Число): Строка
если Число == 0
// Выполнение метода прервется при передаче 0
возврат "Ноль не считается"
;

если Число % 2 == 0
// Выполнение метода прервется, если число четное
возврат "Четное"
;

// Выполнение метода завершится, если число нечетное
возврат "Нечетное"
;

метод Скрипт()
ПроверитьЧетность(0) // "Ноль не считается"
ПроверитьЧетность(4) // "Четное"
ПроверитьЧетность(7) // "Нечетное"
;

Если метод должен вернуть какое-либо значение, необходимо это значение указать в качестве параметра ключевого слова возврат. При этом тип фактически возвращаемого значения должен совпадать с типом возвращаемого значения, который указан при описании метода. Тип возвращаемого значения указывается после окончания списка формальных параметров метода после символа двоеточия. Если метод ничего не возвращает, можно использовать в качестве типа возвращаемого значения метода ключевое слово ничто (подробнее).

метод ОчиститьМассив(Массив: Массив<Объект>): ничто
// Метод ничего не возвращает
Массив.Очистить()
;

Для методов, смысл которых заключается в возврате значений, можно использовать аннотацию @ПроверятьИспользованиеЗначения. Если метод помечен этой аннотацией и возвращаемое им значение не используется, компилятор выдает ошибку. Пример:

@ПроверятьИспользованиеЗначения
метод СгенерироватьЧисло(): Число
возврат 42
;

метод Тест(): Число
// Ошибка компиляции: возвращаемое значение не используется
СгенерироватьЧисло()
// Правильное использование — значение присваивается
пер Число = СгенерироватьЧисло()
возврат Число
;

Перегрузка методов

При разработке может возникнуть необходимость создать метод, который в зависимости от задачи мог бы принимать разное количество или различные типы параметров. При этом выполняемое действие также зависело бы от их количества или типов. Такое поведение можно реализовать, например, используя составные типы параметров или значения параметров по умолчанию, но специально для таких задач существует перегрузка или повторное определение метода. Другими словами, такой способ можно рассматривать как описание нескольких методов с одним и тем же именем, но разными сигнатурами и особенностями реализации. Метод, который имеет несколько объявлений с разным составом параметров, будет называться перегруженным. При этом нужно помнить, что любой вызов перегруженного метода в исходном коде должен позволять компилятору однозначно определить, какой вариант метода необходимо использовать в данном случае. Если определить используемый вариант метода во время компиляции не представляется возможным, произойдет ошибка компиляции. Перегруженные методы не могут различаться только типом возвращаемого значения.

Основные правила определения перегруженных методов:

  • Нельзя использовать составные типы, если хотя бы один из перечисленных типов дублирует тип перегруженного параметра:

    // Некорректные перегрузки из-за дублирования типа Строка
    метод Обработать(Данные: Строка|Число|Булево): Строка
    возврат "Передан тип ${Данные.ПолучитьТип()}"
    ;

    метод Обработать(Данные: Строка): Строка
    возврат "Принимает только тип ${Данные.ПолучитьТип()}"
    ;

    // В методах ниже типы не дублируются — такое объявление корректно
    метод ПравильноОбработать(Данные: Число|Строка): Строка
    возврат "Принимает только тип ${Данные.ПолучитьТип()}"
    ;

    метод ПравильноОбработать(Данные: Булево|Неопределено): Строка
    возврат "Принимает только тип ${Данные.ПолучитьТип()}"
    ;
  • Нельзя использовать тип Объект в качестве типа перегруженного параметра, т. к. тип Объект является базовым для всех типов:

    // Некорректные перегрузки из-за использования типа Объект
    метод Обработать(Данные: Объект): Строка
    возврат "Принимает только тип ${Данные.ПолучитьТип()}"
    ;

    метод Обработать(Данные: Строка): Строка
    возврат "Принимает только тип ${Данные.ПолучитьТип()}"
    ;
  • Параметры со значением по умолчанию не учитываются компилятором при определении неоднозначности перегрузки:

    метод Обработать(Данные: Строка|Число|Булево): Строка
    возврат "Передан тип ${Данные.ПолучитьТип()}"
    ;

    // Корректная перегрузка — используется отличное от первого метода количество параметров
    метод Обработать(Данные: Строка, Количество: Число, Флаг: Булево): Строка
    возврат "Список типов параметров: ${Данные.ПолучитьТип()}, ${Количество.ПолучитьТип()}, ${Флаг.ПолучитьТип()}"
    ;

    // Перегрузка метода Обработать() допускает неоднозначный вызов,
    // т.к. компилятор не учитывает параметры со значением по умолчанию
    метод Обработать(Данные: Строка, Количество: Число = 22): Строка
    возврат "Список параметров: ${Данные.ПолучитьТип()}, ${Количество.ПолучитьТип()}"
    ;
  • Перегруженные методы не могут отличаться только типом возвращаемого значения:

    метод Обработать(Данные: Строка): Строка
    возврат "Передан тип ${Данные.ПолучитьТип()}"
    ;

    // Перегрузка метода Обработать() допускает неоднозначный вызов
    метод Обработать(Данные: Строка): Число
    возврат новый Число(Данные)
    ;

Также стоит отметить несколько особенностей, касающихся перегруженных методов:

  • Основное назначение перегрузки метода — это удобство разработчика.
  • Если описываемый алгоритм изначально ориентирован на то, что в качестве параметров передаются составные типы, не нужно выполнять перегрузку такого метода.
  • Если алгоритм, описываемый методом, отличается от типа переданного параметра, то стоит рассмотреть возможность перегрузки метода, реализующего такой алгоритм.
  • Перегруженными могут быть не только методы, но и конструкторы.