Go
Go (Golang) за 15 минут: синтаксис, типы, срезы, map, структуры, интерфейсы, горутины и каналы — весь язык на одной странице в примерах кода.
Go (Golang) — компилируемый язык от Google: строгая типизация, простой синтаксис, встроенная конкурентность через горутины и каналы, быстрая компиляция в один бинарник. Весь язык — на одной странице, всё в комментариях кода.
Структура программы
Программа на Go — это набор пакетов. Запускаемая программа всегда содержит package main и функцию main.
// Каждый файл начинается с объявления пакета.
// Исполняемая программа должна быть в пакете main.
package main
// Импорт пакетов стандартной библиотеки.
import "fmt"
// Точка входа — функция main без параметров и возврата.
func main() {
fmt.Println("Привет, Go!") // Println — вывод строки + перенос строки
// => Привет, Go!
fmt.Print("без переноса") // Print — без переноса строки
fmt.Printf("%s = %d\n", "x", 42) // Printf — форматированный вывод
// => x = 42
}
// Запуск: go run main.go
// Сборка: go build main.go -> бинарникПеременные и константы
Неиспользованные переменные и импорты — ошибка компиляции. Это особенность Go.
// Полная форма: var имя тип = значение
var age int = 30
// Тип можно опустить — выводится из значения.
var name = "Аня"
// Короткое объявление := (только внутри функций).
count := 10 // тип int выведен автоматически
// Несколько переменных сразу.
var x, y int = 1, 2
a, b := "да", true
// Без инициализации — нулевое значение типа.
var empty int // 0
var s string // "" (пустая строка)
var ok bool // false
// Константы — неизменяемы, объявляются через const.
const Pi = 3.14159
const (
Red = 0
Green = 1
Blue = 2
)
// iota — автонумерация констант (0, 1, 2, ...).
const (
Mon = iota // 0
Tue // 1
Wed // 2
)Базовые типы и строки
// Целые числа: int, int8/16/32/64, uint и т.д.
var i int = -7
var u uint = 7
// Числа с плавающей точкой.
var f float64 = 3.14
var f32 float32 = 1.5
// Булев тип.
var flag bool = true
// Строки — неизменяемая последовательность байтов (UTF-8).
var str string = "Привет"
fmt.Println(len(str)) // 12 — длина в БАЙТАХ (кириллица по 2 байта)
// Руна (rune) — один символ Unicode (псевдоним int32).
var r rune = 'Я'
fmt.Println(r) // 1071 — код символа
fmt.Printf("%c\n", r) // Я
// Перебор строки по рунам через range.
for index, ch := range "abЯ" {
fmt.Printf("%d:%c ", index, ch) // 0:a 1:b 2:Я
}
// Конвертация типов — только явная.
var n int = 65
var c string = string(rune(n)) // "A"
var fl float64 = float64(n) // 65.0Операторы и условия
// Арифметика: + - * / % и сравнения == != < > <= >=
// Логика: && || !
x := 7
// if без скобок вокруг условия, но с обязательными {}.
if x > 5 {
fmt.Println("больше 5")
} else if x == 5 {
fmt.Println("равно 5")
} else {
fmt.Println("меньше 5")
}
// if с инициализатором: переменная видна только внутри if/else.
if y := x * 2; y > 10 {
fmt.Println("y =", y) // y = 14
}
// switch — без break (нет проваливания по умолчанию).
switch x {
case 1, 2, 3:
fmt.Println("малое")
case 7:
fmt.Println("семь")
default:
fmt.Println("другое")
}
// switch без выражения — замена длинным if/else.
switch {
case x < 0:
fmt.Println("отрицательное")
case x == 0:
fmt.Println("ноль")
default:
fmt.Println("положительное")
}
// fallthrough — явно провалиться в следующий case.
switch x {
case 7:
fmt.Println("семь")
fallthrough
case 8:
fmt.Println("и сюда тоже")
}Циклы (только for)
// В Go ОДИН цикл — for. Он же заменяет while и do-while.
// 1) Классический счётчик.
for i := 0; i < 3; i++ {
fmt.Print(i, " ") // 0 1 2
}
// 2) Как while — только условие.
n := 3
for n > 0 {
fmt.Print(n, " ") // 3 2 1
n--
}
// 3) Бесконечный цикл + break/continue.
i := 0
for {
if i == 2 {
i++
continue // пропустить итерацию
}
if i >= 4 {
break // выйти из цикла
}
fmt.Print(i, " ") // 0 1 3
i++
}
// 4) range — перебор срезов, массивов, map, строк, каналов.
nums := []int{10, 20, 30}
for index, value := range nums {
fmt.Printf("%d=%d ", index, value) // 0=10 1=20 2=30
}
// Только значения — индекс игнорируем через _.
for _, value := range nums {
fmt.Print(value, " ") // 10 20 30
}Массивы и срезы
// Массив — фиксированная длина, часть типа.
var arr [3]int = [3]int{1, 2, 3}
arr2 := [...]int{4, 5, 6} // длина выводится: [3]int
fmt.Println(len(arr)) // 3
// Срез (slice) — динамический «вид» на массив. Используется чаще массивов.
var s []int // nil-срез, len 0
s = []int{1, 2, 3}
fmt.Println(s, len(s), cap(s)) // [1 2 3] 3 3
// make — создать срез заданной длины (и ёмкости).
buf := make([]int, 2, 5) // длина 2, ёмкость 5
fmt.Println(buf) // [0 0]
// append — добавление элементов (может вернуть новый массив).
s = append(s, 4, 5)
fmt.Println(s) // [1 2 3 4 5]
// Слайсинг: s[low:high] — полуинтервал [low, high).
fmt.Println(s[1:3]) // [2 3]
fmt.Println(s[:2]) // [1 2]
fmt.Println(s[2:]) // [3 4 5]
// Копирование среза.
dst := make([]int, len(s))
copy(dst, s)
// Двумерный срез.
grid := [][]int{{1, 2}, {3, 4}}
fmt.Println(grid[1][0]) // 3Maps (словари)
// map — хеш-таблица ключ->значение. Создаём через make или литерал.
ages := make(map[string]int)
ages["Аня"] = 30
ages["Боб"] = 25
// Литерал map.
colors := map[string]string{
"red": "красный",
"green": "зелёный",
}
fmt.Println(ages["Аня"]) // 30
fmt.Println(len(ages)) // 2
// Запрос несуществующего ключа -> нулевое значение типа.
fmt.Println(ages["Нет"]) // 0
// Проверка наличия ключа: запятая-ok.
value, ok := ages["Боб"]
if ok {
fmt.Println("найден:", value) // найден: 25
}
if _, exists := ages["Ева"]; !exists {
fmt.Println("Евы нет в map")
}
// Удаление ключа.
delete(ages, "Боб")
// Перебор (порядок НЕ гарантирован!).
for key, val := range colors {
fmt.Printf("%s=%s ", key, val)
}Структуры и методы
Методы объявляются вне структуры. Получатель-указатель *T позволяет изменять оригинал.
// struct — составной тип из именованных полей.
type Point struct {
X, Y int
}
type Person struct {
Name string
Age int
}
// Создание разными способами.
p1 := Point{1, 2} // по порядку полей
p2 := Point{X: 3, Y: 4} // по именам
var p3 Point // нулевые значения: {0 0}
fmt.Println(p1.X, p2.Y) // 1 4
// Метод с получателем-значением (копия структуры).
func (p Point) Dist() int {
return p.X*p.X + p.Y*p.Y
}
// Метод с получателем-указателем — может ИЗМЕНЯТЬ структуру.
func (p *Point) Scale(k int) {
p.X *= k
p.Y *= k
}
// Вызов методов.
pt := Point{3, 4}
fmt.Println(pt.Dist()) // 25
pt.Scale(2)
fmt.Println(pt) // {6 8}
// Встраивание (композиция вместо наследования).
type Employee struct {
Person // встроенная структура
Salary int
}
e := Employee{Person{"Аня", 30}, 1000}
fmt.Println(e.Name) // Аня — поля встроенной доступны напрямуюФункции
// Обычная функция: func имя(параметры) типВозврата.
func add(a int, b int) int {
return a + b
}
// Множественный возврат — частый приём в Go.
func divmod(a, b int) (int, int) {
return a / b, a % b
}
q, r := divmod(17, 5) // 3, 2
// Именованные результаты + «голый» return.
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // вернёт x и y
}
// Variadic — переменное число аргументов.
func total(nums ...int) int {
s := 0
for _, n := range nums {
s += n
}
return s
}
fmt.Println(total(1, 2, 3)) // 6
xs := []int{4, 5, 6}
fmt.Println(total(xs...)) // 15 — распаковка среза
// Функция как значение и замыкание (closure).
func counter() func() int {
c := 0
return func() int { // захватывает c
c++
return c
}
}
next := counter()
fmt.Println(next(), next()) // 1 2
// defer — отложить вызов до выхода из функции (LIFO порядок).
func demo() {
defer fmt.Println("3-й (выполнится последним)")
defer fmt.Println("2-й")
fmt.Println("1-й")
}
// Вывод: 1-й / 2-й / 3-йУказатели
Go передаёт аргументы по значению. Чтобы изменить переменную вызывающего, передавайте указатель.
// & — взять адрес переменной. * — разыменовать (получить значение).
x := 10
p := &x // p — указатель на x, тип *int
fmt.Println(*p) // 10 — значение по адресу
*p = 20 // меняем x через указатель
fmt.Println(x) // 20
// Нулевое значение указателя — nil.
var ptr *int
fmt.Println(ptr == nil) // true
// Указатели в функциях — изменение оригинала.
func increment(n *int) {
*n++ // меняем значение по адресу
}
y := 5
increment(&y)
fmt.Println(y) // 6
// new — выделить память, вернуть указатель на нулевое значение.
np := new(int) // *int, *np == 0
*np = 42
fmt.Println(*np) // 42
// В Go НЕТ арифметики указателей (в отличие от C) — это безопаснее.Интерфейсы
Интерфейсы в Go удовлетворяются неявно — это «утиная типизация» на уровне компиляции.
// Интерфейс — набор сигнатур методов.
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct{ R float64 }
type Rect struct{ W, H float64 }
// Реализация НЕЯВНАЯ: тип реализует интерфейс,
// просто имея нужные методы (без слова "implements").
func (c Circle) Area() float64 { return 3.14 * c.R * c.R }
func (c Circle) Perimeter() float64 { return 2 * 3.14 * c.R }
func (r Rect) Area() float64 { return r.W * r.H }
func (r Rect) Perimeter() float64 { return 2 * (r.W + r.H) }
// Функция принимает любой Shape.
func describe(s Shape) {
fmt.Printf("площадь=%.1f\n", s.Area())
}
describe(Circle{R: 2}) // площадь=12.6
describe(Rect{W: 3, H: 4}) // площадь=12.0
// Пустой интерфейс interface{} (или any) — любое значение.
var anything interface{} = "строка"
// Type assertion — извлечь конкретный тип.
if str, ok := anything.(string); ok {
fmt.Println("это строка:", str)
}
// Type switch — ветвление по динамическому типу.
switch v := anything.(type) {
case int:
fmt.Println("int", v)
case string:
fmt.Println("string", v)
default:
fmt.Println("другой тип")
}Обработка ошибок
Go не использует try/catch. Ошибки возвращаются явно; panic/recover — только для исключительных случаев.
// В Go ошибки — обычные значения типа error, а не исключения.
import "errors"
// Функция возвращает результат И ошибку.
func safeDiv(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("деление на ноль")
}
return a / b, nil
}
// Идиома: проверка if err != nil.
result, err := safeDiv(10, 0)
if err != nil {
fmt.Println("ошибка:", err) // ошибка: деление на ноль
} else {
fmt.Println("результат:", result)
}
// fmt.Errorf — ошибка с форматированием и обёрткой (%w).
func load(id int) error {
return fmt.Errorf("не найден id=%d", id)
}
// panic — аварийное завершение (для невосстановимых ситуаций).
// recover — перехват паники внутри defer.
func mayPanic() {
defer func() {
if r := recover(); r != nil {
fmt.Println("восстановились после:", r)
}
}()
panic("что-то сломалось")
}
mayPanic() // восстановились после: что-то сломалось
fmt.Println("программа продолжает работу")Горутины и каналы
Горутины — дешёвые конкурентные функции. Каналы синхронизируют их по принципу «общение через передачу данных».
import (
"fmt"
"sync"
)
// Горутина — лёгкий поток. Запуск через ключевое слово go.
func say(s string) { fmt.Println(s) }
go say("асинхронно") // выполнится параллельно
// Канал (channel) — типизированная труба для обмена данными.
ch := make(chan int) // небуферизованный канал
// Отправка ch <- value и приём value := <-ch.
go func() {
ch <- 42 // отправить в канал
}()
v := <-ch // принять из канала (блокирует до получения)
fmt.Println(v) // 42
// Буферизованный канал — не блокирует, пока есть место.
buf := make(chan int, 2)
buf <- 1
buf <- 2
close(buf) // закрыть канал
for n := range buf { // читаем до закрытия
fmt.Print(n, " ") // 1 2
}
// select — ждать на нескольких каналах сразу.
c1, c2 := make(chan string), make(chan string)
go func() { c1 <- "от c1" }()
select {
case msg := <-c1:
fmt.Println(msg)
case msg := <-c2:
fmt.Println(msg)
default:
fmt.Println("нет данных")
}
// sync.WaitGroup — дождаться завершения группы горутин.
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1) // +1 к счётчику
go func(id int) {
defer wg.Done() // -1 по завершении
fmt.Printf("горутина %d\n", id)
}(i)
}
wg.Wait() // блокирует, пока счётчик не станет 0
fmt.Println("все горутины завершились")