ШПАРГАЛКА

Go

Шпаргалка по Go: структура программы, переменные, типы, срезы, map, структуры, интерфейсы, ошибки, горутины и каналы с примерами.

Краткая выжимка по языку Go (Golang): синтаксис, типы данных, структуры, интерфейсы, обработка ошибок и конкурентность. Все примеры рабочие — проверяйте их в песочнице или в go run main.go.

Структура программы

Каждый файл начинается с объявления пакета. Исполняемая программа — это пакет main с функцией main. Импорты подключаются через import.

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println("Привет, Go!") // Привет, Go!
    fmt.Println(strings.ToUpper("go")) // GO
}

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

Переменные и константы

Объявление через var или короткое объявление := (только внутри функций). Константы — через const.

var age int = 30
var name = "Аня"      // тип выводится автоматически
count := 10           // короткое объявление, тип int

var x, y = 1, 2       // несколько сразу

const Pi = 3.14159
const (
    StatusOK   = 200
    StatusFail = 500
)

fmt.Println(age, name, count, x, y) // 30 Аня 10 1 2

Неинициализированная переменная получает нулевое значение: 0 для чисел, "" для строк, false для bool, nil для указателей, срезов, map.

var n int       // 0
var s string    // ""
var ok bool     // false
fmt.Println(n, s, ok) // 0  false

iota

iota — счётчик констант, удобен для перечислений.

const (
    Sunday = iota // 0
    Monday        // 1
    Tuesday       // 2
)
fmt.Println(Sunday, Monday, Tuesday) // 0 1 2

Типы данных

Основные встроенные типы Go:

КатегорияТипы
Целыеint, int8, int16, int32, int64, uint, byte (=uint8)
Дробныеfloat32, float64
Логическийbool
Строкиstring
Символrune (=int32)

Явное преобразование типов обязательно — неявного приведения нет.

var i int = 42
var f float64 = float64(i) // 42
var u uint = uint(f)       // 42
fmt.Println(i, f, u)       // 42 42 42

Строки и руны

Строка в Go — неизменяемая последовательность байтов (UTF-8). Длина в байтах — через len, перебор символов — через range (даёт руны).

s := "Привет"
fmt.Println(len(s)) // 12 — байты (кириллица по 2 байта)

for i, r := range s {
    fmt.Printf("%d:%c ", i, r) // 0:П 2:р 4:и 6:в 8:е 10:т
}
fmt.Println()

runes := []rune(s)
fmt.Println(len(runes)) // 6 — символы

Полезные функции пакета strings:

import "strings"

strings.Contains("golang", "go")   // true
strings.Split("a,b,c", ",")        // [a b c]
strings.Join([]string{"a","b"}, "-") // a-b
strings.Replace("aaa", "a", "b", 1)  // baa

Массивы и срезы

Массив имеет фиксированную длину, заданную в типе. На практике чаще используют срезы (slices) — динамические обёртки над массивом.

var arr [3]int = [3]int{1, 2, 3} // массив фиксированной длины
fmt.Println(arr, len(arr))       // [1 2 3] 3

s := []int{10, 20, 30}           // срез
fmt.Println(s[0], s[1:3])        // 10 [20 30]

append и make

append добавляет элементы (может вернуть новый срез), make создаёт срез с заданной длиной и ёмкостью.

s := []int{1, 2}
s = append(s, 3, 4)        // [1 2 3 4]
s = append(s, []int{5,6}...) // распаковка другого среза
fmt.Println(s)             // [1 2 3 4 5 6]

buf := make([]int, 0, 10)  // длина 0, ёмкость 10
fmt.Println(len(buf), cap(buf)) // 0 10

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

Map

map — ассоциативный массив (хеш-таблица). Создаётся через make или литерал.

m := map[string]int{"яблоки": 5, "груши": 3}
m["сливы"] = 7
fmt.Println(m["яблоки"]) // 5

