Go Курс

Функции в Go: объявление, множественные возвраты, defer

Самоучитель Python 3, собранный из материалов данного сайта.Предназначен в основном для тех, кто хочет изучить язык программирования Python с нуля.

Введение: Зачем нужны функции в программировании?

Представь, что ты строишь дом. Ты же не будешь каждый раз заново изобретать молоток, когда нужно забить гвоздь? Ты берешь уже готовый инструмент и используешь его. В программировании функции — это и есть такие "инструменты", которые ты создаешь один раз, а потом используешь снова и снова.

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

В этом уроке мы научимся:

  • Понимать, что такое функции и зачем они нужны
  • Создавать свои собственные функции
  • Передавать данные в функции и получать результаты
  • Использовать функции, чтобы сделать код чище и понятнее

Что такое функция? Аналогия из жизни

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

  1. Взять хлеб
  2. Намазать масло
  3. Положить колбасу
  4. Накрыть вторым куском хлеба

Вместо того чтобы каждый раз объяснять эту последовательность действий, шеф-повар говорит тебе: "Сделай бутерброд!". И ты знаешь, что делать. Вот это и есть функция: ты даешь ей имя (например, makeSandwich), описываешь последовательность действий, и потом можешь вызывать ее по имени.

В программировании функции помогают:

  • Избегать повторения кода — написал один раз, используешь много раз
  • Делить сложную задачу на маленькие части — проще решать по кусочкам
  • Делать код понятнее — вместо кучи непонятного кода у тебя будут осмысленные блоки
  • Тестировать программу — каждую функцию можно проверить отдельно

Синтаксис функций в Go

В Go функция объявляется с помощью ключевого слова func. Вот базовый синтаксис:

func имяФункции(параметры) возвращаемыйТип {
    // тело функции
    // здесь мы пишем код
    return результат
}

Давай разберем каждый элемент:

  • func — ключевое слово, которое говорит Go: "Эй, сейчас будет функция!"
  • имяФункции — как мы назовем нашу функцию (например, sayHello)
  • параметры — данные, которые функция принимает на вход (как ингредиенты для рецепта)
  • возвращаемыйТип — тип данных, который функция вернет после выполнения
  • return — команда, которая возвращает результат из функции

Первая функция: Приветствие

Давай напишем самую простую функцию, которая просто говорит "Привет!":

package main

import "fmt"

// Функция, которая ничего не принимает и ничего не возвращает
func sayHello() {
    fmt.Println("Привет!")  // Просто печатает приветствие
}

func main() {
    // Вызываем нашу функцию
    sayHello()  // Выведет: Привет!
    sayHello()  // Выведет: Привет! (еще раз)
}

Обрати внимание: мы объявили функцию sayHello один раз, а вызвали ее два раза. Это как сказать роботу "Сделай бутерброд" дважды.

Функции с параметрами

Теперь сделаем функцию более полезной — пусть она приветствует конкретного человека:

package main

import "fmt"

// Функция принимает имя (строку) и приветствует этого человека
func greetUser(name string) {
    fmt.Println("Привет,", name, "!")  // Используем параметр name
}

func main() {
    greetUser("Аня")   // Выведет: Привет, Аня !
    greetUser("Петя")  // Выведет: Привет, Петя !
    greetUser("Маша")  // Выведет: Привет, Маша !
}

Параметр name — это как переменная, которая получает значение при вызове функции. Когда мы пишем greetUser("Аня"), Go подставляет "Аня" вместо name внутри функции.

Функции с несколькими параметрами

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

package main

import "fmt"

// Функция принимает два целых числа и выводит их сумму
func addAndPrint(a int, b int) {
    sum := a + b  // Складываем числа
    fmt.Println("Сумма:", sum)  // Печатаем результат
}

func main() {
    addAndPrint(5, 3)   // Выведет: Сумма: 8
    addAndPrint(10, 20) // Выведет: Сумма: 30
    addAndPrint(-5, 7)  // Выведет: Сумма: 2
}

Если параметры одного типа, можно писать короче:

func addAndPrint(a, b int) {  // a и b — оба типа int
    // код такой же
}

Функции, возвращающие значение

