rubric/modules/gomodules/go_structures.go
2026-07-21 14:52:55 +03:00

461 lines
22 KiB
Go
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package gomodules
import (
"fmt"
"rubric/utils"
)
var StructuresModule = Module{
Key: "structs",
Aliases: []string{"s", "struct", "vars", "types"},
Title: "Структура программы",
Description: "Имена, объявления, переменные, указатели, типы, пакеты, область видимости",
Run: showStructures,
}
func showStructures() {
utils.Header("Go — Структура программы")
// ─── Имена и ключевые слова ───────────────────────────────────
utils.Section("Имена и ключевые слова")
utils.Println("Имя начинается с буквы (Unicode) или символа подчёркивания, затем — буквы, цифры, _.")
utils.Println("Регистр имеет значение: " +
utils.ColorBold + utils.ColorCyan + "Name" + utils.ColorReset +
" и " +
utils.ColorBold + utils.ColorCyan + "name" + utils.ColorReset +
" — разные идентификаторы.")
fmt.Println()
utils.Println(utils.ColorBold + utils.ColorYellow + "25 ключевых слов языка Go:" + utils.ColorReset)
fmt.Println()
keywords := [][]string{
{"break", "case", "chan", "const", "continue"},
{"default", "defer", "else", "fallthrough", "for"},
{"func", "go", "goto", "if", "import"},
{"interface", "map", "package", "range", "return"},
{"select", "struct", "switch", "type", "var"},
}
for _, row := range keywords {
fmt.Print(" ")
for _, kw := range row {
fmt.Printf("%s%-14s%s", utils.ColorCyan+utils.ColorBold, kw, utils.ColorReset)
}
fmt.Println()
}
fmt.Println()
utils.Println(utils.ColorBold + utils.ColorYellow + "Предопределённые имена (не ключевые слова — можно переопределить, но не стоит):" + utils.ColorReset)
fmt.Println()
utils.Println(utils.ColorDim + " Типы:" + utils.ColorReset)
utils.Println(" bool byte rune string error")
utils.Println(" int int8 int16 int32 int64")
utils.Println(" uint uint8 uint16 uint32 uint64 uintptr")
utils.Println(" float32 float64 complex64 complex128")
fmt.Println()
utils.Println(utils.ColorDim + " Константы:" + utils.ColorReset)
utils.Println(" true false iota")
fmt.Println()
utils.Println(utils.ColorDim + " Нулевое значение:" + utils.ColorReset)
utils.Println(" nil")
fmt.Println()
utils.Println(utils.ColorDim + " Встроенные функции:" + utils.ColorReset)
utils.Println(" append cap close complex copy delete imag")
utils.Println(" len make new panic print println real recover")
fmt.Println()
utils.Note("Если имя начинается с заглавной буквы — оно экспортируется из пакета (публичное).")
utils.Note("Стиль: camelCase для локальных, CamelCase для экспортируемых. Аббревиатуры — заглавными: URL, ID, HTTP.")
// ─── Объявления ───────────────────────────────────────────────
utils.Section("Объявления")
utils.Println("В Go четыре вида объявлений верхнего уровня:")
fmt.Println()
utils.Code(`package main
import "fmt"
// const — константа верхнего уровня
const Greeting = "Привет"
// type — новый тип
type Celsius float64
// var — переменная верхнего уровня
var boilingPoint Celsius = 100
// func — функция
func main() {
fmt.Println(Greeting, boilingPoint)
}`)
utils.Println("Порядок объявлений на верхнем уровне не важен — компилятор разрешает зависимости сам.")
// ─── Переменные ───────────────────────────────────────────────
utils.Section("Переменные и способы объявления")
utils.Println(utils.ColorBold + "1. Полное объявление через var:" + utils.ColorReset)
utils.Code(`var имя тип = выражение
var x int // x == 0 (нулевое значение)
var s string // s == ""
var b bool // b == false
var p *int // p == nil
// Несколько переменных одного типа:
var i, j, k int
// Несколько переменных разных типов:
var (
name string = "Gopher"
age int = 3
active bool = true
)`)
utils.Println(utils.ColorBold + "2. Краткое объявление := (только внутри функций):" + utils.ColorReset)
utils.Code(`имя := выражение // тип выводится автоматически
x := 42 // int
s := "hello" // string
f := 3.14 // float64
b := true // bool
// Несколько переменных:
i, j := 0, 1
name, err := os.Open("file.txt") // часто используется для ошибок
// ВАЖНО: хотя бы одна переменная слева должна быть новой:
x, y := 1, 2 // ok: обе новые
x, y = 3, 4 // ok: присваивание (уже объявлены)
x, z := 5, 6 // ok: z — новая, x — переприсваивается`)
utils.Println(utils.ColorBold + "3. Нулевые значения (zero values):" + utils.ColorReset)
utils.Code(`// Каждый тип имеет нулевое значение при объявлении без инициализации:
var i int // 0
var f float64 // 0.0
var b bool // false
var s string // ""
var p *int // nil
var sl []int // nil
var m map[string]int // nil
var fn func() // nil
var err error // nil`)
utils.Note("В Go нет неинициализированных переменных — нулевое значение гарантировано.")
utils.Println(utils.ColorBold + "4. Объявление нескольких переменных с var-блоком:" + utils.ColorReset)
utils.Code(`// Группировка для читаемости:
var (
debug = false
version = "1.0.0"
maxConn = 100
)`)
// ─── Указатели ────────────────────────────────────────────────
utils.Section("Указатели")
utils.Println("Указатель хранит " + utils.ColorBold + "адрес" + utils.ColorReset + " переменной в памяти.")
utils.Code(`var x int = 42
p := &x // p — указатель на x, тип *int
fmt.Println(p) // 0xc0000b4008 (адрес)
fmt.Println(*p) // 42 (разыменование — значение по адресу)
*p = 100 // изменить значение x через указатель
fmt.Println(x) // 100
// Указатель на указатель:
pp := &p // тип **int
fmt.Println(**pp) // 100`)
utils.Code(`// Нулевое значение указателя — nil:
var p *int
fmt.Println(p == nil) // true
// *p = 1 — паника! нельзя разыменовывать nil-указатель
// Сравнение: указатели равны, если указывают на один адрес:
a, b := 1, 1
pa, pb := &a, &b
fmt.Println(pa == pb) // false (разные переменные)
pa2 := &a
fmt.Println(pa == pa2) // true (один и тот же адрес)`)
utils.Note("Go — безопасный язык: нет арифметики указателей (без пакета unsafe).")
utils.Note("Указатель позволяет функции изменять переменную вызывающего кода.")
// ─── Функция new ──────────────────────────────────────────────
utils.Section("Функция new")
utils.Println("new(T) выделяет память для значения типа T, инициализирует нулевым значением,")
utils.Println("возвращает указатель *T.")
utils.Code(`p := new(int) // *int, *p == 0
fmt.Println(*p) // 0
*p = 7
fmt.Println(*p) // 7
// Эквивалентно:
var tmp int
p2 := &tmp
// new для структур:
type Point struct{ X, Y int }
pt := new(Point) // *Point, pt.X==0, pt.Y==0
pt.X = 10`)
utils.Note("new используется редко — чаще пишут &T{...} для структур или var x T; return &x.")
utils.Code(`// Эти три записи эквивалентны:
p1 := new(int)
var v int
p2 := &v
p3 := &struct{ int }{} // анонимная структура — другой случай`)
// ─── Время жизни переменных ───────────────────────────────────
utils.Section("Время жизни переменных")
utils.Println("Время жизни — период, в течение которого переменная существует в памяти.")
fmt.Println()
utils.Println(utils.ColorBold + "Переменные пакетного уровня:" + utils.ColorReset +
" живут всё время работы программы.")
utils.Println(utils.ColorBold + "Локальные переменные:" + utils.ColorReset +
" создаются при каждом выполнении объявления, удаляются когда становятся недостижимы.")
fmt.Println()
utils.Code(`// Escape analysis — компилятор решает, где хранить переменную:
// стек (быстро) или куча (heap, медленнее, но живёт дольше).
func newInt() *int {
v := 42 // v "убегает" на кучу (heap escape),
return &v // потому что возвращается её адрес.
}
func sum(a, b int) int {
result := a + b // result на стеке — не "убегает",
return result // возвращается значение, не адрес.
}`)
utils.Note("Не нужно думать о стеке/куче вручную — GC Go управляет памятью автоматически.")
utils.Note("go build -gcflags='-m' — показывает, какие переменные уходят на кучу.")
// ─── Присваивания ─────────────────────────────────────────────
utils.Section("Присваивания")
utils.Println(utils.ColorBold + "Простое присваивание:" + utils.ColorReset)
utils.Code(`x = 1
*p = true
person.name = "Алиса"
count[x] = count[x] * scale`)
utils.Println(utils.ColorBold + "Составные операторы присваивания:" + utils.ColorReset)
utils.Code(`x += 1 x -= 1 x *= 2 x /= 2 x %= 3
x &= y x |= y x ^= y x <<= 2 x >>= 1`)
utils.Println(utils.ColorBold + "Инкремент и декремент (только постфиксные, только как операторы):" + utils.ColorReset)
utils.Code(`x++ // x = x + 1
x-- // x = x - 1
// ВАЖНО: в Go нет префиксных ++/-- и нет выражений с ++/--:
// ++x — ошибка компиляции
// y = x++ — ошибка компиляции`)
utils.Println(utils.ColorBold + "Множественное присваивание (tuple assignment):" + utils.ColorReset)
utils.Code(`// Все правые части вычисляются ДО записи в левые:
x, y = y, x // обмен значениями
a, b, c := 1, 2, 3
a, b, c = b, c, a // сдвиг
// Возврат нескольких значений из функции:
quotient, remainder := divide(17, 5)
// Игнорирование значения через _:
_, err = fmt.Println("hi")`)
utils.Println(utils.ColorBold + "Присваиваемость (assignability):" + utils.ColorReset)
utils.Code(`// Присваивание допустимо, если:
// 1. Типы идентичны
// 2. Типы имеют одинаковую базу и хотя бы один — безымянный
// 3. T реализует интерфейс I
// 4. Присваивание nil совместимому типу (chan, func, interface, map, pointer, slice)
var i interface{} = "строка" // string → interface{}
var n int = 42
var f float64 = float64(n) // явное преобразование типов`)
// ─── Объявления типов ────────────────────────────────────────
utils.Section("Объявления типов")
utils.Println("type создаёт " + utils.ColorBold + "новый именованный тип" + utils.ColorReset + " с тем же базовым представлением.")
utils.Code(`type Celsius float64
type Fahrenheit float64
var boiling Celsius = 100
// Celsius и Fahrenheit — РАЗНЫЕ типы, хотя оба float64:
// boiling = Fahrenheit(212) — ошибка компиляции!
// Явное преобразование разрешено (одно базовое представление):
f := Fahrenheit(boiling*9/5 + 32)
fmt.Println(f) // 212`)
utils.Code(`// Методы можно добавлять только к именованным типам:
func (c Celsius) String() string {
return fmt.Sprintf("%.2f°C", float64(c))
}
fmt.Println(boiling) // 100.00°C (автоматически вызывается String())`)
utils.Code(`// type-алиас (псевдоним) — НЕ создаёт новый тип, это то же самое:
type Rune = int32 // rune — именно так определён в builtin
// Отличие: алиас и оригинал взаимозаменяемы без конверсии.
// Обычный type — нет.`)
utils.Note("type позволяет добавлять методы к базовым типам (int, string, slice и т.д.).")
utils.Note("Явное преобразование T(v) работает если оба типа имеют одно представление.")
// ─── Пакеты, файлы и импорт ──────────────────────────────────
utils.Section("Пакеты, файлы и импорт")
utils.Println("Пакет — единица инкапсуляции и переиспользования в Go.")
utils.Println("Все .go-файлы в одной директории — один пакет. Имя объявляется в первой строке.")
fmt.Println()
utils.Code(`// Каждый файл начинается с объявления пакета:
package main // исполняемая программа
package utils // библиотечный пакет
package http // пакет стандартной библиотеки`)
utils.Println(utils.ColorBold + "Импорт пакетов:" + utils.ColorReset)
utils.Code(`// Одиночный импорт:
import "fmt"
// Групповой импорт (предпочтительный стиль):
import (
"fmt"
"os"
"strings"
"github.com/user/project/utils" // внешний пакет
)`)
utils.Println(utils.ColorBold + "Формы импорта:" + utils.ColorReset)
utils.Code(`import "fmt" // стандартный: fmt.Println(...)
import f "fmt" // псевдоним: f.Println(...)
import . "fmt" // слияние: Println(...) (не рекомендуется)
import _ "image/png" // только для side-эффектов (init)`)
utils.Note("Неиспользуемый импорт — ошибка компиляции. Используйте _ для подавления.")
utils.Note("Имя пакета и имя директории могут отличаться, но по соглашению совпадают.")
utils.Code(`// Путь импорта vs имя пакета:
import "math/rand" // путь: math/rand
rand.Intn(10) // имя пакета: rand
import "crypto/rand" // другой пакет, то же имя!
// Нужен алиас:
import (
mrand "math/rand"
crand "crypto/rand"
)`)
// ─── Инициализация пакетов ────────────────────────────────────
utils.Section("Инициализация пакетов")
utils.Println("Инициализация переменных пакетного уровня происходит в порядке объявления,")
utils.Println("с учётом зависимостей между ними.")
utils.Code(`// Порядок гарантирован: a, b, c
var a = 1
var b = a + 1 // 2
var c = b * 2 // 4
// Сложные случаи — через функцию init():
var lookup = buildLookup()
func buildLookup() map[string]int {
// ...
return m
}`)
utils.Println(utils.ColorBold + "Функция init():" + utils.ColorReset)
utils.Code(`// init — специальная функция инициализации пакета:
// - вызывается автоматически при запуске программы
// - нельзя вызвать явно
// - в одном файле может быть несколько init()
// - в одном пакете — несколько init() в разных файлах
func init() {
// подготовка состояния, регистрация, настройка
log.SetFlags(log.Lshortfile)
}`)
utils.Println(utils.ColorBold + "Порядок инициализации всей программы:" + utils.ColorReset)
utils.Code(`// 1. Инициализируются импортируемые пакеты (рекурсивно, от листьев к корню)
// 2. Переменные пакетного уровня в порядке объявления / зависимостей
// 3. Вызываются все функции init() данного пакета
// 4. ... переход к следующему пакету
// 5. Последним инициализируется пакет main
// 6. Вызывается main.main()
// import _ "pkg" — импортировать только ради init()
// Пример: регистрация драйвера БД:
import _ "github.com/lib/pq" // PostgreSQL driver`)
utils.Note("Порядок вызова init() между файлами одного пакета определяется алфавитным порядком имён файлов.")
// ─── Область видимости ───────────────────────────────────────
utils.Section("Область видимости (Scope)")
utils.Println("Область видимости — часть кода, в которой объявление действует.")
utils.Println("Это " + utils.ColorBold + "статическое" + utils.ColorReset + " свойство (на уровне компиляции), в отличие от времени жизни (runtime).")
fmt.Println()
utils.Code(`// Уровни области видимости (от широкого к узкому):
// 1. Вселенная (universe) — встроенные имена: int, len, nil...
// 2. Пакет — объявления на верхнем уровне файла
// 3. Файл — только import-пути
// 4. Локальный — блок { }, параметры функции, for, if, switch...
var x = "пакет" // область видимости — весь пакет
func f() {
fmt.Println(x) // "пакет"
x := "локальный" // новая переменная x (shadowing!)
fmt.Println(x) // "локальный"
{
x := "вложенный"
fmt.Println(x) // "вложенный"
}
fmt.Println(x) // "локальный"
}`)
utils.Println(utils.ColorBold + "Затенение (shadowing) в управляющих конструкциях:" + utils.ColorReset)
utils.Code(`// if-инициализатор:
if x := compute(); x > 0 {
// x видна только здесь...
} else {
// ...и здесь
}
// x здесь уже не видна
// Распространённая ошибка с := в if:
var err error
if err := doA(); err != nil { // новая err!
return err
}
// err (внешняя) всё ещё nil — doA не изменил её!
fmt.Println(err) // nil`)
utils.Println(utils.ColorBold + "Экспортируемые имена — публичный API пакета:" + utils.ColorReset)
utils.Code(`package geometry
// Экспортируется (заглавная буква):
type Point struct {
X, Y float64 // поля тоже экспортируются
}
func Distance(p, q Point) float64 { ... }
// НЕ экспортируется (строчная буква):
type circle struct { center Point; radius float64 }
func area(c circle) float64 { ... }`)
utils.Note("Экспортируемость — единственный механизм инкапсуляции в Go. Нет private/protected/public ключевых слов.")
utils.Note("Правило: экспортируй только то, что является частью публичного API. Всё остальное — строчными буквами.")
utils.Footer()
}