// проверка наличия ключа
val, ok := m["бананы"]
fmt.Println(val, ok)     // 0 false

delete(m, "груши")       // удаление ключа
fmt.Println(len(m))      // 2
// перебор (порядок не гарантирован!)
for key, value := range m {
    fmt.Println(key, value)
}

Структуры (struct)

Структура группирует поля. Поля с большой буквы экспортируются (видны из других пакетов).

type User struct {
    Name string
    Age  int
}

u := User{Name: "Иван", Age: 25}
fmt.Println(u.Name)        // Иван
u.Age = 26
fmt.Println(u)             // {Иван 26}

// краткий литерал по порядку полей
u2 := User{"Пётр", 40}
fmt.Println(u2.Age)        // 40

Структуры можно вкладывать друг в друга (встраивание):

type Address struct{ City string }
type Person struct {
    Name string
    Address // встроенная структура
}
p := Person{Name: "Лена", Address: Address{City: "Казань"}}
fmt.Println(p.City) // Казань — прямой доступ к полю

Указатели

Указатель хранит адрес значения. & берёт адрес, * разыменовывает. Указатели нужны, чтобы менять значение внутри функции.

x := 10
p := &x          // p — указатель на x
fmt.Println(*p)  // 10
*p = 20          // меняем x через указатель
fmt.Println(x)   // 20

func increment(n *int) { *n++ }
increment(&x)
fmt.Println(x)   // 21

В Go нет арифметики указателей и нет ручного освобождения памяти — работает сборщик мусора.

Условия: if и switch

В if можно объявить переменную с коротким областью видимости. Скобки вокруг условия не нужны, фигурные — обязательны.

if n := 7; n%2 == 0 {
    fmt.Println("чётное")
} else {
    fmt.Println("нечётное") // нечётное
}
switch day := 3; day {
case 1, 2, 3, 4, 5:
    fmt.Println("будни") // будни
case 6, 7:
    fmt.Println("выходные")
default:
    fmt.Println("?")
}

// switch без условия — замена цепочки if-else
x := 42
switch {
case x < 0:
    fmt.Println("отрицательное")
case x == 0:
    fmt.Println("ноль")
default:
    fmt.Println("положительное") // положительное
}

В Go нет проваливания между case по умолчанию — break не нужен. Для явного провала используют fallthrough.

Циклы: только for

В Go единственный цикл — for, но в нескольких формах.

// классический
for i := 0; i < 3; i++ {
    fmt.Print(i) // 012
}

// как while
n := 0
for n < 3 {
    n++
}
fmt.Println(n) // 3

// бесконечный с break
for {
    break
}

range

range перебирает срезы, массивы, map, строки и каналы.

nums := []int{10, 20, 30}
for i, v := range nums {
    fmt.Println(i, v) // 0 10 / 1 20 / 2 30
}

// только значения — индекс через _
for _, v := range nums {
    fmt.Print(v) // 102030
}

Функции

Функция объявляется через func. Тип параметра пишется после имени.

func add(a int, b int) int {
    return a + b
}
fmt.Println(add(2, 3)) // 5

Множественный возврат

Go умеет возвращать несколько значений — часто это пара «результат, ошибка».

func divmod(a, b int) (int, int) {
    return a / b, a % b
}
q, r := divmod(17, 5)
fmt.Println(q, r) // 3 2

Именованные результаты

func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return // голый return возвращает x и y
}
fmt.Println(split(17)) // 7 10

Variadic (переменное число аргументов)

func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}
fmt.Println(sum(1, 2, 3, 4)) // 10
fmt.Println(sum())           // 0

Замыкания

Функция может захватывать переменные окружения.

func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}
c := counter()
fmt.Println(c(), c(), c()) // 1 2 3

Методы и интерфейсы

Метод — функция с получателем (receiver). Получатель-указатель позволяет менять структуру.

type Rect struct{ W, H int }