Самое интересное — когда функция не просто что-то делает, а еще и возвращает результат. Тогда мы можем использовать этот результат дальше в программе.

package main

import "fmt"

// Функция возвращает сумму двух чисел
func add(a int, b int) int {
    result := a + b
    return result  // Возвращаем результат
}

func main() {
    // Сохраняем результат функции в переменную
    sum := add(5, 3)
    fmt.Println("5 + 3 =", sum)  // Выведет: 5 + 3 = 8
    
    // Можно использовать результат сразу
    fmt.Println("10 + 20 =", add(10, 20))  // Выведет: 10 + 20 = 30
    
    // Результат можно использовать в вычислениях
    total := add(1, 2) + add(3, 4)
    fmt.Println("Общая сумма:", total)  // Выведет: Общая сумма: 10
}

Обрати внимание на возвращаемый тип — после параметров мы написали int. Это значит, что функция обещает вернуть целое число. Если мы забудем написать return, Go выдаст ошибку.

Аналогия: Функция как торговый автомат

Представь, что функция — это торговый автомат. Ты:

  1. Передаешь параметры — кидаешь монетки и выбираешь номер товара
  2. Функция работает — автомат проверяет деньги, готовит товар
  3. Получаешь результат — автомат выдает шоколадку

В коде это выглядит так:

func vendingMachine(money int, choice int) string {
    if money >= 50 && choice == 1 {
        return "Шоколадка"  // Выдаем шоколадку
    } else if money >= 30 && choice == 2 {
        return "Печенье"    // Выдаем печенье
    } else {
        return "Недостаточно денег или неверный выбор"
    }
}

func main() {
    item := vendingMachine(50, 1)
    fmt.Println(item)  // Выведет: Шоколадка
    
    fmt.Println(vendingMachine(20, 2))  // Выведет: Недостаточно денег или неверный выбор
}

Функции могут возвращать несколько значений

Удивительно, но в Go функция может вернуть не одно, а несколько значений одновременно! Это очень удобно, когда нужно вернуть и результат, и информацию об ошибке.

package main

import "fmt"

// Функция возвращает два значения: результат и флаг успеха
func divide(a, b float64) (float64, bool) {
    if b == 0 {
        return 0, false  // Нельзя делить на ноль, возвращаем false
    }
    result := a / b
    return result, true  // Все хорошо, возвращаем true
}

func main() {
    // Принимаем два возвращаемых значения
    result, success := divide(10, 3)
    
    if success {
        fmt.Println("Результат деления:", result)  // Выведет: Результат деления: 3.333...
    } else {
        fmt.Println("Ошибка: деление на ноль!")
    }
    
    // Если нам нужно только первое значение, используем _
    res, _ := divide(15, 4)
    fmt.Println("15 / 4 =", res)  // Выведет: 15 / 4 = 3.75
}

Символ _ (подчеркивание) — это специальная переменная в Go. Она означает "мне не нужно это значение, проигнорируй его".

Именованные возвращаемые значения

В Go можно дать имена возвращаемым значениям. Это делает код понятнее:

package main

import "fmt"

// Мы дали имя возвращаемому значению - result
func multiply(a, b int) (result int) {
    result = a * b  // Можно использовать result как переменную
    return          // Достаточно просто написать return, Go сам вернет result
}

func main() {
    product := multiply(6, 7)
    fmt.Println("6 * 7 =", product)  // Выведет: 6 * 7 = 42
}

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

Область видимости переменных

Важное понятие — область видимости. Переменные, объявленные внутри функции, видны только внутри этой функции. Они как секретные ингредиенты, которые знает только повар.

package main

import "fmt"

func myFunction() {
    secret := "Это секрет"  // Переменная существует только внутри функции
    fmt.Println(secret)      // Это работает
}

func main() {
    // fmt.Println(secret)  // Ошибка! Переменная secret не видна здесь
    myFunction()  // Выведет: Это секрет
}

Переменные, объявленные вне функций (на уровне пакета), видны всем функциям. Но старайся их не использовать без необходимости — это может запутать код.

Функции могут вызывать другие функции

Функции — как кирпичики Лего. Из маленьких функций можно собирать большие:

