JavaRush /Курсы /Go SELF /Функции в Go: func...

Функции в Go: func и не только

Go SELF
9 уровень , 1 лекция
Открыта

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

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

Представьте, что main — это рецепт блюда, а функции — отдельные подрецепты: «нарезать овощи», «разогреть духовку», «смешать соус». Вы же не хотите читать рецепт, где всё написано одним абзацем на страницу? Компьютер, кстати, тоже не хочет, но терпит молча — до первой ошибки.

Скелет объявления: func, имя, параметры, результат, тело

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

Простейший пример: функция без параметров и без результата может выглядеть так же, как hello() в демонстрационном коде — просто func hello() { ... }.

А функция с параметром и результатом выглядит похожим образом: func name(param Type) ResultType { ... }.

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

Часть Пример Зачем нужна
Ключевое слово
func
Сообщает компилятору: «сейчас будет функция»
Имя
add, calc, printHeader
По нему вы вызываете функцию
Параметры
(a int, b int)
Входные данные для вычисления/действия
Результат
int
(или ничего)
Что функция возвращает вызывающему коду
Тело
{ ... }
Команды, которые выполняются при вызове

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

Функция без результата: «сделай действие и вернись»

Очень частый тип функций — «процедурные»: они что-то делают (например, печатают текст), но ничего не возвращают. В Go это выглядит просто: после параметров не пишется никакой возвращаемый тип.

Важно понимать: «не возвращает значение» не значит «не заканчивается». Такая функция всё равно завершается, просто результатом является сам факт выполнения.

package main

import "fmt"

func printHeader() {
	fmt.Println("=== MiniCalc v1 ===") // === MiniCalc v1 ===
}

func main() {
	printHeader()
}

Здесь printHeader — функция без параметров и без результата. Она делает одно действие: печатает заголовок. И это уже маленькая победа: main стал чуть «чище», и вы можете переиспользовать printHeader() где угодно.

Отдельный полезный нюанс: return можно использовать и в функции без результата — просто чтобы закончить её раньше. Но делать так стоит только когда это реально делает код проще.

package main

import "fmt"

func printIfPositive(x int) {
	if x <= 0 {
		return // ничего не печатаем и выходим
	}
	fmt.Println("positive:", x) // positive: 3
}

func main() {
	printIfPositive(3)
}

Параметры: входные данные, которые живут только внутри

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

Это удобно сразу по двум причинам. Во-первых, вы не засоряете main кучей временных переменных. Во-вторых, вы не можете случайно «сломать» что-то снаружи, меняя параметры (пока мы не обсуждаем указатели — это будет позже, и там появятся новые нюансы).

Пример с параметрами:

package main

import "fmt"

func add(a int, b int) int {
	return a + b
}

func main() {
	fmt.Println(add(2, 3)) // 5
}

Обратите внимание на две вещи. Во-первых, тип параметра пишется после имени: a int. Во-вторых, параметры — это не «магия»: a и b обычные переменные, просто созданные при вызове.

Ещё один маленький трюк читаемости: если подряд идут параметры одного типа, в Go разрешено писать тип один раз.

package main

import "fmt"

func rectArea(width, height int) int {
	return width * height
}

func main() {
	fmt.Println(rectArea(3, 4)) // 12
}

Такую запись вы будете встречать постоянно: она короче и при этом не хуже читается.

Один возвращаемый результат и return

Функции с результатом отличаются от «процедурных» тем, что в их объявлении после параметров указан тип результата, а внутри тела должен быть return со значением этого типа.

Возврат — это не просто «отдать значение». Это ещё и команда «закончить выполнение функции прямо сейчас». Поэтому в функциях с ветвлениями важно следить, чтобы любой путь приводил к корректному return.

Мини-пример с простым условием:

package main

import "fmt"

