package gomodules import ( "fmt" "rubric/utils" ) var CompositeModule = Module{ Key: "composite", Aliases: []string{"c", "comp", "slice", "map", "struct"}, Title: "Составные типы", Description: "Массивы, срезы, append, отображения, структуры, JSON и HTML", Run: showComposite, } func showComposite() { utils.Header("Go — Составные типы") // ─── Массивы ────────────────────────────────────────────────── utils.Section("Массивы (Arrays)") utils.Println("Массив — фиксированной длины последовательность элементов одного типа.") utils.Println("Длина является частью типа: " + utils.ColorBold + utils.ColorCyan + "[3]int" + utils.ColorReset + " и " + utils.ColorBold + utils.ColorCyan + "[4]int" + utils.ColorReset + " — разные типы.") fmt.Println() utils.Code(`// Объявление: var a [3]int // [0 0 0] — нулевые значения var b [3]int = [3]int{1, 2, 3} c := [3]int{1, 2, 3} d := [...]int{1, 2, 3, 4, 5} // компилятор считает длину сам // Инициализация с индексами: e := [5]string{1: "один", 3: "три"} // остальные — "" // Доступ: fmt.Println(c[0]) // 1 fmt.Println(len(c)) // 3 // Итерация: for i, v := range c { fmt.Printf("c[%d] = %d\n", i, v) }`) utils.Println(utils.ColorBold + "Сравнение массивов:" + utils.ColorReset) utils.Code(`// Массивы можно сравнивать через == и != // (если элементы сравниваемы): a := [3]int{1, 2, 3} b := [3]int{1, 2, 3} fmt.Println(a == b) // true // Передача в функцию — КОПИЯ: func double(arr [3]int) { arr[0] *= 2 } // оригинал не меняется // Чтобы изменить — передавать указатель: *[3]int`) utils.Note("Массивы в Go используются редко — чаще работают со срезами.") // ─── Срезы ──────────────────────────────────────────────────── utils.Section("Срезы (Slices)") utils.Println("Срез — динамический вид на массив. Три компонента:") utils.Code(`// Указатель на массив + длина (len) + ёмкость (cap) // // ┌─────────────────────────────────────────┐ // │ ptr │ len │ cap │ // └───┬───┴───────┴─────────────────────────┘ // │ // ▼ // ┌───┬───┬───┬───┬───┐ // │ 0 │ 1 │ 2 │ 3 │ 4 │ ← underlying array // └───┴───┴───┴───┴───┘`) utils.Println(utils.ColorBold + "Создание срезов:" + utils.ColorReset) utils.Code(`// Из литерала (неявно создаёт массив): s := []int{1, 2, 3, 4, 5} // Срез нулевых значений через make(тип, len, cap): s := make([]int, 5) // len=5, cap=5 s := make([]int, 3, 10) // len=3, cap=10 // Нулевой срез (nil slice): var s []int // s == nil, len=0, cap=0 fmt.Println(s == nil) // true // nil срез безопасен для append и range // Срез из массива: a := [5]int{1, 2, 3, 4, 5} s := a[1:4] // элементы 1,2,3 → [2 3 4] // len=3, cap=4 (до конца массива)`) utils.Println(utils.ColorBold + "Операции срезов:" + utils.ColorReset) utils.Code(`s := []int{1, 2, 3, 4, 5} // Взятие среза: s[low:high] (low включён, high — нет) s[1:3] // [2 3] len=2 s[:3] // [1 2 3] low=0 s[2:] // [3 4 5] high=len s[:] // копия ссылки на весь срез // Трёхиндексный срез: s[low:high:max] — ограничивает cap: s[1:3:4] // len=2, cap=3 // len и cap: fmt.Println(len(s), cap(s)) // 5 5 // Срезы разделяют память с оригиналом! a := []int{1, 2, 3, 4, 5} b := a[1:3] // b = [2 3] b[0] = 99 fmt.Println(a) // [1 99 3 4 5] — оригинал изменён!`) utils.Println(utils.ColorBold + "Копирование срезов (copy):" + utils.ColorReset) utils.Code(`src := []int{1, 2, 3, 4, 5} dst := make([]int, 3) n := copy(dst, src) // копирует min(len(dst), len(src)) элементов fmt.Println(dst, n) // [1 2 3] 3 // Полная независимая копия: clone := make([]int, len(src)) copy(clone, src) // Или через append: clone := append([]int{}, src...)`) utils.Println(utils.ColorBold + "Итерация:" + utils.ColorReset) utils.Code(`s := []string{"a", "b", "c"} for i, v := range s { // v — копия элемента fmt.Println(i, v) } for i := range s { // только индексы s[i] = strings.ToUpper(s[i]) } for _, v := range s { // только значения fmt.Println(v) }`) // ─── append ─────────────────────────────────────────────────── utils.Section("Функция append") utils.Println("append добавляет элементы в срез, возвращая новый (возможно, с новым массивом).") utils.Code(`// Добавить элементы: s := []int{1, 2, 3} s = append(s, 4) // [1 2 3 4] s = append(s, 5, 6, 7) // [1 2 3 4 5 6 7] // Добавить другой срез (оператор ...): other := []int{8, 9} s = append(s, other...) // [1 2 3 4 5 6 7 8 9] // Удалить элемент по индексу i: i := 2 s = append(s[:i], s[i+1:]...) // порядок сохраняется // Быстрое удаление (порядок не важен): s[i] = s[len(s)-1] s = s[:len(s)-1] // Вставить элемент по индексу i: s = append(s[:i+1], s[i:]...) s[i] = newValue // Стек (stack): push := func(s []int, v int) []int { return append(s, v) } pop := func(s []int) ([]int, int) { return s[:len(s)-1], s[len(s)-1] }`) utils.Println(utils.ColorBold + "Механизм роста ёмкости:" + utils.ColorReset) utils.Code(`// При нехватке cap, append выделяет новый массив (~удваивает cap): var s []int for i := 0; i < 8; i++ { s = append(s, i) fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) } // len=1 cap=1 [0] // len=2 cap=2 [0 1] // len=3 cap=4 [0 1 2] // len=4 cap=4 [0 1 2 3] // len=5 cap=8 [0 1 2 3 4] // ...`) utils.Note("Всегда присваивай результат append обратно: s = append(s, v)") utils.Note("Передавай срез с запасом через make([]T, 0, n) если знаешь итоговый размер — избежишь лишних аллокаций.") // ─── Отображения ────────────────────────────────────────────── utils.Section("Отображения (Maps)") utils.Println("Map — ссылочный тип, хэш-таблица. Порядок итерации не гарантирован.") fmt.Println() utils.Println(utils.ColorBold + "Создание:" + utils.ColorReset) utils.Code(`// Через литерал: ages := map[string]int{ "Alice": 30, "Bob": 25, } // Через make: ages := make(map[string]int) ages := make(map[string]int, 100) // с подсказкой ёмкости // Нулевое значение — nil: var m map[string]int // m == nil // m["key"] = 1 // паника! нельзя писать в nil map // fmt.Println(m["key"]) // 0 — читать можно, не паникует`) utils.Println(utils.ColorBold + "Операции с map:" + utils.ColorReset) utils.Code(`ages := map[string]int{"Alice": 30, "Bob": 25} // Запись: ages["Carol"] = 28 ages["Alice"] = 31 // обновить // Чтение: age := ages["Alice"] // 31 age = ages["unknown"] // 0 — нулевое значение, НЕ ошибка // Проверка наличия ключа (comma ok idiom): age, ok := ages["Alice"] if ok { fmt.Println("Alice:", age) } // Коротко: if age, ok := ages["Alice"]; ok { fmt.Println("Alice:", age) } // Удаление: delete(ages, "Bob") delete(ages, "nonexistent") // безопасно, нет ошибки // Длина: fmt.Println(len(ages)) // 2 // Итерация (порядок случаен!): for name, age := range ages { fmt.Printf("%s: %d\n", name, age) } // Итерация по отсортированным ключам: keys := make([]string, 0, len(ages)) for k := range ages { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Println(k, ages[k]) }`) utils.Println(utils.ColorBold + "Сравнение map:" + utils.ColorReset) utils.Code(`// Map нельзя сравнивать через == (только с nil): var m map[string]int fmt.Println(m == nil) // true // Сравнение содержимого — через reflect.DeepEqual или вручную: import "reflect" fmt.Println(reflect.DeepEqual(map1, map2)) // Или maps.Equal из Go 1.21: import "maps" fmt.Println(maps.Equal(map1, map2))`) utils.Println(utils.ColorBold + "Типичные паттерны:" + utils.ColorReset) utils.Code(`// Множество (set): seen := make(map[string]bool) seen["Alice"] = true if seen["Alice"] { ... } // Подсчёт слов: words := strings.Fields(text) count := make(map[string]int) for _, w := range words { count[w]++ } // Группировка: byDept := make(map[string][]Employee) for _, e := range employees { byDept[e.Dept] = append(byDept[e.Dept], e) }`) // ─── Структуры ──────────────────────────────────────────────── utils.Section("Структуры (Structs)") utils.Println("Struct — агрегатный тип, объединяет поля разных типов.") utils.Code(`// Объявление типа: type Point struct { X, Y float64 } type Employee struct { ID int Name string Salary float64 Manager *Employee // рекурсивный тип — только указатель }`) utils.Println(utils.ColorBold + "Создание и доступ:" + utils.ColorReset) utils.Code(`// Структурный литерал (по именам — рекомендуется): p := Point{X: 1.0, Y: 2.0} p := Point{1.0, 2.0} // по позиции (хрупко, не для публичных типов) // Нулевое значение: var p Point // Point{X:0, Y:0} // Доступ к полям: fmt.Println(p.X) // 1.0 p.Y = 5.0 // Указатель на структуру: pp := &Point{1.0, 2.0} pp.X = 10 // (*pp).X = 10 — автодеференс fmt.Println(pp.X) // 10 // Через new: pp := new(Point) // *Point, поля = нули pp.X = 1.0`) utils.Println(utils.ColorBold + "Сравнение структур:" + utils.ColorReset) utils.Code(`// Структуры сравниваемы если все поля сравниваемы: type Point struct{ X, Y int } p1 := Point{1, 2} p2 := Point{1, 2} fmt.Println(p1 == p2) // true // Поля-срезы, map, func — делают структуру несравниваемой. // Используй reflect.DeepEqual для глубокого сравнения. // Структура как ключ map (только если сравниваема): type Coord struct{ Lat, Lon float64 } visited := map[Coord]bool{} visited[Coord{55.75, 37.62}] = true`) // ─── Встраивание и анонимные поля ──────────────────────────── utils.Section("Встраивание структур и анонимные поля") utils.Println("Встраивание (embedding) — включение типа без имени поля.") utils.Println("Поля и методы встроенного типа «поднимаются» во внешний тип.") utils.Code(`type Animal struct { Name string } func (a Animal) Speak() string { return a.Name + " говорит" } type Dog struct { Animal // анонимное поле — встраивание Breed string } d := Dog{ Animal: Animal{Name: "Шарик"}, Breed: "Лабрадор", } // Продвинутый доступ (promotion): fmt.Println(d.Name) // "Шарик" (d.Animal.Name) fmt.Println(d.Speak()) // "Шарик говорит" (d.Animal.Speak()) fmt.Println(d.Breed) // "Лабрадор" // Явный доступ: fmt.Println(d.Animal.Name)`) utils.Code(`// Несколько встраиваний: type Logger struct{ Prefix string } func (l Logger) Log(msg string) { fmt.Println(l.Prefix, msg) } type Server struct { Logger // анонимное поле net.Listener // встроен интерфейс Host string } s := Server{Logger: Logger{Prefix: "[server]"}, Host: "localhost"} s.Log("started") // s.Logger.Log("started") // Конфликт имён — нужно явно указывать путь: type A struct{ X int } type B struct{ X int } type C struct { A; B } c := C{} // c.X — ошибка компиляции: ambiguous c.A.X = 1 // явно`) utils.Code(`// Встраивание указателя: type Cache struct { *sync.Mutex // анонимный указатель data map[string]string } c := Cache{ Mutex: &sync.Mutex{}, data: make(map[string]string), } c.Lock() // c.Mutex.Lock() c.data["key"] = "value" c.Unlock()`) utils.Note("Встраивание — это композиция, не наследование. Go не имеет наследования.") // ─── Работа с JSON ──────────────────────────────────────────── utils.Section("Работа с JSON") utils.Println("Пакет " + utils.ColorBold + utils.ColorCyan + "encoding/json" + utils.ColorReset + " — маршалинг и демаршалинг JSON.") fmt.Println() utils.Println(utils.ColorBold + "Теги структур:" + utils.ColorReset) utils.Code(`type Person struct { Name string ` + "`" + `json:"name"` + "`" + ` Age int ` + "`" + `json:"age"` + "`" + ` Email string ` + "`" + `json:"email,omitempty"` + "`" + ` // пропустить если пусто Phone string ` + "`" + `json:"-"` + "`" + ` // никогда не включать private string // неэкспортируемые — игнорируются } // Теги: // json:"name" — имя в JSON // json:"name,omitempty"— пропустить нулевые значения // json:"-" — исключить поле`) utils.Println(utils.ColorBold + "Маршалинг (структура → JSON):" + utils.ColorReset) utils.Code(`import "encoding/json" p := Person{Name: "Alice", Age: 30, Email: "alice@example.com"} // Сериализация: data, err := json.Marshal(p) if err != nil { log.Fatal(err) } fmt.Println(string(data)) // {"name":"Alice","age":30,"email":"alice@example.com"} // Красивый вывод с отступами: data, err = json.MarshalIndent(p, "", " ") fmt.Println(string(data)) // { // "name": "Alice", // "age": 30, // "email": "alice@example.com" // }`) utils.Println(utils.ColorBold + "Демаршалинг (JSON → структура):" + utils.ColorReset) utils.Code(`jsonStr := ` + "`" + `{"name":"Bob","age":25}` + "`" + ` var p Person if err := json.Unmarshal([]byte(jsonStr), &p); err != nil { log.Fatal(err) } fmt.Println(p.Name, p.Age) // Bob 25 // Потоковое чтение из http.Response: resp, _ := http.Get("https://api.example.com/users") defer resp.Body.Close() var users []Person json.NewDecoder(resp.Body).Decode(&users) // Потоковая запись в http.ResponseWriter: json.NewEncoder(w).Encode(users)`) utils.Println(utils.ColorBold + "Произвольный JSON (interface{} / any):" + utils.ColorReset) utils.Code(`// Когда структура неизвестна: var result map[string]any json.Unmarshal(data, &result) name := result["name"].(string) // type assertion age := result["age"].(float64) // числа → float64 по умолчанию // Проверка типа: if s, ok := result["name"].(string); ok { fmt.Println(s) } // json.RawMessage — отложенный демаршалинг: type Response struct { Status string ` + "`" + `json:"status"` + "`" + ` Payload json.RawMessage ` + "`" + `json:"payload"` + "`" + ` } var resp Response json.Unmarshal(data, &resp) var user Person json.Unmarshal(resp.Payload, &user)`) utils.Println(utils.ColorBold + "Кастомные маршалеры:" + utils.ColorReset) utils.Code(`// Реализовать json.Marshaler / json.Unmarshaler: type Duration time.Duration func (d Duration) MarshalJSON() ([]byte, error) { return json.Marshal(time.Duration(d).String()) } func (d *Duration) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } dur, err := time.ParseDuration(s) *d = Duration(dur) return err }`) // ─── Работа с HTML ──────────────────────────────────────────── utils.Section("Работа с HTML") utils.Println("Пакет " + utils.ColorBold + utils.ColorCyan + "html/template" + utils.ColorReset + " — безопасные HTML-шаблоны с автоматическим экранированием.") fmt.Println() utils.Code(`import "html/template" // Шаблон: const tmpl = ` + "`" + `

Привет, {{.Name}}!

{{if .Admin}}

Вы администратор

{{end}} ` + "`" + ` type Data struct { Name string Items []string Admin bool } // Парсинг и выполнение: t := template.Must(template.New("page").Parse(tmpl)) data := Data{ Name: "Alice", Items: []string{"Go", "Docker", "Kubernetes"}, Admin: true, } // В os.Stdout: t.Execute(os.Stdout, data) // В http.ResponseWriter: func handler(w http.ResponseWriter, r *http.Request) { t.Execute(w, data) }`) utils.Println(utils.ColorBold + "Экранирование и безопасность:" + utils.ColorReset) utils.Code(`// html/template автоматически экранирует пользовательский ввод: // → <script>alert(1)</script> // Если нужно вставить безопасный HTML — использовать template.HTML: type Page struct { Title string Content template.HTML // не экранируется } // Никогда не делай: // template.HTML(userInput) — XSS! // text/template — без экранирования (для не-HTML): import "text/template" // Используй для генерации кода, конфигов, emails в plain text`) utils.Println(utils.ColorBold + "Шаблонные функции и Actions:" + utils.ColorReset) utils.Code(`// Функции в шаблоне: funcMap := template.FuncMap{ "upper": strings.ToUpper, "add": func(a, b int) int { return a + b }, } t := template.New("t").Funcs(funcMap).Parse(` + "`" + ` {{.Name | upper}} {{add 2 3}} ` + "`" + `) // Actions (действия): {{/* комментарий */}} {{.Field}} // вывести поле {{.Method arg}} // вызов метода {{if .Cond}} ... {{end}} {{if .Cond}} ... {{else}} ... {{end}} {{range .Slice}} {{.}} {{end}} {{range .Map}} {{.}} {{end}} {{with .Val}} {{.}} {{end}} // установить . = Val если не ноль {{template "name" .}} // вложенный шаблон // Шаблоны из файлов: t := template.Must(template.ParseFiles("index.html", "base.html")) t := template.Must(template.ParseGlob("templates/*.html"))`) utils.Println(utils.ColorBold + "Парсинг HTML (html пакет):" + utils.ColorReset) utils.Code(`import "html" // Экранирование и разэкранирование HTML-сущностей: escaped := html.EscapeString("") // <script>alert('xss')</script> unescaped := html.UnescapeString("<b>жирный</b>") // жирный // Для разбора HTML-дерева — golang.org/x/net/html: import "golang.org/x/net/html" resp, _ := http.Get("https://example.com") doc, _ := html.Parse(resp.Body) // Рекурсивный обход: var walk func(*html.Node) walk = func(n *html.Node) { if n.Type == html.ElementNode && n.Data == "a" { for _, attr := range n.Attr { if attr.Key == "href" { fmt.Println(attr.Val) } } } for c := n.FirstChild; c != nil; c = c.NextSibling { walk(c) } } walk(doc)`) utils.Footer() }