package main

import "fmt"

// Маленькая функция: проверяет, можно ли голосовать
func canVote(age int) bool {
    return age >= 18
}

// Большая функция: использует маленькую
func checkVotingEligibility(name string, age int) {
    fmt.Print(name, ", ")
    if canVote(age) {  // Вызываем другую функцию
        fmt.Println("ты можешь голосовать!")
    } else {
        yearsLeft := 18 - age
        fmt.Println("тебе осталось ждать", yearsLeft, "лет")
    }
}

func main() {
    checkVotingEligibility("Аня", 20)  // Выведет: Аня, ты можешь голосовать!
    checkVotingEligibility("Петя", 15) // Выведет: Петя, тебе осталось ждать 3 лет
}

Практический пример: Калькулятор

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

package main

import "fmt"

// Функции для математических операций
func add(a, b float64) float64 {
    return a + b
}

func subtract(a, b float64) float64 {
    return a - b
}

func multiply(a, b float64) float64 {
    return a * b
}

func divide(a, b float64) (float64, bool) {
    if b == 0 {
        return 0, false  // Нельзя делить на ноль
    }
    return a / b, true
}

// Функция, которая выводит меню
func showMenu() {
    fmt.Println("=== Калькулятор ===")
    fmt.Println("1. Сложение")
    fmt.Println("2. Вычитание")
    fmt.Println("3. Умножение")
    fmt.Println("4. Деление")
    fmt.Println("0. Выход")
    fmt.Print("Выбери операцию: ")
}

func main() {
    var choice int
    var a, b float64
    
    showMenu()
    fmt.Scan(&choice)
    
    if choice == 0 {
        fmt.Println("До свидания!")
        return
    }
    
    fmt.Print("Введи первое число: ")
    fmt.Scan(&a)
    fmt.Print("Введи второе число: ")
    fmt.Scan(&b)
    
    switch choice {
    case 1:
        fmt.Println("Результат:", add(a, b))
    case 2:
        fmt.Println("Результат:", subtract(a, b))
    case 3:
        fmt.Println("Результат:", multiply(a, b))
    case 4:
        if result, ok := divide(a, b); ok {
            fmt.Println("Результат:", result)
        } else {
            fmt.Println("Ошибка: деление на ноль!")
        }
    default:
        fmt.Println("Неверный выбор!")
    }
}

Рекурсия: Функция вызывает саму себя

Самое удивительное — функция может вызывать саму себя! Это называется рекурсия. Как матрешка: внутри большой матрешки находится такая же, но поменьше.

Классический пример — вычисление факториала числа. Факториал числа n (обозначается n!) — это произведение всех чисел от 1 до n.

package main

import "fmt"

// Рекурсивная функция для вычисления факториала
func factorial(n int) int {
    // Базовый случай: если n равно 0 или 1, возвращаем 1
    if n <= 1 {
        return 1
    }
    // Рекурсивный случай: n * факториал (n-1)
    return n * factorial(n-1)
}

func main() {
    fmt.Println("5! =", factorial(5))  // Выведет: 5! = 120
    // Как это работает:
    // factorial(5) = 5 * factorial(4)
    // factorial(4) = 4 * factorial(3)
    // factorial(3) = 3 * factorial(2)
    // factorial(2) = 2 * factorial(1)
    // factorial(1) = 1
    // Итого: 5 * 4 * 3 * 2 * 1 = 120
}

Рекурсия — мощный инструмент, но используй ее осторожно. Если забыть про базовый случай (условие остановки), функция будет вызывать себя бесконечно, и программа зависнет.

Функции как значения

В Go функции — это тоже значения. Их можно присваивать переменным, передавать в другие функции и возвращать из функций. Это как если бы сам рецепт можно было передавать друзьям.

package main

import "fmt"

func main() {
    // Объявляем переменную, которая хранит функцию
    var operation func(int, int) int
    
    // Присваиваем ей функцию сложения
    operation = func(a, b int) int {
        return a + b
    }
    
    // Используем функцию через переменную
    result := operation(5, 3)
    fmt.Println("5 + 3 =", result)  // Выведет: 5 + 3 = 8
    
    // Меняем функцию на умножение
    operation = func(a, b int) int {
        return a * b
    }
    
    result = operation(5, 3)
    fmt.Println("5 * 3 =", result)  // Выведет: 5 * 3 = 15
}

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