func max2(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func main() {
	fmt.Println(max2(10, 3)) // 10
}

Здесь есть два пути. Если a > b, возвращаем a. Иначе возвращаем b. И что приятно: код читается линейно, без else — вы уже встречали такой стиль в if, и в функциях он особенно хорошо заходит.

Аккуратность с типами: return должен совпадать с объявлением

Одна из самых частых причин «почему оно не компилируется» у новичков — несоответствие типов в return. Компилятор Go достаточно строгий: если вы объявили func something(...) int, то вернуть вы обязаны именно int.

Пример: хотим вернуть строку — нужно объявить результат строкой.

package main

import "fmt"

func greet(name string) string {
	return "Hello, " + name
}

func main() {
	fmt.Println(greet("Gopher")) // Hello, Gopher
}

Здесь всё честно: результат string, и return возвращает строку. Если бы мы написали func greet(name string) int, а внутри вернули строку — компилятор бы справедливо возмутился.

3. Контракт и читаемость

Пока вы учитесь писать функции, есть одна идея, которая экономит вам много нервов: у каждой функции должен быть понятный контракт. Даже если вы пока не пишете документацию комментариями, вы всё равно должны уметь ответить на вопросы: «что эта функция ждёт на вход?» и «что она обещает вернуть?»

Например, rectArea(width, height int) int — контракт понятен: берёт ширину и высоту, возвращает площадь. А вот функция calc(a, b int) int без уточнения — подозрительная: «что именно она считает?». Имя функции — часть контракта. Поэтому мы стараемся называть функции глаголами или «действиями»: add, printHeader, max2, rectArea.

И ещё важный момент: не бойтесь маленьких функций. Новички часто думают, что «много функций = сложно». На практике обычно наоборот: «одна функция на 200 строк = сложно», а «10 функций по 10 строк = жить можно».

У функций есть ещё одна «тихая» ловушка: аргументы передаются по позиции. То есть sum(a, b) — это не «положи числа как-нибудь», а «первое значение идёт в первый параметр, второе — во второй».

В функциях, где параметры однотипные, особенно важно следить за смыслом и названием параметров. Например, rectArea(width, height int) гораздо понятнее, чем rectArea(a, b int), хотя типы одинаковые.

Мини-демо:

package main

import "fmt"

func rectArea(width, height int) int {
	return width * height
}

func main() {
	fmt.Println(rectArea(2, 10)) // 20
	// rectArea(10, 2) тоже будет 20, но смысл "ширина/высота" вы уже потеряли :)
}

Да, математика в этом примере не страдает, но в реальной задаче (например, «минимум/максимум», «начало/конец», «x/y») порядок аргументов может полностью поменять смысл.

4. Пример: MiniCalc v1

Сейчас мы начнём собирать мини-приложение, которое будет развиваться дальше по темам. Это будет простой консольный калькулятор: он читает два числа и печатает сумму и произведение. Звучит скромно, зато идеально для тренировки функций: вычисления можно вынести, а main оставить «дирижёром».

Сначала посмотрим на «наивный» вариант (всё внутри main). Он рабочий, но как только логики станет больше — начнёт распухать.

package main

import (
	"fmt"
)

func main() {
	var a, b int
	fmt.Scan(&a, &b)

	fmt.Println("sum =", a+b)
	fmt.Println("mul =", a*b)
}

Теперь сделаем то же самое, но с функциями для вычислений. Важно: мы пока не усложняем ввод/вывод, просто выносим арифметику, чтобы увидеть механику.

package main

import "fmt"

func sum(a, b int) int {
	return a + b
}

func mul(a, b int) int {
	return a * b
}

func main() {
	var a, b int
	fmt.Scan(&a, &b)

	fmt.Println("sum =", sum(a, b))
	fmt.Println("mul =", mul(a, b))
}

С точки зрения результата программа делает то же самое. Но с точки зрения структуры произошло важное: main теперь читается как сценарий «прочитать → вызвать функции → вывести», а детали вычислений из него ушли.

main как сценарий, функции как шаги

Когда вы начинаете писать функции, полезно иногда рисовать в голове (или на бумаге) цепочку вызовов. Это помогает не потеряться: «кто кого вызывает» и «где что считается».

Например, для нашего MiniCalc v1 цепочка такая:

flowchart TD
    A[main: читаем a и b] --> B["sum(a, b)"]
    A --> C["mul(a, b)"]
    B --> D[main: печатаем sum]
    C --> E[main: печатаем mul]

Смысл диаграммы простой: main отвечает за «организацию», а sum и mul — за «вычисления». Это хорошая привычка, которая позже сильно поможет, когда вычисления станут сложнее и появятся ошибки.

5. Типичные ошибки

Ошибка №1: забыли указать тип параметра (или указали только для первого).
Новички часто пишут что-то вроде func sum(a, b int) int и удивляются, что это работает, а потом пишут func sum(a, b) int и получают ошибку. В Go тип обязателен, но его можно «разделить» на несколько имён только когда тип одинаковый, как в a, b int. Если типы разные — каждый параметр должен явно иметь свой тип.

Ошибка №2: перепутали местами части сигнатуры.
Иногда пытаются написать «по-человечески»: func int sum(a int, b int). Но Go любит порядок: func → имя → параметры → результат. Это как очередь в поликлинике: можно спорить, но проще один раз запомнить и жить дальше.

Ошибка №3: функция с результатом не возвращает значение во всех ветках.
Вы пишете if, в одной ветке делаете return, а в другой забываете, и компилятор ругается. Это не вредность компилятора, а его забота о вашей психике: лучше получить ошибку сразу, чем «странное поведение» на проде. Лечится просто: проверяйте, что любой путь заканчивается return нужного типа.

Ошибка №4: слишком «абстрактные» имена функций и параметров.
f(x, y) работает, но читается как «что-то где-то». Через неделю вы забудете, что такое f, а через месяц начнёте подозревать, что это вообще не ваш код (хотя ваш). Нормальные имена вроде rectArea(width, height) или max2(a, b) делают половину работы за комментарии.

Ошибка №5: превращение main в свалку всего на свете — даже после появления функций.
Бывает так: функции вы создали, но main всё равно остался огромным, потому что вы вынесли только «крошечные» вещи, а большие логические шаги оставили внутри. Хороший ориентир: если main перестал помещаться на экран без прокрутки — пора снова спросить себя: «какой здесь повторяющийся шаг можно назвать одним словом и вынести в функцию?»

1
Задача
Go SELF, 9 уровень, 1 лекция
Недоступна
Тёплое приветствие
Тёплое приветствие
1
Задача
Go SELF, 9 уровень, 1 лекция
Недоступна
План комнаты
План комнаты
1
Задача
Go SELF, 9 уровень, 1 лекция
Недоступна
Лучший результат
Лучший результат
1
Задача
Go SELF, 9 уровень, 1 лекция
Недоступна
Охрана простоты
Охрана простоты
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