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

Практика 4. Функции, несколько результатов и closures

Цель

Научиться выделять вычисления в функции, использовать несколько возвращаемых значений, variadic-параметры, функции как значения, closures и defer.

Общие требования

  • создайте модуль example.com/practice-04;
  • имена функций и переменных пишите на английском языке;
  • функции вычисления не должны самостоятельно печатать результат;
  • ошибочные входные данные обрабатывайте явно;
  • выполните go fmt ./..., go test ./... и go vet ./....

A. Разминка

Задание 1. Минимум и максимум

Напишите две функции:

func min(a, b int) int
func max(a, b int) int

Вызовите их для положительных, отрицательных и одинаковых чисел.

Задание 2. Чётность

Напишите:

func isEven(number int) bool

В main выведите результат для чисел от -3 до 3.

Задание 3. Деление с проверкой

Создайте функцию:

func divide(dividend, divisor int) (quotient int, remainder int, ok bool)

Если делитель равен нулю, функция должна вернуть нулевые значения и false. Иначе — частное, остаток и true.

Проверьте вызовы:

  • divide(17, 5);
  • divide(20, 4);
  • divide(10, 0).

Задание 4. Разложение времени

Напишите функцию:

func splitSeconds(total int) (hours, minutes, seconds int)

Для отрицательного входа возвращайте нулевые результаты. Примеры:

0 → 0:00:00
65 → 0:01:05
3661 → 1:01:01

Форматирование выполняйте в main, а не внутри функции.


B. Основные задания

Задание 5. Среднее значение

Реализуйте variadic-функцию:

func average(values ...float64) (float64, bool)

Требования:

  • пустой набор возвращает 0, false;
  • непустой — среднее и true;
  • функция ничего не печатает.

Проверьте обычные аргументы и готовый slice:

fmt.Println(average(4, 5, 3))
values := []float64{4.5, 4.7, 4.2}
fmt.Println(average(values...))

Задание 6. Выбор операции

Создайте функции:

func add(a, b float64) float64
func subtract(a, b float64) float64
func multiply(a, b float64) float64

И функцию выбора:

func selectOperation(name string) (func(float64, float64) float64, bool)

Она должна поддерживать add, subtract, multiply. Для неизвестного имени верните nil, false. Перед вызовом результата обязательно проверьте bool.

Задание 7. Функция высшего порядка

Напишите:

func transform(values []int, operation func(int) int) []int

Она возвращает новый slice и не изменяет исходный. Примените её для:

  • удвоения;
  • возведения в квадрат;
  • изменения знака.

Хотя slices подробно изучаются в следующей лекции, здесь достаточно перебора через range и добавления через append.

Задание 8. Closure-счётчик

Реализуйте:

func newCounter(start int, step int) func() int

Каждый вызов возвращённой функции должен увеличивать состояние на step и возвращать новое значение.

Создайте два независимых счётчика:

byOne := newCounter(0, 1)
byTen := newCounter(100, 10)

Докажите выводом, что их состояния независимы.

Задание 9. Порядок defer

Напишите функцию, которая выводит:

start
work
finish 3
finish 2
finish 1

Используйте три defer. Затем создайте эксперимент, показывающий разницу между:

defer fmt.Println(value)

и:

defer func() {
fmt.Println(value)
}()

Кратко объясните результат в README.md.


C. Проект «Операции над задачами»

Продолжите сквозной проект. Пока каждая задача представлена названием и приоритетом в отдельных slices:

titles := []string{
"Подготовить отчёт",
"Проверить почту",
"Исправить ошибку",
"Обновить документацию",
}
priorities := []int{5, 2, 8, 3}

Реализуйте функции:

func validPriority(priority int) bool
func priorityLabel(priority int) (string, bool)
func averagePriority(priorities ...int) (float64, bool)
func countValidPriorities(priorities ...int) int
func formatTask(number int, title string, priority int) (string, bool)

Правила:

  • допустимый приоритет — 1–5;
  • приоритет 1 — низкий;
  • 2–3 — обычный;
  • 4 — высокий;
  • 5 — критический;
  • некорректные приоритеты не входят в среднее;
  • formatTask не печатает, а возвращает готовую строку;
  • если title пуст после strings.TrimSpace, задача считается некорректной;
  • если длины titles и priorities различаются, программа выводит ошибку и завершает работу до обхода данных.

Пример вывода:

1. Подготовить отчёт [критический]
2. Проверить почту [обычный]
3. пропущена: некорректный приоритет 8
4. Обновить документацию [обычный]
Корректных задач: 3
Средний приоритет: 3.33

Дополнительная часть

Создайте функцию:

func makePriorityFilter(minimum int) func(int) bool

Она возвращает closure, проверяющий, что приоритет корректен и не меньше minimum. Используйте фильтры для высоких (4) и критических (5) задач.

Структура сдачи

practice-04/
├── go.mod
├── main.go
└── README.md

Критерии оценивания

КритерийБаллы
Простые функции и несколько результатов0–3
Variadic-функции0–2
Функции как значения0–3
Closure и defer0–3
Проект и декомпозиция0–4
Стиль и проверки инструментами0–1
Максимум16

Вопросы для защиты

  1. Что входит в сигнатуру функции?
  2. Почему divide возвращает дополнительный bool?
  3. Как передать slice в variadic-функцию?
  4. Что произойдёт при вызове nil-функции?
  5. Что такое closure?
  6. Когда вычисляются аргументы defer?
  7. В каком порядке выполняются несколько defer?
  8. Почему функция вычисления не должна без необходимости печатать результат?