Анонимные функции (лямбды)

Иногда функцию нужно использовать только один раз. Тогда можно не давать ей имя, а написать прямо на месте:

package main

import "fmt"

func main() {
    // Анонимная функция, которая вызывается сразу
    func() {
        fmt.Println("Это анонимная функция!")
    }()  // Скобки в конце означают вызов функции
    
    // Анонимная функция с параметрами
    result := func(a, b int) int {
        return a + b
    }(10, 20)  // Передаем параметры и сразу вызываем
    
    fmt.Println("Результат:", result)  // Выведет: Результат: 30
}

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

Замыкания (closures)

Замыкание — это функция, которая "запоминает" переменные из того места, где была создана. Это как если бы робот запомнил, что ты любишь бутерброды с сыром.

package main

import "fmt"

// Функция, которая возвращает другую функцию
func createCounter() func() int {
    count := 0  // Эта переменная "запоминается"
    
    // Возвращаем функцию, которая увеличивает счетчик
    return func() int {
        count++  // Увеличиваем count
        return count
    }
}

func main() {
    // Создаем счетчик
    counter := createCounter()
    
    fmt.Println(counter())  // Выведет: 1
    fmt.Println(counter())  // Выведет: 2
    fmt.Println(counter())  // Выведет: 3
    
    // Создаем еще один счетчик - он независим
    anotherCounter := createCounter()
    fmt.Println(anotherCounter())  // Выведет: 1 (начинает с 1)
    fmt.Println(counter())         // Выведет: 4 (первый счетчик продолжает)
}

Замыкания — это мощный инструмент, но сначала просто запомни, что они существуют. Ты будешь часто с ними встречаться в реальных проектах.

Практические советы по написанию функций

  1. Давай функциям понятные имена. Вместо f1 или doStuff используй calculateSum или printUserInfo.
  2. Одна функция — одна задача. Если функция делает слишком много, разбей ее на несколько маленьких.
  3. Не делай функции слишком длинными. Хорошая функция помещается на экране (20-30 строк).
  4. Используй комментарии. Объясни, что делает функция, особенно если это не очевидно.
  5. Проверяй входные данные. Если функция ожидает положительное число, проверь это.
  6. Избегай глобальных переменных. Передавай все нужные данные через параметры.

Типичные ошибки новичков

Вот что чаще всего идет не так:

  • Забывают указать return. Если функция должна что-то возвращать, не забудь про return.
  • Путают параметры и аргументы. Параметры — это то, что функция ожидает (в объявлении), аргументы — то, что передаешь (при вызове).
  • Игнорируют возвращаемые значения. Если функция возвращает ошибку, проверь ее!
  • Слишком много параметров. Если у функции больше 3-4 параметров, возможно, стоит объединить их в структуру.

Практические задания

А теперь самое интересное — попробуй сам! Вот три задания разного уровня сложности.

Задание 1: Калькулятор площади (легкий)

Напиши функцию calculateRectangleArea, которая принимает длину и ширину прямоугольника (тип float64) и возвращает его площадь. В main вызови эту функцию для трех разных прямоугольников и выведи результаты.

Подсказка: Площадь прямоугольника = длина * ширина.

// Примерный шаблон для решения:
func calculateRectangleArea(length, width float64) float64 {
    // Твой код здесь
}

func main() {
    // Вызови функцию три раза с разными значениями
    // Например: 5x3, 7x2, 10x10
}

Задание 2: Проверка пароля (средний)

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

  • Длина не менее 8 символов
  • Содержит хотя бы одну цифру
  • Содержит хотя бы одну заглавную букву

В main проверь несколько паролей и выведи результат.

Подсказка: Используй цикл для проверки каждого символа. Функции unicode.IsDigit() и unicode.IsUpper() помогут определить тип символа. Не забудь импортировать пакет unicode.

import "unicode"

func checkPassword(password string) (bool, string) {
    // Твой код здесь
    // Используй цикл for для