func (r Rect) Area() int { return r.W * r.H }
func (r *Rect) Scale(k int) { r.W *= k; r.H *= k }

r := Rect{2, 3}
fmt.Println(r.Area()) // 6
r.Scale(2)
fmt.Println(r.Area()) // 24

Интерфейсы

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

type Shape interface {
    Area() int
}

func describe(s Shape) {
    fmt.Println("площадь:", s.Area())
}

describe(Rect{4, 5}) // площадь: 20

Пустой интерфейс interface{} (или any в Go 1.18+) принимает любое значение. Достать конкретный тип можно через type assertion или type switch.

var v any = "строка"
if s, ok := v.(string); ok {
    fmt.Println("это строка:", s) // это строка: строка
}

switch x := v.(type) {
case int:
    fmt.Println("int", x)
case string:
    fmt.Println("string", x) // string строка
}

Обработка ошибок

В Go ошибки — это значения типа error, а не исключения. Стандартный паттерн — проверка if err != nil.

import (
    "errors"
    "fmt"
    "strconv"
)

n, err := strconv.Atoi("123")
if err != nil {
    fmt.Println("ошибка:", err)
} else {
    fmt.Println("число:", n) // число: 123
}

Создание собственных ошибок:

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("деление на ноль")
    }
    return a / b, nil
}

res, err := divide(10, 0)
if err != nil {
    fmt.Println(err) // деление на ноль
}
_ = res

// форматированная ошибка с обёрткой
err2 := fmt.Errorf("не удалось: %w", err)
fmt.Println(err2) // не удалось: деление на ноль

panic и recover

panic прерывает выполнение, recover внутри defer перехватывает панику. Используйте только для действительно исключительных ситуаций.

func safeDiv(a, b int) (result int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("восстановились:", r)
            result = -1
        }
    }()
    return a / b // при b==0 будет panic
}
fmt.Println(safeDiv(10, 0)) // восстановились: ... / -1

defer

defer откладывает вызов до выхода из функции. Удобно для закрытия ресурсов. Отложенные вызовы выполняются в порядке LIFO (последний — первым).

func main() {
    defer fmt.Println("3 — выполнится последним")
    defer fmt.Println("2")
    fmt.Println("1")
    // Вывод:
    // 1
    // 2
    // 3 — выполнится последним
}
// типичное применение — закрытие файла
f, err := os.Open("data.txt")
if err != nil {
    return
}
defer f.Close() // закроется при выходе из функции

Горутины и каналы

Горутина — лёгкий поток, запускается словом go. Горутины общаются через каналы (chan).

func main() {
    go fmt.Println("из горутины") // запуск параллельно
    fmt.Println("из main")
    time.Sleep(time.Millisecond) // дать горутине шанс выполниться
}

Каналы

Канал передаёт значения между горутинами. Оператор <- отправляет или принимает данные.

ch := make(chan int)

go func() {
    ch <- 42 // отправка в канал
}()

v := <-ch // приём из канала (блокирует, пока не придёт)
fmt.Println(v) // 42

// буферизованный канал
buf := make(chan int, 2)
buf <- 1
buf <- 2
fmt.Println(<-buf, <-buf) // 1 2

select

select ждёт готовности одного из нескольких каналов.

c1 := make(chan string)
c2 := make(chan string)
go func() { c1 <- "раз" }()
go func() { c2 <- "два" }()

select {
case msg := <-c1:
    fmt.Println(msg)
case msg := <-c2:
    fmt.Println(msg)
}

sync.WaitGroup

WaitGroup ждёт завершения группы горутин.

import "sync"

var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
    wg.Add(1)
    go func(n int) {
        defer wg.Done()
        fmt.Println("задача", n)
    }(i)
}
wg.Wait() // ждём все горутины
fmt.Println("готово")

Совет: закрывайте канал через close(ch), когда отправка закончена. Перебор канала через range завершится автоматически после закрытия.

Поддержать проект