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 завершится автоматически после закрытия.