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() }